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  }