Sobes.tech
Middle

Tell about cases of using context in Go.

sobes.tech AI

Answer from AI

In Go, context is used to manage the lifecycle of requests and control the execution of goroutines. Main use cases include:

  • Cancellation: Allows canceling a chain of goroutines associated with a specific request. For example, when closing a connection or upon timeout.
  • Timeout: Sets a deadline for an operation. If the operation does not complete within the deadline, the context is canceled. This prevents infinite waiting.
  • Passing request-specific data: Enables passing data related to a specific request (e.g., user ID, trace ID) through the call chain without changing function signatures.

Examples of usage:

  1. Cancellation by signal:

    package main
    
    import (
    	"context"
    	"fmt"
    	"time"
    )
    
    func worker(ctx context.Context, id int) {
    	for {
    		select {
    		case <-ctx.Done():
    			fmt.Printf("Worker %d interrupted\n", id)
    			return
    		default:
    			fmt.Printf("Worker %d working...\n", id)
    			time.Sleep(500 * time.Millisecond)
    		}
    	}
    }
    
    func main() {
    	ctx, cancel := context.WithCancel(context.Background())
    	defer cancel() // Ensure cancellation
    
    	go worker(ctx, 1)
    	go worker(ctx, 2)
    
    	time.Sleep(2 * time.Second)
    	cancel() // Cancel the context
    
    	time.Sleep(500 * time.Millisecond) // Give goroutines time to finish
    }
    
  2. Timeout:

    package main
    
    import (
    	"context"
    	"fmt"
    	"time"
    )
    
    func longOperation(ctx context.Context) error {
    	select {
    	case <-time.After(3 * time.Second): // Simulate long work
    		fmt.Println("Operation completed successfully")
    		return nil
    	case <-ctx.Done():
    		fmt.Println("Operation cancelled by context:", ctx.Err())
    		return ctx.Err()
    	}
    }
    
    func main() {
    	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    	defer cancel()
    
    	err := longOperation(ctx)
    	if err != nil {
    		fmt.Println("Error:", err)
    	}
    }
    
  3. Passing data:

    package main
    
    import (
    	"context"
    	"fmt"
    )
    
    type UserKey string
    
    const userIDKey UserKey = "userID"
    
    func processRequest(ctx context.Context) {
    	userID := ctx.Value(userIDKey).(int)
    	fmt.Printf("Processing request for user ID: %d\n", userID)
    	// Further request processing using userID
    }
    
    func main() {
    	ctx := context.WithValue(context.Background(), userIDKey, 123)
    	processRequest(ctx)
    }
    

It is important to use context.Background() or context.TODO() as the root context and pass it down the call chain. Do not store context in data structures unless it is directly related to the lifecycle of that structure.