Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusing parent context with context.WithTimeout in go?

Tags:

go

Reusing parent context with context.WithTimeout with a new timeout

Hi there, I'm new to go. I was wondering if it's possible to reuse a parent context to create multiple context.withTimeout(). The rationale would be where I have to call multiple network requests in sequence and would like to set a timeout for each request at the same time using the parent's context.

Rationale

When the parent's context is cancelled, all the requests made would be cancelled too.

Problem

In the code below, it shows an example whereby LongProcess is the network request. However, the context is closed before the second LongProcess call can be made with a context deadline exceeded.

The documentation withDeadline states The returned context's Done channel is closed when the deadline expires, when the returned cancel function is called, or when the parent context's Done channel isclosed, whichever happens first.

So if that's the case, is there a way where I can reset the timer for withTimeout? Or do I have to create a new context context.Background() for every request? That would mean the parent context will not be passed. :(


// LongProcess refers to a long network request
func LongProcess(ctx context.Context, duration time.Duration, msg string) error {
    c1 := make(chan string, 1)
    go func() {
        // Simulate processing
        time.Sleep(duration)
        c1 <- msg
    }()

    select {
    case m := <-c1:
        fmt.Println(m)
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

func main() {
    ctx := context.Background()
    t := 2 * time.Second

    ctx, cancel := context.WithTimeout(ctx, t)
    defer cancel()

    // Simulate a 2 second process time
    err := LongProcess(ctx, 2*time.Second, "first process")
    fmt.Println(err)

    // Reusing the context.
    s, cancel := context.WithTimeout(ctx, t)
    defer cancel()

    // Simulate a 1 second process time
    err = LongProcess(s, 1*time.Second, "second process")
    fmt.Println(err) // context deadline exceeded
}

like image 398
Isaiah Avatar asked Jun 20 '26 20:06

Isaiah


1 Answers

It looks like the first call to context.WithTimeout shadow the parent context ctx. The later process re-use this already canceled context hence the error. You have to re-use the parent one. Here is the example updated:

func main() {
    // Avoid to shadow child contexts
    parent := context.Background()
    t := 2 * time.Second

    // Use the parent context.
    ctx1, cancel := context.WithTimeout(parent, t)
    defer cancel()

    err := LongProcess(ctx1, 2*time.Second, "first process")
    fmt.Println(err)

    // Use the parent context not the canceled one.
    ctx2, cancel := context.WithTimeout(parent, t)
    defer cancel()

    err = LongProcess(ctx2, 1*time.Second, "second process")
    fmt.Println(err)
}
like image 91
Samuel Vaillant Avatar answered Jun 23 '26 09:06

Samuel Vaillant



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!