package cmd import ( "context" "golang.org/x/sync/singleflight" ) // A NamedAction describes an Action that also has a unique identifier. This // interface is used by the Debounce Executor to prevent duplicate actions from // running concurrently. type NamedAction interface { Action // ID returns the name for this Action. Identical actions // should return the same ID value. ID() string } type namedAction struct { ActionFunc name string } func (a namedAction) ID() string { return a.name } // Named creates a NamedAction from fn, with n as its name. This function is // just a helper to simplify creating NamedActions. func Named(n string, fn ActionFunc) NamedAction { return namedAction{ ActionFunc: fn, name: n, } } // Debounce wraps e, preventing duplicate NamedActions from running // concurrently, even from concurrent calls to Execute. func Debounce(e Executor) Executor { return debouncer{ ex: e, sf: new(singleflight.Group), } } type debouncer struct { ex Executor sf *singleflight.Group } func (d debouncer) Execute(ctx context.Context, actions []Action) error { wrapped := make([]Action, len(actions)) for i, a := range actions { if na, ok := a.(NamedAction); ok { wrapped[i] = debouncedAction{ NamedAction: na, sf: d.sf, } } else { wrapped[i] = actions[i] } } return d.ex.Execute(ctx, wrapped) } type debouncedAction struct { NamedAction sf *singleflight.Group } func (da debouncedAction) Execute(ctx context.Context) error { fn := func() (interface{}, error) { return nil, da.NamedAction.Execute(ctx) } _, err, _ := da.sf.Do(da.ID(), fn) return err }