| // 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 | 
 | } |