| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package lsp |
| |
| import ( |
| "sync" |
| "time" |
| ) |
| |
| type debounceFunc struct { |
| order uint64 |
| done chan struct{} |
| } |
| |
| type debouncer struct { |
| mu sync.Mutex |
| funcs map[string]*debounceFunc |
| } |
| |
| func newDebouncer() *debouncer { |
| return &debouncer{ |
| funcs: make(map[string]*debounceFunc), |
| } |
| } |
| |
| // debounce waits timeout before running f, if no subsequent call is made with |
| // the same key in the intervening time. If a later call to debounce with the |
| // same key occurs while the original call is blocking, the original call will |
| // return immediately without running its f. |
| // |
| // If order is specified, it will be used to order calls logically, so calls |
| // with lesser order will not cancel calls with greater order. |
| func (d *debouncer) debounce(key string, order uint64, timeout time.Duration, f func()) { |
| if timeout == 0 { |
| // Degenerate case: no debouncing. |
| f() |
| return |
| } |
| |
| // First, atomically acquire the current func, cancel it, and insert this |
| // call into d.funcs. |
| d.mu.Lock() |
| current, ok := d.funcs[key] |
| if ok && current.order > order { |
| // If we have a logical ordering of events (as is the case for snapshots), |
| // don't overwrite a later event with an earlier event. |
| d.mu.Unlock() |
| return |
| } |
| if ok { |
| close(current.done) |
| } |
| done := make(chan struct{}) |
| next := &debounceFunc{ |
| order: order, |
| done: done, |
| } |
| d.funcs[key] = next |
| d.mu.Unlock() |
| |
| // Next, wait to be cancelled or for our wait to expire. There is a race here |
| // that we must handle: our timer could expire while another goroutine holds |
| // d.mu. |
| select { |
| case <-done: |
| case <-time.After(timeout): |
| d.mu.Lock() |
| if d.funcs[key] != next { |
| // We lost the race: another event has arrived for the key and started |
| // waiting. We could reasonably choose to run f at this point, but doing |
| // nothing is simpler. |
| d.mu.Unlock() |
| return |
| } |
| delete(d.funcs, key) |
| d.mu.Unlock() |
| f() |
| } |
| } |