package common import ( "sync/atomic" ) // val: the value returned after task execution. // err: the error returned during task completion. // abort: tells Parallel to return, whether or not all tasks have completed. type Task func(i int) (val interface{}, err error, abort bool) type TaskResult struct { Value interface{} Error error Panic interface{} } type TaskResultCh <-chan TaskResult // Run tasks in parallel, with ability to abort early. // Returns ok=false iff any of the tasks returned abort=true. // NOTE: Do not implement quit features here. Instead, provide convenient // concurrent quit-like primitives, passed implicitly via Task closures. (e.g. // it's not Parallel's concern how you quit/abort your tasks). func Parallel(tasks ...Task) (chz []TaskResultCh, ok bool) { var taskResultChz = make([]TaskResultCh, len(tasks)) // To return. var taskDoneCh = make(chan bool, len(tasks)) // A "wait group" channel, early abort if any true received. var numPanics = new(int32) // Keep track of panics to set ok=false later. ok = true // We will set it to false iff any tasks panic'd or returned abort. // Start all tasks in parallel in separate goroutines. // When the task is complete, it will appear in the // respective taskResultCh (associated by task index). for i, task := range tasks { var taskResultCh = make(chan TaskResult, 1) // Capacity for 1 result. taskResultChz[i] = taskResultCh go func(i int, task Task, taskResultCh chan TaskResult) { // Recovery defer func() { if pnk := recover(); pnk != nil { atomic.AddInt32(numPanics, 1) taskResultCh <- TaskResult{nil, nil, pnk} taskDoneCh <- false } }() // Run the task. var val, err, abort = task(i) // Send val/err to taskResultCh. // NOTE: Below this line, nothing must panic/ taskResultCh <- TaskResult{val, err, nil} // Decrement waitgroup. taskDoneCh <- abort }(i, task, taskResultCh) } // Wait until all tasks are done, or until abort. // DONE_LOOP: for i := 0; i < len(tasks); i++ { abort := <-taskDoneCh if abort { ok = false break } } // Ok is also false if there were any panics. // We must do this check here (after DONE_LOOP). ok = ok && (atomic.LoadInt32(numPanics) == 0) // Caller can use this however they want. // TODO: implement convenience functions to // make sense of this structure safely. return taskResultChz, ok }