Go has a package named context in its standard library. It’s responsibilities are cancelations and carrying request-scoped data (but that doesn’t mean it’s only used in HTTP handlers). I’ve learned it in two parts. For a long time I used it only for storing request-scoped data in HTTP middlewares, but recently I’ve learned the rest of it – (1) how to set up canceling context and (2) acting upon receiving canceling signal from context.
Setting up canceling context
Setting up canceling context is easy. We can use
context.WithDeadline(). All those functions return resulting context and cancel function. When calling, cancel function will cancel context:
- immediatelly if created with WithCancel or
- after elapsed time if created with WithTimeout and WithDeadline.
WithDeadline and WithTimeout differ on how to specify time when cancel function sends signal to context’s
Done() channel. WithTimeout expects
time.Duration while WithDeadline expects
Another important note is that when new context is created from existing, a parent-child link between contexts is created. This allows us if parent is canceled, its childrens are canceled too. So that’s why when we are creating a context we need to specify a base context (in most cases that would be
context.Background() or in HTTP handlers,
r.Context() where r is of
Acting upon receiving canceling signal from context
Here we acknowledge contex.Done() signal and cancel function/service that could take a long time to execute and for which we are doing all this context thing.
Acknowledging is easy because for this we use built-in select with at least two cases. One case is reserved for Done() and other for our function/service singaling that it’s done (as in it’s result is sent into dedicated custom channel). If our function/service is done before canceling signal then we return its return.
If Done() is triggered first, we also need to cancel our function/service so it stops eating system’s resources. This might be the most challenging part of all. For example canceling a request requires us to get a reference to its transport instance and call its cancelRequest. If we our service/function is implemented with third-party package, explore its API if some sort of cancelation is offered.
Below is an example of canceling a request that takes too long to complete and we’d like to cancel it after timeout. We have a server that simulates long running task by sleeping
Than we have a client which sends request to server. On line 17 we create context with WithTimeout and then defering call to cancel. Function
httpDo performs the actual canceling part.
You can start client with different timeout to see how client’s response differ if timeout is greater/lower than server’s response time.