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