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  }