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 }