package lifecycle import ( "context" "errors" "reflect" ) var ( ErrInvalidGroup = errors.New("invalid lifecycle group") ErrRunnerStopped = errors.New("lifecycle runner stopped") ) type Runner interface { Run(context.Context) error } type Group struct { runners []Runner } func NewGroup(runners ...Runner) (*Group, error) { if len(runners) == 0 { return nil, ErrInvalidGroup } owned := make([]Runner, len(runners)) for index, runner := range runners { if isNilRunner(runner) { return nil, ErrInvalidGroup } owned[index] = runner } return &Group{runners: owned}, nil } func (group *Group) Run(ctx context.Context) error { if group == nil || ctx == nil || len(group.runners) == 0 { return ErrInvalidGroup } groupCtx, cancel := context.WithCancel(ctx) defer cancel() done := make(chan error, len(group.runners)) for _, runner := range group.runners { go func() { done <- runner.Run(groupCtx) }() } first := <-done cancel() for range len(group.runners) - 1 { <-done } if ctx.Err() != nil { return ctx.Err() } if first == nil { return ErrRunnerStopped } return first } func isNilRunner(runner Runner) bool { if runner == nil { return true } reflected := reflect.ValueOf(runner) switch reflected.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: return reflected.IsNil() default: return false } }