Source File
debounce.go
Belonging Package
go.pact.im/x/flaky
package flakyimport ()// ErrDebounced is an error that DebounceExecutor returns when an Execute call// is superseded by another operation.var ErrDebounced = errors.New("flaky: debounced")// DebounceExecutor is an executor that debounces an operation. That is, it// performs an operation once it stops being called for the specified debounce// duration.//// Keep in mind that using DebounceExecutor introduces latency to the operation// for at least the specified debounce duration. In most cases it should be used// when the operation is expensive to execute or under high event rate or load.// As a side effect, DebounceExecutor guarantees that at most one operation is// executing at a time.type DebounceExecutor struct {once sync.OncedebounceStatec *clock.Clockw time.Durationtimer clock.Timer}// debounceState is the debounce state for DebounceExecutor.type debounceState struct {lock chan struct{}exec chan struct{}steal chan struct{}next chan struct{}}// Debounce returns a new DebounceExecutor instance for the given wait duration.func ( time.Duration) *DebounceExecutor {return &DebounceExecutor{w: ,}}// clone returns a copy of the executor.func ( *DebounceExecutor) () *DebounceExecutor {.init()return &DebounceExecutor{debounceState: .debounceState,c: .c,w: .w,timer: .timer,}}// WithClock returns a copy of the executor that uses the given clock.func ( *DebounceExecutor) ( *clock.Clock) *DebounceExecutor {= .clone().timer = nil.c =.init()return}// WithWait returns a copy of the executor that uses the given wait duration.//// It shares the underlying state and can be used to debounce operation with// non-default wait duration.func ( *DebounceExecutor) ( time.Duration) *DebounceExecutor {= .clone().w =.init()return}// Execute calls the function f if and only if Execute is not called again// during the debounce interval. Context expiration cancels an operation. It// returns ErrDebounced error if the given operation f was superseded by another// Execute call. If Execute is called concurrently, the last invocation wins.// When another operation is already executing, it returns ErrDebounced.//// Callers should handle ErrDebounced error to avoid breaking assumptions when// running under another DebounceExecutor.//// As a side effect, it guarantees that at most one f is executing at a time.// That is, it is safe to avoid locking if the state is mutated exclusively// under the DebounceExecutor.func ( *DebounceExecutor) ( context.Context, Op) error {.init()// Acquire a lock or steal it from an ongoing debounced Execute call.select {case <-.Done():return .Err()case := <-.exec:.exec <-return ErrDebouncedcase .lock <- struct{}{}:case .steal <- struct{}{}:// Wait until the Execute call we are stealing from stops the// timer and passes the lock to us.<-.next}// Reset timer for the debounce duration.if .timer == nil {.timer = .c.Timer(.w)} else {.timer.Reset(.w)}// Wait until the debounce timer expiration, another Execute invocation// that steals our lock, or the action cancellation.var boolselect {case <-.timer.C():.exec <- struct{}{}:= ()<-.exec<-.lockreturn unwrapInternal()case <-.steal:= truecase <-.Done():}if !.timer.Stop() {<-.timer.C()}if {.next <- struct{}{}return ErrDebounced}<-.lockreturn .Err()}// init initializes the internal debouncer state.func ( *DebounceExecutor) () {.once.Do(func() {if .c == nil {.c = clock.System()}if .lock == nil {.lock = make(chan struct{}, 1)}if .exec == nil {.exec = make(chan struct{}, 1)}if .steal == nil {.steal = make(chan struct{})}if .next == nil {.next = make(chan struct{})}})}
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64)