You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
4.7 KiB

9 years ago
9 years ago
9 years ago
  1. package async
  2. import (
  3. "fmt"
  4. "runtime"
  5. "sync/atomic"
  6. )
  7. //----------------------------------------
  8. // Task
  9. // val: the value returned after task execution.
  10. // err: the error returned during task completion.
  11. // abort: tells Parallel to return, whether or not all tasks have completed.
  12. type Task func(i int) (val interface{}, abort bool, err error)
  13. type TaskResult struct {
  14. Value interface{}
  15. Error error
  16. }
  17. type TaskResultCh <-chan TaskResult
  18. type taskResultOK struct {
  19. TaskResult
  20. OK bool
  21. }
  22. type TaskResultSet struct {
  23. chz []TaskResultCh
  24. results []taskResultOK
  25. }
  26. func newTaskResultSet(chz []TaskResultCh) *TaskResultSet {
  27. return &TaskResultSet{
  28. chz: chz,
  29. results: make([]taskResultOK, len(chz)),
  30. }
  31. }
  32. func (trs *TaskResultSet) Channels() []TaskResultCh {
  33. return trs.chz
  34. }
  35. func (trs *TaskResultSet) LatestResult(index int) (TaskResult, bool) {
  36. if len(trs.results) <= index {
  37. return TaskResult{}, false
  38. }
  39. resultOK := trs.results[index]
  40. return resultOK.TaskResult, resultOK.OK
  41. }
  42. // NOTE: Not concurrency safe.
  43. // Writes results to trs.results without waiting for all tasks to complete.
  44. func (trs *TaskResultSet) Reap() *TaskResultSet {
  45. for i := 0; i < len(trs.results); i++ {
  46. var trch = trs.chz[i]
  47. select {
  48. case result, ok := <-trch:
  49. if ok {
  50. // Write result.
  51. trs.results[i] = taskResultOK{
  52. TaskResult: result,
  53. OK: true,
  54. }
  55. }
  56. // else {
  57. // We already wrote it.
  58. // }
  59. default:
  60. // Do nothing.
  61. }
  62. }
  63. return trs
  64. }
  65. // NOTE: Not concurrency safe.
  66. // Like Reap() but waits until all tasks have returned or panic'd.
  67. func (trs *TaskResultSet) Wait() *TaskResultSet {
  68. for i := 0; i < len(trs.results); i++ {
  69. var trch = trs.chz[i]
  70. result, ok := <-trch
  71. if ok {
  72. // Write result.
  73. trs.results[i] = taskResultOK{
  74. TaskResult: result,
  75. OK: true,
  76. }
  77. }
  78. // else {
  79. // We already wrote it.
  80. // }
  81. }
  82. return trs
  83. }
  84. // Returns the firstmost (by task index) error as
  85. // discovered by all previous Reap() calls.
  86. func (trs *TaskResultSet) FirstValue() interface{} {
  87. for _, result := range trs.results {
  88. if result.Value != nil {
  89. return result.Value
  90. }
  91. }
  92. return nil
  93. }
  94. // Returns the firstmost (by task index) error as
  95. // discovered by all previous Reap() calls.
  96. func (trs *TaskResultSet) FirstError() error {
  97. for _, result := range trs.results {
  98. if result.Error != nil {
  99. return result.Error
  100. }
  101. }
  102. return nil
  103. }
  104. //----------------------------------------
  105. // Parallel
  106. // Run tasks in parallel, with ability to abort early.
  107. // Returns ok=false iff any of the tasks returned abort=true.
  108. // NOTE: Do not implement quit features here. Instead, provide convenient
  109. // concurrent quit-like primitives, passed implicitly via Task closures. (e.g.
  110. // it's not Parallel's concern how you quit/abort your tasks).
  111. func Parallel(tasks ...Task) (trs *TaskResultSet, ok bool) {
  112. var taskResultChz = make([]TaskResultCh, len(tasks)) // To return.
  113. var taskDoneCh = make(chan bool, len(tasks)) // A "wait group" channel, early abort if any true received.
  114. var numPanics = new(int32) // Keep track of panics to set ok=false later.
  115. // We will set it to false iff any tasks panic'd or returned abort.
  116. ok = true
  117. // Start all tasks in parallel in separate goroutines.
  118. // When the task is complete, it will appear in the
  119. // respective taskResultCh (associated by task index).
  120. for i, task := range tasks {
  121. var taskResultCh = make(chan TaskResult, 1) // Capacity for 1 result.
  122. taskResultChz[i] = taskResultCh
  123. go func(i int, task Task, taskResultCh chan TaskResult) {
  124. // Recovery
  125. defer func() {
  126. if pnk := recover(); pnk != nil {
  127. atomic.AddInt32(numPanics, 1)
  128. // Send panic to taskResultCh.
  129. const size = 64 << 10
  130. buf := make([]byte, size)
  131. buf = buf[:runtime.Stack(buf, false)]
  132. taskResultCh <- TaskResult{nil, fmt.Errorf("panic in task %v : %s", pnk, buf)}
  133. // Closing taskResultCh lets trs.Wait() work.
  134. close(taskResultCh)
  135. // Decrement waitgroup.
  136. taskDoneCh <- false
  137. }
  138. }()
  139. // Run the task.
  140. var val, abort, err = task(i)
  141. // Send val/err to taskResultCh.
  142. // NOTE: Below this line, nothing must panic/
  143. taskResultCh <- TaskResult{val, err}
  144. // Closing taskResultCh lets trs.Wait() work.
  145. close(taskResultCh)
  146. // Decrement waitgroup.
  147. taskDoneCh <- abort
  148. }(i, task, taskResultCh)
  149. }
  150. // Wait until all tasks are done, or until abort.
  151. // DONE_LOOP:
  152. for i := 0; i < len(tasks); i++ {
  153. abort := <-taskDoneCh
  154. if abort {
  155. ok = false
  156. break
  157. }
  158. }
  159. // Ok is also false if there were any panics.
  160. // We must do this check here (after DONE_LOOP).
  161. ok = ok && (atomic.LoadInt32(numPanics) == 0)
  162. return newTaskResultSet(taskResultChz).Reap(), ok
  163. }