
Five Go Concurrency Patterns, every Go Developer must know - Context Cancellation
with a hotel room booking simulation
This is a part of a multi-part series on Go Concurrency Patterns:
Go Concurrency Pattern - Context Cancellation
The Context Cancellation pattern in Go is a way to manage the lifecycle of goroutines, enabling:
Propagation of cancellation signals across multiple goroutines.
Timeouts and deadlines to control the execution duration of tasks.
Resource cleanup when operations are no longer needed.
Scenario : Hotel room booking with timeout cancellation
In a hotel booking system, a customer initiates a booking process:
The system reserves a room.
The payment gateway processes the transaction.
The confirmation is sent to the user.
Problem
If payment processing exceeds a time limit (e.g., due to gateway issues), the room reservation should be canceled to avoid blocking inventory.
We will Use the Context Cancellation pattern to ensure:
Timeout for payment processing.
Propagation of cancellation to the room reservation goroutine if payment fails or exceeds the deadline.
Solution:
Setup business process functions passing in a handle to the context reference with the required timeout settings.
Perform business process regularly
When the whole process takes more than the required timelimit, then context timeout automatically happens, cancelling all the outstanding processes
In the normal situation, when all the processes completes within timelimit, then we explicitly call context cancellation to cleanup all the resources
reserveRoom: reserveRoom function simulates serverving a room using Select..Case. Select..Case is a blocking wait till anyone of the case is activated. When a case is activated, it executes logic under that case and return the function, after marking wb.Done(). First case simulates reserving a room and second case monitor for context cancellation signal. Reserve room returns success message 80% of the time through rand function. For 20% of time, it returns error again simulating an error in room reservation. Second case writes error when the context cancellation signal is received.
processPayment: Similar to above reserveRoom, processPayment simulates payment processing using Select..Case. First case, simulates payment processing and second case monitor for context cancellation.
sendConfirmation: Similar to above reserveRoom/processPayment, sendConfirmation simulates sending room reservation status (confirmation/error) using Select..Case. First case, simulates sending confirmation and second case monitor for context cancellation.
bookHotel : bookHotel is the main orchestrator of room reservation process steps. It make use of Go’s waitGroup, channel and goroutines. Channel is used for tracking errors from all the goroutines. We spin up all the goroutines concurrently and wait for all them to close in the separate goroutine. Meanwhile we collect error messages from error channel as and when they occur. When the error channel is closed, for range terminates and all the errors collected if any are returned to main function.
Pls note, all the goroutines are triggered parallelly which means they can run from any order which can produce misleading behavior as give below in the output result. Say Sending cofirmation can happen even before payment is done or room is reserved. This issue can be addressed using Saga pattern or sequencing above steps.
main : main function starts with creating a context with a timeout of 10 seconds. This is the key point in this pattern. meaning the whole process must completes within 10 seconds otherwise for any reason, context will be auto cancelled which might cancel all the outstanding running goroutine automatically. Also note that even after a successful completion, we still call the cancel function (due to defer cancel()) as a best practice which will ensure cleaning up of all the active outstanding resources if any
Sample Output:
Below is the result of multiple running of the program with error and successful scenario.
Conclusion:
As a best practice
Set Reasonable Timeouts: Choose timeout values that balance user experience and system performance.
Always Use
defer cancel()
: Ensure that resources tied to the context are released properly.Pass Context Downstream: Use the same context across all functions involved in an operation to propagate cancellation effectively.
Check
ctx.Done()
: Usectx.Done()
to identify whether a timeout or explicit cancellation occurred.Avoid Leaking Goroutines: Use context cancellation to terminate long-running or unused goroutines.