Documentation ¶
Overview ¶
Package detach provides utility function for getting new contexts that are detached from their parent's timeouts and cancel calls but inheriting any values registered with the library.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Async ¶
Async wraps run in the Async Hooks registered with the library, creates a new context, and calls the wrapped run function with that context. The wrappers may attach values from the parent context to the new context and perform any cleanup tasks after run completes.
Async should be used when you want to run an async task as a part of another all, but need to keep some values from the parent context.
Example ¶
package main import ( "context" "fmt" "sync" "time" "github.com/reddit/baseplate.go/detach" ) // In a real package, you should register hooks for both inline and async // calls at the same time. // // It should likely be done within an `init()` function rather than calling the // function explicitly. func InitAsync() { detach.Register(detach.Hooks{ Inline: nil, Async: func(dst, src context.Context, next func(ctx context.Context)) { if v, ok := src.Value(asyncContextKey).(*asyncContextVal); ok && v != nil { newVal := &asyncContextVal{val: v.val} dst = context.WithValue(dst, asyncContextKey, newVal) defer func() { newVal.close() }() } next(dst) }, }) } type asyncContextKeyType struct{} var asyncContextKey asyncContextKeyType type asyncContextVal struct { mu sync.Mutex val int closed bool } func (v *asyncContextVal) close() { v.mu.Lock() defer v.mu.Unlock() v.closed = true } func main() { InitAsync() const parentTimeout = time.Millisecond // asyncDone is used in this example to let us wait until the async call // finishes, so we can test the output. In real code you probably wouldn't do // this. asyncDone := sync.WaitGroup{} asyncDone.Add(1) run := func() { v := &asyncContextVal{val: 1} defer v.close() ctx, cancel := context.WithTimeout(context.Background(), parentTimeout) defer cancel() ctx = context.WithValue(ctx, asyncContextKey, v) ch := make(chan bool, 1) go func() { time.Sleep(5 * parentTimeout) ch <- true }() select { case <-ch: return case <-ctx.Done(): } fmt.Println("timed out") go detach.Async(ctx, func(detachedCtx context.Context) { detachedCtx, detachedCancel := context.WithTimeout(detachedCtx, 100*parentTimeout) defer detachedCancel() fmt.Printf("parent.Err() == %v\n", ctx.Err()) fmt.Printf("detached.Err() == %v\n", detachedCtx.Err()) detachedV, _ := detachedCtx.Value(asyncContextKey).(*asyncContextVal) fmt.Printf("value equal: %v\n", v == detachedV) fmt.Printf("value.val equal: %v\n", v.val == detachedV.val) ch := make(chan bool, 1) go func() { time.Sleep(parentTimeout) ch <- true }() select { case <-detachedCtx.Done(): fmt.Println("never happens") case <-ch: } asyncDone.Done() }) } fmt.Println("running...") run() fmt.Println("waiting for async cleanup") asyncDone.Wait() fmt.Println("finished cleanup") }
Output: running... timed out waiting for async cleanup parent.Err() == context deadline exceeded detached.Err() == <nil> value equal: false value.val equal: true finished cleanup
func Inline ¶
Inline returns a new context who inherits the values attached using the Inline Hooks registered with the library from the parent but replaces the parent's timeout/cancel with the timeout given and the returned cancel func.
Inline should be used when you need to run something within the current call, rather than in a new goroutine, but need to ignore any timeouts/cancellations from the parent context, such as cleaning up after a call that might have been timed out or canceled.
Example ¶
package main import ( "context" "fmt" "time" "github.com/reddit/baseplate.go/detach" ) // In a real package, you should register hooks for both inline and async // calls at the same time. // // It should likely be done within an `init()` function rather than calling the // function explicitly. func InitInline() { detach.Register(detach.Hooks{ Inline: func(dst, src context.Context) context.Context { if v, ok := src.Value(inlineContextKey).(*inlineContextVal); ok && v != nil { dst = context.WithValue(dst, inlineContextKey, v) } return dst }, Async: nil, }) } type inlineContextKeyType struct{} var inlineContextKey inlineContextKeyType type inlineContextVal struct { val int closed bool } func (v *inlineContextVal) close() { v.closed = true } func main() { InitInline() const parentTimeout = time.Millisecond v := &inlineContextVal{val: 1} defer v.close() ctx, cancel := context.WithTimeout(context.Background(), parentTimeout) defer cancel() ctx = context.WithValue(ctx, inlineContextKey, v) defer func() { detachedCtx, detachedCancel := detach.Inline(ctx, 100*parentTimeout) defer detachedCancel() fmt.Printf("parent.Err() == %v\n", ctx.Err()) fmt.Printf("detached.Err() == %v\n", detachedCtx.Err()) detachedV, _ := detachedCtx.Value(inlineContextKey).(*inlineContextVal) fmt.Printf("value equal: %v\n", v == detachedV) fmt.Printf("value closed: %v\n", detachedV.closed) ch := make(chan bool, 1) go func() { time.Sleep(parentTimeout) ch <- true }() select { case <-detachedCtx.Done(): fmt.Println("never happens") case <-ch: fmt.Println("finished cleanup") } }() ch := make(chan bool, 1) go func() { time.Sleep(100 * parentTimeout) ch <- true }() select { case <-ctx.Done(): return case <-ch: fmt.Println("never happens") } }
Output: parent.Err() == context deadline exceeded detached.Err() == <nil> value equal: true value closed: false finished cleanup
Types ¶
type Hooks ¶
type Hooks struct { // Inline functions are used to attach values from the src context to the dst // context in calls to Inline. // // Inline functions should assume that the calling function will wait for the // any calls after Inline to return before it returns, so they may just // copy values from src to dst. Inline func(dst, src context.Context) context.Context // Async functions wrap the run methods passed to Async. They should derive // values from the src context to add to the dst context and do any // setup/cleanup around those values. // // Async functions should assume that the calling function could return before // they finish, so if context values on src could be closed or otherwise // cleaned up, the wrapper should likely create new values and clean those up // rather than just copying the values from src to dst. Async func(dst, src context.Context, next func(ctx context.Context)) }
Hooks are functions that are called with specific detach helpers to attach values from the parent context to the new context. In order to be registered, at least one function in Hooks must be non-nil. Nil functions will be skipped.