diff options
| author | Jeff Carr <[email protected]> | 2025-10-12 07:33:05 -0500 |
|---|---|---|
| committer | Jeff Carr <[email protected]> | 2025-10-12 07:33:05 -0500 |
| commit | b31ca9e2bbd1a0110a44ff6412036b78429cc321 (patch) | |
| tree | 49c751ca69a8b11446d5be1ad6087b5b699401ad | |
| parent | 49f9535f1cd17f1c30b62464555b057d3daa3c66 (diff) | |
notes for laterv0.1.36
| -rw-r--r-- | context.go.notes | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/context.go.notes b/context.go.notes new file mode 100644 index 0000000..7cbb38c --- /dev/null +++ b/context.go.notes @@ -0,0 +1,109 @@ +✦ That's one of the most important questions for a modern Go developer. The context package, and the ctx variable, are + fundamental to writing robust, scalable, and reliable concurrent programs in Go. + + At its core, a context.Context is a standard way to carry deadlines, cancellation signals, and other request-scoped + values across API boundaries and between goroutines. + + Think of it as a "control channel" for a request or a task. + + The Three Main Jobs of a context.Context + + 1. Cancellation (The Most Important Job) + + This is the primary reason context was created. Imagine you start a complex operation that involves multiple goroutines + and network calls. + + * The Problem: What happens if the user who initiated the operation closes their browser or clicks "cancel"? Without a + context, all those background goroutines would keep running, consuming CPU, memory, and network resources, even though + their work is no longer needed. This is a resource leak. + + * The `context` Solution: You create a single context object at the beginning of the operation. You then pass this ctx to + every function and goroutine involved in the task. The original function holds a "cancel function." When the user + cancels, this function is called. + * All the downstream functions and goroutines are written to periodically check if the context has been cancelled + (e.-g., select { case <-ctx.Done(): return }). + * As soon as they see the cancellation signal, they stop their work, clean up any resources they were using, and exit + gracefully. + + This allows for a clean, cascading shutdown of a whole tree of concurrent operations. + + Example: + + 1 func main() { + 2 // Create a context that can be cancelled. + 3 ctx, cancel := context.WithCancel(context.Background()) + 4 + 5 // Simulate cancelling the operation after 2 seconds. + 6 time.AfterFunc(2*time.Second, cancel) + 7 + 8 // Start a long-running operation, passing it the context. + 9 longRunningOperation(ctx) + 10 } + 11 + 12 func longRunningOperation(ctx context.Context) { + 13 for { + 14 select { + 15 case <-ctx.Done(): + 16 // The context was cancelled! Clean up and exit. + 17 fmt.Println("Operation cancelled. Cleaning up.") + 18 return + 19 default: + 20 // Do a small piece of work. + 21 fmt.Println("Working...") + 22 time.Sleep(500 * time.Millisecond) + 23 } + 24 } + 25 } + + 2. Timeouts and Deadlines + + This is a specialized form of cancellation. It's extremely common in network clients and servers. + + * The Problem: You make a request to a database or another microservice. What if that service is slow or completely + unresponsive? Your request could hang forever, holding up a connection and a goroutine. + + * The `context` Solution: You create a context with a timeout. + + 1 // This context will be automatically cancelled after 3 seconds. + 2 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + 3 defer cancel() // Good practice to always call cancel + 4 + 5 // Now, make the database call with this context. + 6 db.QueryRowContext(ctx, "SELECT ...") + The database/sql driver (and any well-written modern Go library) is designed to accept a ctx. It will monitor the + context and, if the 3-second timeout is reached before the query finishes, it will automatically cancel the database + query on the server and return a timeout error. + + 3. Request-Scoped Values + + This is the third, and least common, use case. A context can carry a lightweight key-value store. + + * The Problem: You have a web request, and you need to pass a request ID or a user's authentication token through many + layers of your application (from the HTTP handler to the middleware to the business logic to the database layer). + Passing these values as explicit arguments to every single function would be cumbersome and clutter the function + signatures. + + * The `context` Solution: You can store these values in the context at the beginning of the request. + + 1 // In your middleware or HTTP handler: + 2 ctx := context.WithValue(r.Context(), "requestID", "xyz-123") + 3 // Now call the next handler with the new context. + 4 businessLogic(ctx, ...) + 5 + 6 // In your businessLogic function: + 7 func businessLogic(ctx context.Context, ...) { + 8 // Retrieve the value later. + 9 if reqID, ok := ctx.Value("requestID").(string); ok { + 10 log.Printf("Handling request %s", reqID) + 11 } + 12 } + Important Caveat: This should only be used for request-scoped data that is optional or informational. It should not be + used for required parameters, as that makes your function's dependencies hidden and unclear. + + The Rule of Thumb + + If a function is doing I/O (network, disk), waiting for anything, or could potentially be slow, it should accept a + context.Context as its first argument. This has become a strong convention in the Go community. + + func DoSomething(ctx context.Context, arg1 string, arg2 int) error + |
