github.com/haraldrudell/parl@v0.4.176/g0/go-context.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
     7  
     8  import (
     9  	"context"
    10  	"sync/atomic"
    11  
    12  	"github.com/haraldrudell/parl"
    13  	"github.com/haraldrudell/parl/pdebug"
    14  )
    15  
    16  const (
    17  	g1ccSkipFrames = 1
    18  	g1ccPrepend    = "— "
    19  )
    20  
    21  // goContext is a promotable private field
    22  //   - public methods: Cancel() Context() EntityID()
    23  //   - goContext is based on parl.NewCancelContext
    24  type goContext struct {
    25  	goEntityID // EntityID()
    26  	wg         parl.WaitGroup
    27  	//	- updatable, therefore must be atomic access
    28  	ctxp atomic.Pointer[context.Context]
    29  	// cancelListener is function invoked immediately prior to
    30  	// parl.InvokeCancel
    31  	cancelListener atomic.Pointer[func()]
    32  }
    33  
    34  // newGoContext returns a subordinate context with Cancel and Context methods
    35  //   - uses non-pointer atomics
    36  func newGoContext(fieldp *goContext, ctx context.Context) (g *goContext) {
    37  	if ctx == nil {
    38  		panic(parl.NilError("ctx"))
    39  	}
    40  	if fieldp != nil {
    41  		g = fieldp
    42  		*g = goContext{goEntityID: *newGoEntityID()}
    43  	} else {
    44  		g = &goContext{goEntityID: *newGoEntityID()}
    45  	}
    46  	var ctx2 = parl.NewCancelContext(ctx)
    47  	g.ctxp.Store(&ctx2)
    48  	return
    49  }
    50  
    51  // Cancel signals shutdown to all threads of a thread-group.
    52  func (c *goContext) Cancel() {
    53  	if f := c.cancelListener.Load(); f != nil {
    54  		(*f)()
    55  	}
    56  	// if caller is debug, debug-print cancel action
    57  	if parl.IsThisDebugN(g1ccSkipFrames) {
    58  		parl.GetDebug(g1ccSkipFrames)("CancelAndContext.Cancel:\n" + pdebug.NewStack(g1ccSkipFrames).Shorts(g1ccPrepend))
    59  	}
    60  	parl.InvokeCancel(*c.ctxp.Load())
    61  }
    62  
    63  // Context returns the context of this cancelAndContext.
    64  //   - Context is used to detect cancel using the receive channel Context.Done.
    65  //   - Context cancellation has happened when Context.Err is non-nil.
    66  func (c *goContext) Context() (ctx context.Context) {
    67  	return *c.ctxp.Load()
    68  }
    69  
    70  // addNotifier adds a stack trace to every Cancel invocation
    71  //
    72  // Usage:
    73  //
    74  //	threadGroup := g0.NewGoGroup(ctx)
    75  //	threadGroup.(*g0.GoGroup).addNotifier(func(slice pruntime.StackSlice) {
    76  //	  parl.D("CANCEL %s %s\n\n\n\n\n", g0.GoChain(threadGroup), slice)
    77  //	})
    78  // func (c *goContext) addNotifier(notifier func(slice pruntime.StackSlice)) {
    79  // 	for {
    80  // 		var ctxp0 = c.ctxp.Load()
    81  // 		var ctx = parl.AddNotifier1(*ctxp0, notifier)
    82  // 		if c.ctxp.CompareAndSwap(ctxp0, &ctx) {
    83  // 			return
    84  // 		}
    85  // 	}
    86  // }
    87  
    88  func (c *goContext) setCancelListener(f func()) {
    89  	c.cancelListener.Store(&f)
    90  }