Contexts with deadlines

Today we’re talking about canceling contexts based on time. Arguably, this should have come first, as it’s a little simpler to work with than the explicit cancelation we talked about a week ago. But once again, I’m going in essentially alphabetical order, so it’s what we have… func WithDeadline func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) WithDeadline returns a derived context that points to the parent context but has the deadline adjusted to be no later than d.


Context causes

About a week ago, I talked about the two error types that a context may return: context.Canceled and context.DeadlineExceeded. But what if you want to convey some other error? Maybe you want to distinguish between a context that was canceled because the server is shutting down due to a SIGINT, versus becasue the request was canceled by the client, for example? Enter Cause. func Cause func Cause(c Context) error Cause returns a non-nil error explaining why c was canceled.


Canceling a context

I’m jumping out of order a bit today, to cover a more basic function first, before the more advanced version. Bear with me. func WithCancel func WithCancel(parent Context) (ctx Context, cancel CancelFunc) WithCancel returns a derived context that points to the parent context but has a new Done channel. The returned context’s Done channel is closed when the returned cancel function is called or when the parent context’s Done channel is closed, whichever happens first.


AfterFunc

One of the drawbacks of this format I’ve chosen—reading through a document, and providing commentaty, in order—is that sometimes advanced topics come up before the foundational ones they rely on. Today is one such case. Typically, context cancelation is used to terminate an action in progress. But it can also be used to begin an action. One way to accomplish this is to wait for a cancelation signal in a goroutine:


Context package variables

The context package exports two variables: Variables var Canceled = errors.New("context canceled") Canceled is the error returned by [Context.Err] when the context is canceled for some reason other than its deadline passing. var DeadlineExceeded error = deadlineExceededError{} DeadlineExceeded is the error returned by [Context.Err] when the context is canceled due to its deadline passing. That’s it. These are the only two types of errors that a context’s Err() method is allowed to return.


Concurrent use of contexts

Overview … The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines. Contexts are built using layers. Every time you call one of the context.With* functions, the orginal context is wrapped in a new layer. This is the “magic” that makes contexts concurrency safe by design–they’re never mutated once they’re created. ctx := context.Background() // Create an original context ctx, cancel = context.


When not to use context values

Storing values in a context is quite flexible, but comes without strict type safety, and obscures the API. So what can we do about this? First, for type safety, we’re limited to runtime assertions: userID := ctx.Value(userIDKey).(string) But this can panic if we ever get an unexpected value (or even nil). So to make it safer, we can use the two-variable assignment form, which yields a bool indicating success: userID, ok := ctx.

Subscribe to Boldly Go: Daily

Every day I'll send you advice to improve your understanding of Go. Don't miss out! I will respect your inbox, and honor my privacy policy.

Unsure? Browse the archive.


Context values and type safety

Last week I asked whether or not it’s a good idea to pass things like a user-scoped database handle or logger via a context value. Before I provide my direct answer, I want to take a short detour… Go is a (mostly) strictly-typed language. This gives us certain guarantees. When we have a variable of type int, we know it doesn’t contain the value "cow", because that’s not an integer.


Context abuse

We’re nearly through the overview of the context package, when we come across this seemlingly straightforward sentence: Overview … Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. But is that really so straight forward? Let’s consider some examples. Let’s first tackle what probably is straight-forward: For garden variety optional parameters, context is the wrong tool! Do this: // Connect connects to the service using the provided host and port, or the // default host and/or port if omitted.


T.Context

After my last email, discussing context.TODO(), I got this question from Bruno Schaatsbergen: Thanks for the blog Jonathan, as always a pleasure to read. Regarding the last part on testing, any particular reason why you do not recommend using the new T.Context and B.Context methods? Great question! Go 1.24 added new methods to the testing package: T.Context, B.Context, and F.Context. These methods return a context which is canceled when the test, benchmark, or fuzz test, respectively, is canceled.


context.TODO

Overview … Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use. Two pieces of advice in today’s section. I’m going to tackle them in reverse, though. Use context.TODO() if you are unsure which Context to use. Functionally context.TODO() is identical to context.Background(). In fact, the only difference between the two, is their implementation of the String() method, as we can see by reading the source: