Go Package context: Difference between revisions
Line 19: | Line 19: | ||
This section documents the idiomatic pattern to preempt (cancel) a blocking function. | This section documents the idiomatic pattern to preempt (cancel) a blocking function. | ||
It consists in a <code>someFunc()</code> function that gets a Context instance as its first argument. The function will <code>select</code> | It consists in a <code>someFunc()</code> function that gets a Context instance as its first argument. The function will <code>select</code> reading the "done" channel returned by the Context's <code>Done()</code> method. When the context is [[#Cancelling_Context|externally cancelled]], reading on the "done" channel will unblock and allow the function to return. | ||
<syntaxhighlight lang='go'> | <syntaxhighlight lang='go'> |
Revision as of 23:45, 8 February 2024
External
Internal
Overview
The context
package provides two, somewhat unrelated, features.
One is the capability to preempt (cancel) blocking code executing on goroutines downstream in our call graph under a variety of circumstances (programmatic cancellation, timeout, deadline). This is an idiomatic preemption pattern, equivalent, but preferred to the done
channel pattern. It is preferred because comes from the standard library and it is more expressive.
The second feature is the capability to store request-scoped data.
Concepts
Programmatic Preemption (Cancellation)
This section documents the idiomatic pattern to preempt (cancel) a blocking function.
It consists in a someFunc()
function that gets a Context instance as its first argument. The function will select
reading the "done" channel returned by the Context's Done()
method. When the context is externally cancelled, reading on the "done" channel will unblock and allow the function to return.
func someFunc(ctx context.Context, c <-chan interface{}) {
for {
select {
case <-ctx.Done():
fmt.Printf("%v someFunc()\n", ctx.Err())
return
case <-c: // this will ensure the goroutine will block, as we will never write on that channel
}
}
}
Deadline
Timeout
Cancel Function
Call Graph
Context
context.Context
is an interface exposed by the context
package, aimed at being implemented by the Context instances. The Context
instances will flow through your system in the same way done
channels do. Each function downstream from the top level concurrent call would take Context
as its first argument.
The implementations ensure that they are concurrent-safe, so their methods may be called by multiple goroutines simultaneously.
The Background Context
The background Context is a non-null, empty, no-deadline and no-values Context returned by the context.Background()
function. This context is never canceled, and it is typically used by the main function, initialization and tests, and as the top-level Context for incoming requests.
Context Parent/Child Relationship
Context hierarchy.
Request
Request-Scoped Data
TODO
One of the key differences between Go and other language is explicit context propagation. Context propagation is a mechanism of propagating an additional call argument, called context, into function calls. There is actually a type called context.Context
.
The context is used for:
- Cancellation logic. You can pass a special instance of a context that can get canceled. In that case, all functions you were to call with it would be able to detect this. Can be useful for handling application shutdown or stopping any processing.
- Timeouts. Timeouts can be set for execution, by using the corresponding context functions.
- Propagating extra metadata. Additional key/value pairs can be propagate inside the context. Useful in distributed tracing. This feature should be used sparingly, in exceptional cases.
The context is immutable, but it can be cloned with extra metadata.
Function using context should accept it as their first argument.
Manage goroutines lifecycles.
If you have a component that blocks for any reason (disk, network IO, user), then it should probably take the context as a first parameter.
type reportStore interface {
listTimes(ctx context.Context, ...) (..., error)
writeFile(ctx context.Context, ...) error
serveFile(ctx context.Context, ...) error
}
Using the value propagation feature of the Context is dubious. Don't use Context.value for stuff that should be regular dependencies between components. Use Context.value for data that can be passed to your program in any other way: only data that is request-scoped, stuff that is created at the beginning of a request lifecycle, like request ID, etc. If the information is available when the program starts or at any point prior to when the request starts, do not use context.value. This is the case for database handles, loggers, etc.
- https://go.dev/blog/context
- All functions performing I/O operations must accept context. Why? https://learning.oreilly.com/library/view/microservices-with-go/9781804617007/B18865_02.xhtml#:-:text=all%20functions%20performing
- What is with the _ context.Context https://learning.oreilly.com/library/view/microservices-with-go/9781804617007/B18865_02.xhtml#:-:text=(_%20context.Context