In Golang, I am fairly new to the intentions of passing contexts
downstream to other methods and functions. I understand how a context
works, how it is used, how it holds its values, how it's related to the parent context
and their behaviors -- I just don't understand why to use a context in the first place.
In a more specific example, which is the actual reason of this question, in the company I work for, we have identified some very long-running queries that happen every so often due to an edge case.
An obvious solution we decided to take, given our constraints until we invest time to fix the root cause, is to kill the queries that take more than 5 minutes.
The method that runs our transactions accepts a context
which is originally initiated in the API call. This context
is passed down all the way to the transaction function. At that moment I found 2 solutions to kill that query:
1) Using a new context:
Initiate a new context.WithTimeout(ctx, time.Duration( 5 * time.Minute) )
Watch the Done
channel in a go routine
and kill the transaction when there's a signal there
cancel
the context and commit the transaction as expected.2) Using a Timer
:
Timer
with 5 minutes durationLogically speaking, they are the same solution, however, when and how to decide whether to use a context
with a set Deadline or a good old Timer
?
The answer lies in how the context.Context
and the time.Timer
deliver the (cancel) signal.
context.Context
gives you access to a channel via the Context.Done()
method which will be closed when the goroutines using it should terminate.
time.Timer
gives you access to a channel in the Timer.C
struct field on which a value will be sent after the given time period (that value will be the current time, but not important here).
There, the key points are highlighted. The channel close may be observed by any number of goroutines, and infinite number of times. Spec: Receive operator:
A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
So a Context
may be used to signal cancelation to arbitrary number of goroutines and places. A Timer
can only be used to signal one target, the one who receives the value from its channel. If multiple clients are listening or trying to receive from its channel, only one will be lucky to receive it.
Also if you're using / working with code that already supports / expects context.Context
, then it ins't a question which one to use. Go 1.8 also added more context support. There have been significant additions to the database/sql
package with context support; including DB.BeginTx()
:
The provided context is used until the transaction is committed or rolled back. If the context is canceled, the sql package will roll back the transaction. Tx.Commit will return an error if the context provided to BeginTx is canceled.
This is the primary use of context.Context
: to carry a deadline and signal cancelation across API boundaries, and it's done in a concurrent-safe manner (as Context
values are immutable, and channels are also safe for concurrent use, data races cannot occur, by design; more on this: If I am using channels properly should I need to use mutexes?).
Related blog posts:
The Go Blog: Go Concurrency Patterns: Context
Pitfalls of context values and how to avoid or mitigate them in Go
Dave Cheney: Context is for cancelation
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With