Sequence of events leading up to cancellation of context

In the below program, there is a select sequence in both C1 & C2. A deadline context for 1.5 seconds is defined in C1, which after the deadline, cancels the context, leads to ctx.Done() and prevents the reading of integers in C2 which would further cancel the context and lead to ctx.Done() in C1.

If that is the case then what use is cancelCtx() in C1 when the context is already being cancelled?

package main

import (

func main() {
    ctx := context.Background()
func C1(ctx context.Context) {
    deadline := time.Now().Add(1500 * time.Millisecond) 
    ctx, cancelCtx := context.WithDeadline(ctx, deadline)
    printCh := make(chan int) 
    go C2(ctx, printCh)
    for num := 1; num <= 3; num++ {
        select {
        case printCh <- num:
            time.Sleep(1 * time.Second)
        case <-ctx.Done():
            fmt.Printf("C1: Finished\n")
    time.Sleep(100 * time.Millisecond)
func C2(ctx context.Context, printCh <-chan int) {
    for {
        select {
        case num := <-printCh:
            fmt.Printf("C2: %d\n", num)
        case <-ctx.Done():
            if err := ctx.Err(); err != nil {
                fmt.Printf("C2 Error: %s\n", err)
            fmt.Printf("C2: Finished\n")

>Solution :

As per the docs for context.WithDeadline():

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

So best practice is to call cancelCtx() (or whatever you call the function) as soon as you no longer need it (it’s fairly common to defer cancel() even if this means it’s called multiple times).

In your example there is another reason that this might be beneficial. Lets assume that the deadline is configurable and has been increased to 5 seconds; now cancelling the context leads to the termination of C2 being triggered by the call cancelCtx() instead of waiting until the deadline (playground).

Leave a Reply