'How can I sleep with responsive context cancelation?
In Go, I want to time.Sleep
for some time (e.g. waiting between retries), but want to return quickly if the context gets canceled (not just from a deadline, but also manually).
What is the right or best way to do that? Thanks!
Solution 1:[1]
You can use select
to acheive this:
package main
import (
"fmt"
"time"
"context"
)
func main() {
fmt.Println("Hello, playground")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func(){
t := time.Now()
select{
case <-ctx.Done(): //context cancelled
case <-time.After(2 * time.Second): //timeout
}
fmt.Printf("here after: %v\n", time.Since(t))
}()
cancel() //cancel manually, comment out to see timeout kick in
time.Sleep(3 * time.Second)
fmt.Println("done")
}
Here is the Go-playground link
Solution 2:[2]
Here is a sleepContext
function that you can use instead of time.Sleep
:
func sleepContext(ctx context.Context, delay time.Duration) {
select {
case <-ctx.Done():
case <-time.After(delay):
}
}
And some sample usage (full runnable code on the Go Playground):
func main() {
ctx := context.Background()
fmt.Println(time.Now())
sleepContext(ctx, 1*time.Second)
fmt.Println(time.Now())
ctxTimeout, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
sleepContext(ctxTimeout, 1*time.Second)
cancel()
fmt.Println(time.Now())
}
Solution 3:[3]
You can use select
as others have mentioned; however, the other answers have a bug since timer.After()
will leak memory if not cleaned up.
func SleepWithContext(ctx context.Context, d time.Duration) {
timer := time.NewTimer(d)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
case <-timer.C:
}
}
Solution 4:[4]
I managed to do something similar by combining a CancelContext with a TimeoutContext...
Here is the sample code:
cancelCtx, cancel := context.WithCancel(context.Background())
defer cancel()
// The program "sleeps" for 5 seconds.
timeoutCtx, _ := context.WithTimeout(cancelCtx, 5*time.Second)
select {
case <-timeoutCtx.Done():
if cancelCtx.Err() != nil {
log.Printf("Context cancelled")
}
}
In this repo you can find the complete usage of the above code. Sorry for my short answer, I didn't turned on the computer yet, and is not that easy to answer from the phone...
Solution 5:[5]
The time.After()
function has this problem:
The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed.
It is better to use a Timer
object and call Stop()
:
// Delay returns nil after the specified duration or error if interrupted.
func Delay(ctx context.Context, d time.Duration) error {
t := time.NewTimer(d)
select {
case <-ctx.Done():
t.Stop()
return fmt.Errorf("Interrupted")
case <-t.C:
}
return nil
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | Hunter Ford |
Solution 4 | Sebastián Rodriguez |
Solution 5 | lgekman |