Propagating Request-Scoped Data in Go with context.Context
In distributed systems and concurrent Go applications, it's often necessary to pass request-scoped data, such as request IDs, user authentication tokens, deadlines, or cancellation signals, across different function calls and goroutines. The context.Context type in Go is specifically designed for this purpose, providing a standardized way to carry deadlines, cancellation signals, and request-scoped values across API boundaries and between goroutines.
This challenge focuses on implementing and demonstrating proper context propagation in a multi-stage Go operation.
Problem Description
Your task is to implement a function that simulates a multi-stage process, where each stage might perform an operation that requires access to request-scoped data. This data should be passed using context.Context.
You will need to:
- Define a main function that initiates a request and creates a
context.Contextwith some custom request-scoped values. - Implement several hypothetical "stages" or "services" that perform distinct operations. Each of these stages must accept a
context.Contextas its first argument. - Propagate the
context.Contextthrough all subsequent function calls within each stage, and if applicable, to other goroutines spawned within a stage. - Demonstrate how to retrieve request-scoped values from the context within the stages.
- Showcase context cancellation: Implement a mechanism where the context can be cancelled, and how downstream operations gracefully handle this cancellation.
Key Requirements:
- Use
context.WithValueto associate custom data (e.g., a request ID, user ID) with the context. - Use
context.WithCancelto allow for explicit cancellation of the context. - Each function that is part of the request processing chain must accept
context.Contextas its first parameter. - Demonstrate retrieving values using
ctx.Value(). - Handle potential errors returned by context operations (e.g.,
ctx.Done()). - When a context is cancelled, all goroutines or operations listening to that context should stop their work and return promptly.
Expected Behavior:
- The main function should create a root context with some initial values and a cancellation function.
- When values are added to the context, they should be retrievable in downstream functions.
- When the context is cancelled (either explicitly or due to a deadline, though explicit cancellation is the focus here), all ongoing operations associated with that context should cease.
- The program should output messages indicating the progress of each stage and whether it successfully completed or was cancelled.
Edge Cases to Consider:
- What happens if
ctx.Value()is called with a key that doesn't exist? (It should returnnil). - How do you handle operations that might take a long time and need to respect the context's cancellation?
Examples
Example 1: Successful Operation without Cancellation
Input:
A root context is created with a request ID "req-123" and a user ID "user-abc".
The context is not cancelled.
The simulated stages execute successfully.
Output:
Starting request req-123 for user user-abc.
Stage 1: Processing...
Stage 1: Done.
Stage 2: Processing...
Stage 2: Done.
Stage 3: Processing...
Stage 3: Done.
Request req-123 completed successfully.
Example 2: Operation Cancelled Mid-way
Input:
A root context is created with a request ID "req-456" and a user ID "user-xyz".
The context is cancelled after Stage 1 completes.
Stage 2 is interrupted by the cancellation.
Output:
Starting request req-456 for user user-xyz.
Stage 1: Processing...
Stage 1: Done.
Cancelling request req-456.
Stage 2: Detected cancellation, stopping.
Stage 3: Skipped due to cancellation.
Request req-456 was cancelled.
Constraints
- The solution must be written in Go.
- Standard Go libraries, including
context,fmt,time, andsync, are permitted. - The core logic should demonstrate the propagation of
context.Contextand its associated values/cancellation. - Focus on clarity and correctness of context usage rather than complex algorithmic solutions within the stages.
Notes
- Think about how to define custom key types for
context.WithValueto avoid collisions. An empty struct or a dedicated type is a common pattern. - Consider using
selectstatements withctx.Done()to gracefully exit goroutines or long-running operations when the context is cancelled. - The "stages" can be simple functions that print messages, simulate work with
time.Sleep, and check for context cancellation. - The goal is to understand the mechanics of context propagation, not to build a production-ready distributed system component.