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