github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/util/util.go (about)

     1  package util
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"reflect"
     7  
     8  	"github.com/onflow/flow-go/module"
     9  	"github.com/onflow/flow-go/module/irrecoverable"
    10  )
    11  
    12  // AllReady calls Ready on all input components and returns a channel that is
    13  // closed when all input components are ready.
    14  func AllReady(components ...module.ReadyDoneAware) <-chan struct{} {
    15  	readyChans := make([]<-chan struct{}, len(components))
    16  
    17  	for i, c := range components {
    18  		readyChans[i] = c.Ready()
    19  	}
    20  
    21  	return AllClosed(readyChans...)
    22  }
    23  
    24  // AllDone calls Done on all input components and returns a channel that is
    25  // closed when all input components are done.
    26  func AllDone(components ...module.ReadyDoneAware) <-chan struct{} {
    27  	doneChans := make([]<-chan struct{}, len(components))
    28  
    29  	for i, c := range components {
    30  		doneChans[i] = c.Done()
    31  	}
    32  
    33  	return AllClosed(doneChans...)
    34  }
    35  
    36  // AllClosed returns a channel that is closed when all input channels are closed.
    37  func AllClosed(channels ...<-chan struct{}) <-chan struct{} {
    38  	done := make(chan struct{})
    39  	if len(channels) == 0 {
    40  		close(done)
    41  		return done
    42  	}
    43  
    44  	go func() {
    45  		for _, ch := range channels {
    46  			<-ch
    47  		}
    48  		close(done)
    49  	}()
    50  
    51  	return done
    52  }
    53  
    54  // WaitClosed waits for either a signal/close on the channel or for the context to be cancelled
    55  // Returns nil if the channel was signalled/closed before returning, otherwise, it returns the context
    56  // error.
    57  //
    58  // This handles the corner case where the context is cancelled at the same time that the channel
    59  // is closed, and the Done case was selected.
    60  // This is intended for situations where ignoring a signal can cause safety issues.
    61  func WaitClosed(ctx context.Context, ch <-chan struct{}) error {
    62  	select {
    63  	case <-ctx.Done():
    64  		select {
    65  		case <-ch:
    66  			return nil
    67  		default:
    68  		}
    69  		return ctx.Err()
    70  	case <-ch:
    71  		return nil
    72  	}
    73  }
    74  
    75  // CheckClosed checks if the provided channel has a signal or was closed.
    76  // Returns true if the channel was signaled/closed, otherwise, returns false.
    77  //
    78  // This is intended to reduce boilerplate code when multiple channel checks are required because
    79  // missed signals could cause safety issues.
    80  func CheckClosed(done <-chan struct{}) bool {
    81  	select {
    82  	case <-done:
    83  		return true
    84  	default:
    85  		return false
    86  	}
    87  }
    88  
    89  // MergeChannels merges a list of channels into a single channel
    90  func MergeChannels(channels interface{}) interface{} {
    91  	sliceType := reflect.TypeOf(channels)
    92  	if sliceType.Kind() != reflect.Slice && sliceType.Kind() != reflect.Array {
    93  		panic("argument must be an array or slice")
    94  	}
    95  	chanType := sliceType.Elem()
    96  	if chanType.ChanDir() == reflect.SendDir {
    97  		panic("channels cannot be send-only")
    98  	}
    99  	c := reflect.ValueOf(channels)
   100  	var cases []reflect.SelectCase
   101  	for i := 0; i < c.Len(); i++ {
   102  		cases = append(cases, reflect.SelectCase{
   103  			Dir:  reflect.SelectRecv,
   104  			Chan: c.Index(i),
   105  		})
   106  	}
   107  	elemType := chanType.Elem()
   108  	out := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, elemType), 0)
   109  	go func() {
   110  		for len(cases) > 0 {
   111  			i, v, ok := reflect.Select(cases)
   112  			if !ok {
   113  				lastIndex := len(cases) - 1
   114  				cases[i], cases[lastIndex] = cases[lastIndex], cases[i]
   115  				cases = cases[:lastIndex]
   116  				continue
   117  			}
   118  			out.Send(v)
   119  		}
   120  		out.Close()
   121  	}()
   122  	return out.Convert(reflect.ChanOf(reflect.RecvDir, elemType)).Interface()
   123  }
   124  
   125  // WaitError waits for either an error on the error channel or the done channel to close
   126  // Returns an error if one is received on the error channel, otherwise it returns nil
   127  //
   128  // This handles a race condition where the done channel could have been closed as a result of an
   129  // irrecoverable error being thrown, so that when the scheduler yields control back to this
   130  // goroutine, both channels are available to read from. If the done case happens to be chosen
   131  // at random to proceed instead of the error case, then we would return without error which could
   132  // result in unsafe continuation.
   133  func WaitError(errChan <-chan error, done <-chan struct{}) error {
   134  	select {
   135  	case err := <-errChan:
   136  		return err
   137  	case <-done:
   138  		select {
   139  		case err := <-errChan:
   140  			return err
   141  		default:
   142  		}
   143  		return nil
   144  	}
   145  }
   146  
   147  // readyDoneAwareMerger is a utility structure which implements module.ReadyDoneAware and module.Startable interfaces
   148  // and is used to merge []T into one T.
   149  type readyDoneAwareMerger struct {
   150  	components []module.ReadyDoneAware
   151  }
   152  
   153  func (m readyDoneAwareMerger) Start(signalerContext irrecoverable.SignalerContext) {
   154  	for _, component := range m.components {
   155  		startable, ok := component.(module.Startable)
   156  		if ok {
   157  			startable.Start(signalerContext)
   158  		}
   159  	}
   160  }
   161  
   162  func (m readyDoneAwareMerger) Ready() <-chan struct{} {
   163  	return AllReady(m.components...)
   164  }
   165  
   166  func (m readyDoneAwareMerger) Done() <-chan struct{} {
   167  	return AllDone(m.components...)
   168  }
   169  
   170  var _ module.ReadyDoneAware = (*readyDoneAwareMerger)(nil)
   171  var _ module.Startable = (*readyDoneAwareMerger)(nil)
   172  
   173  // MergeReadyDone merges []module.ReadyDoneAware into one module.ReadyDoneAware.
   174  func MergeReadyDone(components ...module.ReadyDoneAware) module.ReadyDoneAware {
   175  	return readyDoneAwareMerger{components: components}
   176  }
   177  
   178  // DetypeSlice converts a typed slice containing any kind of elements into an
   179  // untyped []any type, in effect removing the element type information from the slice.
   180  // It is useful for passing data into structpb.NewValue, which accepts []any but not
   181  // []T for any specific type T.
   182  func DetypeSlice[T any](typedSlice []T) []any {
   183  	untypedSlice := make([]any, len(typedSlice))
   184  	for i, t := range typedSlice {
   185  		untypedSlice[i] = t
   186  	}
   187  	return untypedSlice
   188  }
   189  
   190  // SampleN computes a percentage of the given number 'n', and returns the result as an unsigned integer.
   191  // If the calculated sample is greater than the provided 'max' value, it returns the ceil of 'max'.
   192  // If 'n' is less than or equal to 0, it returns 0.
   193  //
   194  // Parameters:
   195  // - n: The input number, used as the base to compute the percentage.
   196  // - max: The maximum value that the computed sample should not exceed.
   197  // - percentage: The percentage (in range 0.0 to 1.0) to be applied to 'n'.
   198  //
   199  // Returns:
   200  // - The computed sample as an unsigned integer, with consideration to the given constraints.
   201  func SampleN(n int, max, percentage float64) uint {
   202  	if n <= 0 {
   203  		return 0
   204  	}
   205  	sample := float64(n) * percentage
   206  	if sample > max {
   207  		sample = max
   208  	}
   209  	return uint(math.Ceil(sample))
   210  }