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  }