github.com/haraldrudell/parl@v0.4.176/g0/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 g0 provides Go threads and thread-groups 7 package g0 8 9 import ( 10 "github.com/haraldrudell/parl" 11 "github.com/haraldrudell/parl/goid" 12 "github.com/haraldrudell/parl/pdebug" 13 "github.com/haraldrudell/parl/perrors" 14 "github.com/haraldrudell/parl/pruntime" 15 ) 16 17 const ( 18 // counts public method: Go.Register/Go/SubGo/SubGroup/AddError/Done 19 // and [Go.checkState] 20 grCheckThreadFrames = 2 21 ) 22 23 // Go supports a goroutine executing part of a thread-group. Thread-safe. 24 // - Register allows to name the thread and collects information on the new thread 25 // - AddError processes non-fatal errors 26 // - Done handles thread termination and fatal errors and is deferrable. 27 // The Go thread terminates on Done invocation. 28 // - Go creates a sibling thread object to be provided in a go statement 29 // - SubGo creates a subordinate thread-group managed by the parent thread group. 30 // The SubGo can be independently terminated or terminated prior to thread exit. 31 // - SubGroup creates a subordinate thread-group with its own error channel. 32 // Fatal-error thread-exits in SubGroup can be recovered locally in that thread-group 33 type Go struct { 34 // EntityID(), uniquely identifies Go object 35 goEntityID 36 // Cancel() Context() 37 goParent 38 // the thread ID of the goroutine creating this thread 39 creatorThreadId parl.ThreadID 40 // this thread’s Thread ID, creator location, go-function and 41 // possible printable thread-name 42 thread *ThreadSafeThreadData 43 // [parl.AwaitableCh] that closes when this Go ends 44 endCh parl.Awaitable 45 } 46 47 // newGo returns a Go object providing functions to a thread operating in a 48 // Go thread-group. Thread-safe 49 // - parent is a GoGroup type configured as GoGroup, SubGo or SubGroup 50 // - goInvocation is the invoker of Go, ie. the parent thread 51 // - returns Go entity ID and thread data since the parent will 52 // immediately need those 53 func newGo(parent goParent, goInvocation *pruntime.CodeLocation) ( 54 g0 parl.Go, 55 goEntityID parl.GoEntityID, 56 threadData *ThreadData) { 57 if parent == nil { 58 panic(perrors.NewPF("parent cannot be nil")) 59 } 60 g := Go{ 61 goEntityID: *newGoEntityID(), 62 goParent: parent, 63 creatorThreadId: goid.GoID(), 64 thread: NewThreadSafeThreadData(), 65 } 66 g.thread.SetCreator(goInvocation) 67 68 // return values 69 g0 = &g 70 goEntityID = g.EntityID() 71 threadData = g.thread.Get() 72 73 return 74 } 75 76 func (g *Go) Register(label ...string) (g00 parl.Go) { return g.ensureThreadData(label...) } 77 func (g *Go) Go() (g00 parl.Go) { return g.ensureThreadData().goParent.FromGoGo() } 78 func (g *Go) SubGo(onFirstFatal ...parl.GoFatalCallback) (subGo parl.SubGo) { 79 return g.ensureThreadData().goParent.FromGoSubGo(onFirstFatal...) 80 } 81 func (g *Go) SubGroup(onFirstFatal ...parl.GoFatalCallback) (subGroup parl.SubGroup) { 82 return g.ensureThreadData().goParent.FromGoSubGroup(onFirstFatal...) 83 } 84 85 // AddError emits a non-fatal errors 86 func (g *Go) AddError(err error) { 87 g.ensureThreadData() 88 89 if err == nil { 90 return // nil error return 91 } 92 93 g.ConsumeError(NewGoError(perrors.Stack(err), parl.GeNonFatal, g)) 94 } 95 96 // Done handles thread exit. Deferrable 97 // - *errp contains possible fatalk thread error 98 // - errp can be nil 99 func (g *Go) Done(errp *error) { 100 if !g.ensureThreadData().endCh.Close() { 101 panic(perrors.ErrorfPF("Go received multiple Done: “%s”", perrors.ErrpString(errp))) 102 } 103 104 // obtain fatal error and ensure it has stack 105 var err error 106 if errp != nil { 107 err = perrors.Stack(*errp) 108 } 109 110 // notify parent of exit 111 g.goParent.GoDone(g, err) 112 } 113 114 func (g *Go) ThreadInfo() (threadData parl.ThreadData) { return g.thread.Get() } 115 func (g *Go) GoID() (threadID parl.ThreadID) { return g.thread.ThreadID() } 116 func (g *Go) Creator() (threadID parl.ThreadID, createLocation *pruntime.CodeLocation) { 117 threadID = g.creatorThreadId 118 var threadData = g.thread.Get() 119 createLocation = &threadData.createLocation 120 return 121 } 122 func (g *Go) GoRoutine() (threadID parl.ThreadID, goFunction *pruntime.CodeLocation) { 123 var threadData = g.thread.Get() 124 threadID = threadData.threadID 125 goFunction = &threadData.funcLocation 126 return 127 } 128 func (g *Go) Wait() { <-g.endCh.Ch() } 129 func (g *Go) WaitCh() (ch parl.AwaitableCh) { return g.endCh.Ch() } 130 131 // ensureThreadData is invoked by Go’s public methods ensuring that 132 // the thread’s information is collected 133 // - label is an optional printable thread-name 134 // - ensureThreadData supports functional chaining 135 func (g *Go) ensureThreadData(label ...string) (g1 *Go) { 136 g1 = g 137 138 // if thread-data has already been collected, do nothing 139 if g.thread.HaveThreadID() { 140 return // already have thread-data return 141 } 142 143 // optional printable thread name 144 var label0 string 145 if len(label) > 0 { 146 label0 = label[0] 147 } 148 149 // get stack that contains thread ID, go function, go-function invoker 150 // for the new thread 151 var stack = pdebug.NewStack(grCheckThreadFrames) 152 if stack.IsMain() { 153 return // this should not happen, called by Main 154 } 155 // creator has already been set 156 g.thread.Update(stack.ID(), nil, stack.GoFunction(), label0) 157 158 // propagate thread information to parent 159 g.UpdateThread(g.EntityID(), g.thread.Get()) 160 161 return 162 } 163 164 // g1ID:4:g0.(*g1WaitGroup).Go-g1-thread-group.go:63 165 func (g *Go) String() (s string) { 166 td := g.thread.Get() 167 return parl.Sprintf("go:%s:%s", td.threadID, td.createLocation.Short()) 168 }