github.com/haraldrudell/parl@v0.4.176/if-go.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package parl 7 8 import ( 9 "context" 10 "fmt" 11 "time" 12 13 "github.com/haraldrudell/parl/pruntime" 14 ) 15 16 // Thread interface 17 18 // Go provides the four needs of a running goroutione thread. 19 // The Go is provided as a function argument in the go statement function call 20 // that launches the thread. 21 // - the four needs: 22 // - — to be waited upon via [Go.Done] 23 // - — to submit non-fatal errors via [Go.AddError] 24 // - — to detect and initiate cancel via [Go.Context] [Go.Cancel] 25 // - [Go.Cancel] cancels: 26 // - — this Go thread’s context 27 // - — this Go’s parent thread-group’s context and 28 // - — this Go’s parent thread-group’s subordinate thread-groups’ contexts 29 // - The Go Context is canceled when 30 // - — the parent GoGroup thread-group’s context is Canceled or 31 // - —a thread in the parent GoGroup thread-group initiates Cancel 32 // - Cancel by threads in sub ordinate thread-groups do not Cancel this Go thread 33 // 34 // Usage: 35 // 36 // var threadGroup = g0.NewGoGroup(context.Background()) 37 // go someFunc(threadGroup.Go()) 38 // … 39 // func someFunc(g parl.Go) { 40 // var err error 41 // defer g.Register().Done(&err) 42 // defer parl.RecoverErr(func() parl.DA { return parl.A() }, &err) 43 // … 44 type Go interface { 45 // Register performs no function but allows the Go object to collect 46 // information on the new thread. 47 // - label is an optional name that can be assigned to a Go goroutine thread 48 Register(label ...string) (g0 Go) 49 // AddError emits a non-fatal errors 50 AddError(err error) 51 // Go returns a Go object to be provided as a go-statement function-argument 52 // in a function call invocation launching a new gorotuine thread. 53 // - the new thread belongs to the same GoGroup thread-group as the Go 54 // object whose Go method was invoked. 55 Go() (g0 Go) 56 // SubGo returns a GoGroup thread-group whose fatal and non-fatel errors go to 57 // the Go object’s parent thread-group. 58 // - a SubGo is used to ensure sub-threads exiting prior to their parent thread 59 // or to facilitate separate cancelation of the threads in the subordinate thread-group. 60 // - fatal errors from SubGo threads are handled in the same way as those of the 61 // Go object, typically terminating the application. 62 // - the SubGo thread-group terminates when both its own threads have exited and 63 // - the threads of its subordinate thread-groups. 64 SubGo(onFirstFatal ...GoFatalCallback) (subGo SubGo) 65 // SubGroup returns a thread-group with its own error channel. 66 // - a SubGroup is used for threads whose fatal errors should be handled 67 // in the Go thread. 68 // - The threads of the Subgroup can be canceled separately. 69 // - SubGroup’s error channel collects fatal thread terminations 70 // - the SubGroup’s error channel needs to be read in real-time or after 71 // SubGroup termination 72 // - non-fatal errors in SubGroup threads are sent to the Go object’s parent 73 // similar to the AddError method 74 // - the SubGroup thread-group terminates when both its own threads have exited and 75 // - the threads of its subordinate thread-groups. 76 SubGroup(onFirstFatal ...GoFatalCallback) (subGroup SubGroup) 77 // Done indicates that this goroutine is exiting 78 // - err == nil means successful exit 79 // - non-nil err indicates fatal error 80 // - deferrable 81 Done(errp *error) 82 // Wait awaits exit of this Go thread. 83 Wait() 84 WaitCh() (ch AwaitableCh) 85 // Cancel signals for the threads in this Go thread’s parent GoGroup thread-group 86 // and any subordinate thread-groups to exit. 87 Cancel() 88 // Context will Cancel when the parent thread-group Cancels 89 // or Cancel is invoked on this Go object. 90 // - Subordinate thread-groups do not Cancel the context of the Go thread. 91 Context() (ctx context.Context) 92 // ThreadInfo returns thread data that is partially or fully populated 93 // - ThreadID may be invalid: threadID.IsValid. 94 // - goFunction may be zero-value: goFunction.IsSet 95 // - those values present after public methods of parl.Go has been invoked by 96 // the new goroutine 97 ThreadInfo() (threadData ThreadData) 98 // values always present 99 Creator() (threadID ThreadID, createLocation *pruntime.CodeLocation) 100 // - ThreadID may be invalid: threadID.IsValid. 101 // - goFunction may be zero-value: goFunction.IsSet 102 // - those values present after public methods of parl.Go has been invoked by 103 // the new goroutine 104 GoRoutine() (threadID ThreadID, goFunction *pruntime.CodeLocation) 105 // GoID efficiently returns the goroutine ID that may be invalid 106 // - valid after public methods of parl.Go has been invoked by 107 // the new goroutine 108 GoID() (threadID ThreadID) 109 // EntityID returns a value unique for this Go 110 // - ordered: usable as map key or for sorting 111 // - always valid, has .String method 112 EntityID() (goEntityID GoEntityID) 113 fmt.Stringer 114 } 115 116 // GoFatalCallback receives the thread-group on its first fatal thread-exit 117 // - GoFatalCallback is an optional onFirstFatal argument to 118 // - — NewGoGroup 119 // - — SubGo 120 // - — SubGroup 121 type GoFatalCallback func(goGen GoGen) 122 123 // GoGen allows for new Go threads, new SubGo and SubGroup thread-groups and 124 // cancel of threads in the thread-group and its subordinate thread-groups. 125 // - GoGen is value from NewGoGroup GoGroup SubGo SubGroup Go, 126 // ie. any Go-interface object 127 type GoGen interface { 128 // Go returns a Go object to be provided as a go statement function argument. 129 Go() (g0 Go) 130 // SubGo returns a thread-group whose fatal errors go to GoGen’s parent. 131 // - both non-fatal and fatal errors in SubGo threads are sent to GoGen’s parent 132 // like Go.AddError and Go.Done. 133 // - therefore, when a SubGo thread fails, the application will typically exit. 134 // - by awaiting SubGo, Go can delay its exit until SubGo has terminated 135 // - the SubGo thread-group terminates when the its thread exits 136 SubGo(onFirstFatal ...GoFatalCallback) (subGo SubGo) 137 // SubGroup creates a sub-ordinate thread-group 138 SubGroup(onFirstFatal ...GoFatalCallback) (subGroup SubGroup) 139 // Cancel terminates the threads in the Go consumer thread-group. 140 Cancel() 141 // Context will Cancel when the parent thread-group Cancels. 142 // Subordinate thread-groups do not Cancel this context. 143 Context() (ctx context.Context) 144 } 145 146 // Thread Group interfaces and Factory 147 148 // GoGroup manages a hierarchy of threads 149 // - GoGroup only terminates when: 150 // - — the last thread in its hierarchy exits 151 // - — [GoGroup.EnableTermination] is set to true when 152 // no Go threads exist in the hierarchy 153 // - the GoGroup hierarchy consists of: 154 // - — managed goroutines returned by [GoGroup.Go] 155 // - — a [SubGo] subordinate thread-group hierarchy returned by [GoGroup.SubGo] 156 // that allows for a group of threads to be canceled or waited upon separately 157 // - — a [SubGroup] subordinate thread-group hierarchy returned by [GoGroup.SubGroup] 158 // that allows for a group of threads to exit with fatal errors without 159 // canceling the GoGroup and for those threads to be 160 // canceled or waited upon separately 161 // - — each subordinate Go thread or SubGo or SubGroup subordinate thread-groups 162 // can create additional threads and subordinate thread-groups. 163 // - [GoGroup.Context] returns a context that is the context or parent context 164 // of all the Go threads, SubGo and SubGroup subordinate thread-groups 165 // in its hierarchy 166 // - [GoGroup.Cancel] cancels the GoGroup Context, 167 // thereby signaling to all threads in the GoGroup hierarchy to exit. 168 // This will eventually terminate the GoGroup 169 // - providing a parent context to the GoGroup implementation allows 170 // for terminating the GoGroup via this parent context 171 // - A thread invoking [Go.Cancel] will signal to all threads in its 172 // GoGroup or SubGo or SubGroup thread-groups to exit. 173 // It will also signal to all threads in its subordinate thread-groups to exit. 174 // This will eventually terminate its threadgroup and all that threadgroup’s 175 // subordinate threadgroups. 176 // - Alternatives to [parl.Go] is [parl.NewGoResult] and [parl.NewGoResult2] 177 // that only provides being awaitable to a goroutine 178 // - — 179 // - from this thread-group will terminate all threads in this 180 // and subordinate thread-groups if this thread-group was provided 181 // the FirstFailTerminates option, which is default. 182 // - A fatal thread-termination in a sub thread-group only affects this 183 // thread-group if the sub thread-group was provided a nil fatal function, 184 // the FirstFailTerminates option, which is default, and no explicit 185 // FailChannel option. 186 // - Fatal thread terminations will propagate to parent thread-groups if 187 // this thread group did not have a fatal function provided and was not 188 // explicitly provided the FailChannel option. 189 // - A Cancel in this thread-group or in a parent context cancels threads in 190 // this and all subordinate thread-groups. 191 // - A Cancel in a subordinate thread-group does not affect this thread-group. 192 // - Wait in this thread-group wait for threads in this and all subordinate 193 // thread-groups. 194 type GoGroup interface { 195 // Go returns a Go object to be provided as a go statement function argument. 196 Go() (g0 Go) 197 // SubGo returns athread-group whose fatal errors go to Go’s parent. 198 // - both non-fatal and fatal errors in SubGo threads are sent to Go’s parent 199 // like Go.AddError and Go.Done. 200 // - therefore, when a SubGo thread fails, the application will typically exit. 201 // - by awaiting SubGo, Go can delay its exit until SubGo has terminated 202 // - the SubGo thread-group terminates when the its thread exits 203 SubGo(onFirstFatal ...GoFatalCallback) (subGo SubGo) 204 // SubGroup creates a sub-ordinate GoGroup. 205 // - SubGroup fatal and non-fatal errors are sent to the parent GoGroup. 206 // - SubGroup-context initiated Cancel only affect threads in the SubGroup thread-group 207 // - parent-initiated Cancel terminates SubGroup threads 208 // - SubGroup only awaits SubGroup threads 209 // - parent await also awaits SubGroup threads 210 SubGroup(onFirstFatal ...GoFatalCallback) (subGroup SubGroup) 211 // Ch returns a channel sending the all fatal termination errors when 212 // the FailChannel option is present, or only the first when both 213 // FailChannel and StoreSubsequentFail options are present. 214 Ch() (ch <-chan GoError) 215 // Wait waits for this thread-group to terminate. 216 Wait() 217 // EnableTermination controls temporarily preventing the GoGroup from 218 // terminating. 219 // EnableTermination is initially true. 220 // - invoked with no argument returns the current state of EnableTermination 221 // - invoked with [AllowTermination] again allows for termination and 222 // immediately terminates the thread-group if no threads are currently running. 223 // - invoked with [PreventTermination] allows for the number of managed 224 // threads to be temporarily zero without terminating the thread-group 225 EnableTermination(allowTermination ...bool) (mayTerminate bool) 226 // Cancel terminates the threads in this and subordinate thread-groups. 227 Cancel() 228 // Context will Cancel when the parent context Cancels. 229 // Subordinate thread-groups do not Cancel this context. 230 Context() (ctx context.Context) 231 // the available data for all threads 232 Threads() (threads []ThreadData) 233 // threads that have been named ordered by name 234 NamedThreads() (threads []ThreadData) 235 // SetDebug enables debug logging on this particular instance 236 // - parl.NoDebug 237 // - parl.DebugPrint 238 // - parl.AggregateThread 239 SetDebug(debug GoDebug, log ...PrintfFunc) 240 fmt.Stringer 241 } 242 243 type SubGo interface { 244 // Go returns a [Go] object managing a thread of the GoGroup thread-group 245 // by providing the g value as a go-statement function argument. 246 Go() (g Go) 247 // SubGo returns a thread-group whose fatal errors go to Go’s parent. 248 // - both non-fatal and fatal errors in SubGo threads are sent to Go’s parent 249 // like Go.AddError and Go.Done. 250 // - therefore, when a SubGo thread fails, the application will typically exit. 251 // - by awaiting SubGo, Go can delay its exit until SubGo has terminated 252 // - the SubGo thread-group terminates when the its thread exits 253 SubGo(onFirstFatal ...GoFatalCallback) (subGo SubGo) 254 // SubGroup creates a sub-ordinate GoGroup. 255 // - SubGroup fatal and non-fatal errors are sent to the parent GoGroup. 256 // - SubGroup-context initiated Cancel only affect threads in the SubGroup thread-group 257 // - parent-initiated Cancel terminates SubGroup threads 258 // - SubGroup only awaits SubGroup threads 259 // - parent await also awaits SubGroup threads 260 SubGroup(onFirstFatal ...GoFatalCallback) (subGroup SubGroup) 261 // Wait waits for all threads of this thread-group to terminate. 262 Wait() 263 // returns a channel that closes on subGo end similar to Wait 264 WaitCh() (ch AwaitableCh) 265 // EnableTermination controls temporarily preventing the GoGroup from 266 // terminating. 267 // EnableTermination is initially true. 268 // - invoked with no argument returns the current state of EnableTermination 269 // - invoked with [AllowTermination] again allows for termination and 270 // immediately terminates the thread-group if no threads are currently running. 271 // - invoked with [PreventTermination] allows for the number of managed 272 // threads to be temporarily zero without terminating the thread-group 273 EnableTermination(allowTermination ...bool) (mayTerminate bool) 274 // Cancel terminates the threads in this and subordinate thread-groups. 275 Cancel() 276 // Context will Cancel when the parent context Cancels. 277 // Subordinate thread-groups do not Cancel this context. 278 Context() (ctx context.Context) 279 // the available data for all threads 280 Threads() (threads []ThreadData) 281 // threads that have been named ordered by name 282 NamedThreads() (threads []ThreadData) 283 // SetDebug enables debug logging on this particular instance 284 // - parl.NoDebug 285 // - parl.DebugPrint 286 // - parl.AggregateThread 287 SetDebug(debug GoDebug, log ...PrintfFunc) 288 fmt.Stringer 289 } 290 291 type SubGroup interface { 292 SubGo 293 // Ch returns a receive channel for fatal errors if this SubGo has LocalChannel option. 294 Ch() (ch <-chan GoError) 295 // FirstFatal allows to await or inspect the first thread terminating with error. 296 // it is valid if this SubGo has LocalSubGo or LocalChannel options. 297 // To wait for first fatal error using multiple-semaphore mechanic: 298 // firstFatal := g0.FirstFatal() 299 // for { 300 // select { 301 // case <-firstFatal.Ch(): 302 // … 303 // To inspect first fatal: 304 // if firstFatal.DidOccur() … 305 FirstFatal() (firstFatal *OnceWaiterRO) 306 } 307 308 type GoFactory interface { 309 // NewGo returns a light-weight thread-group. 310 // - GoGroup only receives Cancel from ctx, it does not cancel this context. 311 NewGoGroup(ctx context.Context, onFirstFatal ...GoFatalCallback) (g0 GoGroup) 312 } 313 314 const ( 315 AllowTermination = true 316 PreventTermination = false 317 ) 318 319 // data types 320 321 // GoError is an error or a thread exit associated with a goroutine 322 // - GoError encapsulates the original unadulterated error 323 // - GoError provides context for taking action on the error 324 // - Go provides the thread associated with the error. All GoErrors are associated with 325 // a Go object 326 // - because GoError is both error and fmt.Stringer, to print its string representation 327 // requires using the String() method, otherwise fmt.Printf defaults to the Error() 328 // method 329 type GoError interface { 330 error // Error() string 331 // Err retrieves the original error value 332 Err() (err error) 333 // ErrString returns string representation of error 334 // - if no error “OK” 335 // - if not debug or panic, short error with location 336 // - otherwise error with stack trace 337 ErrString() (errString string) 338 // Time provides when this error occurred 339 Time() time.Time 340 // IsThreadExit determines if this error is a thread exit, ie. GeExit GePreDoneExit 341 // - thread exits may have err nil 342 // - fatals are non-nil thread exits that may require specific actions such as 343 // application termination 344 IsThreadExit() (isThreadExit bool) 345 // IsFatal determines if this error is a fatal thread-exit, ie. a thread exiting with non-nil error 346 IsFatal() (isThreadExit bool) 347 // ErrContext returns in what situation this error occurred 348 ErrContext() (errContext GoErrorContext) 349 // Go provides the thread and goroutine emitting this error 350 Go() (g0 Go) 351 fmt.Stringer 352 } 353 354 // ThreadData is information about a Go object thread. 355 // - initially, only Create is present 356 // - Name is only present for threads that have been named 357 type ThreadData interface { 358 // threadID is the ID of the running thread assigned by the go runtime 359 // - IsValid method checks if value is present 360 // - zero value is empty string 361 // - .ThreadID().String(): "5" 362 ThreadID() (threadID ThreadID) 363 // createLocation is the code line of the go statement function-call 364 // creating the goroutine thread 365 // - IsSet method checks if value is present 366 // - Create().Short(): "g0.(*SomeType).SomeCode()-thread-data_test.go:73" 367 Create() (createLocation *pruntime.CodeLocation) 368 // Func returns the code line of the function of the running thread. 369 // - IsSet method checks if value is present 370 // - .Func().Short(): "g0.(*SomeType).SomeFunction()-thread-data_test.go:80" 371 Func() (funcLocation *pruntime.CodeLocation) 372 // optional thread-name assigned by consumer 373 // - zero-value: empty string "" for threads that have not been named 374 Name() (label string) 375 // Short returns a short description of the thread "label:threadID" or fmt.Stringer 376 // - "myThreadName:4" 377 // - zero-value: "[empty]" ThreadDataEmpty 378 // - nil value: "threadData:nil" ThreadDataNil 379 Short() (short string) 380 // all non-empty fields: [label]:[threadID]_func:[Func]_cre:[Create] 381 // - "myThreadName:5_func:g0.(*SomeType).SomeFunction()-thread-data_test.go:80_cre:g0.(*SomeType).SomeCode()-thread-data_test.go:73" 382 // - zero-value: "[empty]" ThreadDataEmpty 383 fmt.Stringer 384 } 385 386 const ( 387 // GeNonFatal indicates a non-fatal error ocurring during processing. 388 // err is non-nil 389 GeNonFatal GoErrorContext = iota + 1 390 // GePreDoneExit indicates an exit value of a subordinate goroutine, 391 // other than the final exit of the last running goroutine. 392 // err may be nil 393 GePreDoneExit 394 // A SubGroup with its own error channel is sending a 395 // locally fatal error not intended to terminate the app 396 GeLocalChan 397 // A thread is requesting app termination without a fatal error. 398 // - this could be a callback 399 GeTerminate 400 // GeExit indicates exit of the last goroutine. 401 // err may be nil. 402 // The error channel may close after GeExit. 403 GeExit 404 ) 405 406 const ( 407 NoDebug GoDebug = iota 408 DebugPrint 409 AggregateThread 410 ) 411 412 type GoDebug uint8