|  | // package combinectx implements context.Contexts that 'combine' two other | 
|  | // 'parent' contexts. These can be used to deal with cases where you want to | 
|  | // cancel a method call whenever any of two pre-existing contexts expires first. | 
|  | // | 
|  | // For example, if you want to tie a method call to some incoming request | 
|  | // context and an active leader lease, then this library is for you. | 
|  | package combinectx | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "sync" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | // Combine 'joins' two existing 'parent' contexts into a single context. This | 
|  | // context will be Done() whenever any of the parent context is Done(). | 
|  | // Combining contexts spawns a goroutine that will be cleaned when any of the | 
|  | // parent contexts is Done(). | 
|  | func Combine(a, b context.Context) context.Context { | 
|  | c := &Combined{ | 
|  | a:     a, | 
|  | b:     b, | 
|  | doneC: make(chan struct{}), | 
|  | } | 
|  | go c.run() | 
|  | return c | 
|  | } | 
|  |  | 
|  | type Combined struct { | 
|  | // a is the first parent context. | 
|  | a context.Context | 
|  | // b is the second parent context. | 
|  | b context.Context | 
|  |  | 
|  | // mu guards done. | 
|  | mu sync.Mutex | 
|  | // done is an Error if either parent context is Done(), or nil otherwise. | 
|  | done *Error | 
|  | // doneC is closed when either parent context is Done() and Error is set. | 
|  | doneC chan struct{} | 
|  | } | 
|  |  | 
|  | // Error wraps errors returned by parent contexts. | 
|  | type Error struct { | 
|  | // underlyingA points to an error returned by the first parent context if the | 
|  | // combined context was Done() as a result of the first parent context being | 
|  | // Done(). | 
|  | underlyingA *error | 
|  | // underlyingB points to an error returned by the second parent context if the | 
|  | // combined context was Done() as a result of the second parent context being | 
|  | // Done(). | 
|  | underlyingB *error | 
|  | } | 
|  |  | 
|  | func (e *Error) Error() string { | 
|  | if e.underlyingA != nil { | 
|  | return (*e.underlyingA).Error() | 
|  | } | 
|  | if e.underlyingB != nil { | 
|  | return (*e.underlyingB).Error() | 
|  | } | 
|  | return "" | 
|  | } | 
|  |  | 
|  | // First returns true if the Combined context's first parent was Done(). | 
|  | func (e *Error) First() bool { | 
|  | return e.underlyingA != nil | 
|  | } | 
|  |  | 
|  | // Second returns true if the Combined context's second parent was Done(). | 
|  | func (e *Error) Second() bool { | 
|  | return e.underlyingB != nil | 
|  | } | 
|  |  | 
|  | // Unwrap returns the underlying error of either parent context that is Done(). | 
|  | func (e *Error) Unwrap() error { | 
|  | if e.underlyingA != nil { | 
|  | return *e.underlyingA | 
|  | } | 
|  | if e.underlyingB != nil { | 
|  | return *e.underlyingB | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // Is allows errors.Is to be true against any *Error. | 
|  | func (e *Error) Is(target error) bool { | 
|  | if _, ok := target.(*Error); ok { | 
|  | return true | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | // As allows errors.As to be true against any *Error. | 
|  | func (e *Error) As(target interface{}) bool { | 
|  | if v, ok := target.(**Error); ok { | 
|  | *v = e | 
|  | return true | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | // run is the main logic that ties together the two parent contexts. It exits | 
|  | // when either parent context is canceled. | 
|  | func (c *Combined) run() { | 
|  | mark := func(first bool, err error) { | 
|  | c.mu.Lock() | 
|  | defer c.mu.Unlock() | 
|  | c.done = &Error{} | 
|  | if first { | 
|  | c.done.underlyingA = &err | 
|  | } else { | 
|  | c.done.underlyingB = &err | 
|  | } | 
|  | close(c.doneC) | 
|  | } | 
|  | select { | 
|  | case <-c.a.Done(): | 
|  | mark(true, c.a.Err()) | 
|  | case <-c.b.Done(): | 
|  | mark(false, c.b.Err()) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Deadline returns the earlier Deadline from the two parent contexts, if any. | 
|  | func (c *Combined) Deadline() (deadline time.Time, ok bool) { | 
|  | d1, ok1 := c.a.Deadline() | 
|  | d2, ok2 := c.b.Deadline() | 
|  |  | 
|  | if ok1 && !ok2 { | 
|  | return d1, true | 
|  | } | 
|  | if ok2 && !ok1 { | 
|  | return d2, true | 
|  | } | 
|  | if !ok1 && !ok2 { | 
|  | return time.Time{}, false | 
|  | } | 
|  |  | 
|  | if d1.Before(d2) { | 
|  | return d1, true | 
|  | } | 
|  | return d2, true | 
|  | } | 
|  |  | 
|  | func (c *Combined) Done() <-chan struct{} { | 
|  | return c.doneC | 
|  | } | 
|  |  | 
|  | // Err returns nil if neither parent context is Done() yet, or an error otherwise. | 
|  | // The returned errors will have the following properties: | 
|  | //   1) errors.Is(err, Error{}) will always return true. | 
|  | //   2) errors.Is(err, ctx.Err()) will return true if the combined context was | 
|  | //      canceled with the same error as ctx.Err(). | 
|  | //      However, this does NOT mean that the combined context was Done() because | 
|  | //      of the ctx being Done() - to ensure this is the case, use errors.As() to | 
|  | //      retrieve an Error and its First()/Second() methods. | 
|  | //   3) errors.Is(err, context.{Canceled,DeadlineExceeded}) will return true if | 
|  | //      the combined context is Canceled or DeadlineExceeded. | 
|  | //   4) errors.Is will return false otherwise. | 
|  | //   5) errors.As(err, &&Error{})) will always return true. The Error object can | 
|  | //      then be used to check the cause of the combined context's error. | 
|  | func (c *Combined) Err() error { | 
|  | c.mu.Lock() | 
|  | defer c.mu.Unlock() | 
|  | if c.done == nil { | 
|  | return nil | 
|  | } | 
|  | return c.done | 
|  | } | 
|  |  | 
|  | // Value returns the value located under the given key by checking the first and | 
|  | // second parent context in order. | 
|  | func (c *Combined) Value(key interface{}) interface{} { | 
|  | if v := c.a.Value(key); v != nil { | 
|  | return v | 
|  | } | 
|  | if v := c.b.Value(key); v != nil { | 
|  | return v | 
|  | } | 
|  | return nil | 
|  | } |