multithreadinggogo-context

Prevent context cancel from stopping function execution in the middle of business logic


Is there a way to safeguard the execution of business logic from context cancel? Here is the code snippet for a better understanding of my problem

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go foo(ctx)
    time.Sleep(time.Second * 3)
    cancel()
}

func foo(ctx context.Context) {
    // batch process data
    // context cancel should not stop function execution 
    // in the middle of business logic
    for i:= 0; i<10; i++ {
        fmt.Println("START of business logic for ID:", i)
        fmt.Println("Critical component")
        fmt.Print("Saving changes to DB...")
        time.Sleep(time.Second * 1)
        fmt.Println("Done")
        fmt.Println("END of business logic for ID:", i)
    }
}

Output:

START of business logic for ID: 0
Critical component
Saving changes to DB...Done
END of business logic for ID: 0
START of business logic for ID: 1
Critical component
Saving changes to DB...Done
END of business logic for ID: 1
START of business logic for ID: 2
Critical component
Saving changes to DB...Done
END of business logic for ID: 2

When the execution starts in the for loop, it should not stop until it finishes that iteration. Is this possible with using context cancel? or should I use another approach, please suggest.

Go Playground Link


Solution

  • Context cancelation is a signal mechanism to a task. It does not ensure enforcement - that is left up to the task. This allows a task to finish up critical sub-tasks before aborting the larger operation.

    So in your theoretical example any critical sub-steps should ignore cancelation - and only after they are complete poll the context:

    select {
        case <-ctx.Done():
            return ctx.Err() // we're cancelled, abort
        default:
    }
    

    EDIT: applying to your example

    for i := 0; i < 10; i++ {
        //
        // critical section
        //
    
        // ...
    
        select {
        case <-ctx.Done():
            return ctx.Err() // we're cancelled, abort
        default:
        }
    }
    

    https://play.golang.org/p/kZ39VEVyP4L