My use case is: when user stops typing in the text box, I will call a function process_input(), I can't call it on every character, I need to only call it after the typing is finished.
So the idea is: use a helper function to throttle the process_input(), if it is called repeatedly within a period of time, the previous calls will be discarded, only the last call will be triggered after a while.
I want to use it like:
func process_input() {
fmt.Println(`process_input() called`)
}
func main() {
fn := buffer.Timed(time.Second, process_input)
fn() // shouldn't be called
fn() // may be called or not
time.Sleep(time.Second)
fn() // shouldn't be called
fn() // will be called
time.Sleep(time.Hour) // block it from exiting
}
My implementation:
package buffer
import (
"sync/atomic"
"time"
)
func Timed(dur time.Duration, fn func()) func() {
kill := make(chan bool)
isScheduled := atomic.Bool{}
return func() {
if isScheduled.Load() { // if there is previous job scheduled
kill <- true // kill the previous, sometimes this line causes deadlock.
}
// start the next
isScheduled.Store(true)
go func() {
defer isScheduled.Store(false)
select {
case <-time.After(dur):
go fn()
case <-kill:
}
}()
}
}
Sometimes it crashes with deadlock at the line kill <- true, how to fix it?
You've made a number of logical errors dealing with synchronization, but you can avoid most of the complications by reducing the concurrency and just using a timer to trigger the function for you.
Your goal (at least as it appears in the code above) is to have the function triggered after a delay, and reset that delay on each new call if the function has not yet been called. You can do this with a single
time.Timerand a mutex to protect the field: https://go.dev/play/p/54QMJ7xHTZ5An example with multiple calls would look like: