// Package onecontext provides a mechanism to merge multiple existing contexts.
package onecontext import ( ) // ErrCanceled is the returned when the CancelFunc returned by Merge is called. var ErrCanceled = errors.New("context canceled") // OneContext is the struct holding the context grouping logic. type OneContext struct { ctx context.Context ctxs []context.Context done chan struct{} err error errMutex sync.RWMutex cancelFunc context.CancelFunc cancelCtx context.Context } // Merge allows to merge multiple contexts. // It returns the merged context and a CancelFunc to cancel it. func ( context.Context, ...context.Context) (context.Context, context.CancelFunc) { , := context.WithCancel(context.Background()) := &OneContext{ done: make(chan struct{}), ctx: , ctxs: , cancelCtx: , cancelFunc: , } go .run() return , } // Deadline returns the minimum deadline among all the contexts. func ( *OneContext) () (time.Time, bool) { := time.Time{} if , := .ctx.Deadline(); { = } for , := range .ctxs { if , := .Deadline(); { if .IsZero() || .Before() { = } } } return , !.IsZero() } // Done returns a channel for cancellation. func ( *OneContext) () <-chan struct{} { return .done } // Err returns the first error raised by the contexts, otherwise a nil error. func ( *OneContext) () error { .errMutex.RLock() defer .errMutex.RUnlock() return .err } // Value returns the value associated with the key from one of the contexts. func ( *OneContext) ( interface{}) interface{} { if := .ctx.Value(); != nil { return } for , := range .ctxs { if := .Value(); != nil { return } } return nil } func ( *OneContext) () { if len(.ctxs) == 1 { .runTwoContexts(.ctx, .ctxs[0]) return } .runMultipleContexts() } func ( *OneContext) (, context.Context) { select { case <-.cancelCtx.Done(): .cancel(ErrCanceled) case <-.Done(): .cancel(.ctx.Err()) case <-.Done(): .cancel(.ctxs[0].Err()) } } func ( *OneContext) () { := make([]reflect.SelectCase, len(.ctxs)+2) [0] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(.cancelCtx.Done())} [1] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(.ctx.Done())} for , := range .ctxs { [+2] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(.Done())} } , , := reflect.Select() switch { case 0: .cancel(ErrCanceled) case 1: .cancel(.ctx.Err()) default: .cancel(.ctxs[-2].Err()) } } func ( *OneContext) ( error) { .errMutex.Lock() .err = .errMutex.Unlock() close(.done) .cancelFunc() }