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