Sobes.tech
Middle

How are contexts structured in the Go language?

sobes.tech AI

Answer from AI

The (context.Context) in Go is used to pass information about deadlines, cancellations, and other data across API boundaries. It forms a tree: each context is a child of another, except for the root (context.Background() or context.TODO()).

Main methods of the context.Context interface:

  • Deadline() (deadline time.Time, ok bool): Returns the deadline of the context.
  • Done() <-chan struct{}: Returns a channel that is closed when the context is canceled.
  • Err() error: Returns the error that caused the cancellation.
  • Value(key any) any: Returns the value associated with the key in the context.

Created using functions from the context package:

  • context.Background(): The root, empty context, used as the base for the main process.
  • context.TODO(): Used as a placeholder when the specific context is unknown.
  • context.WithCancel(parent Context): Creates a child context and a cancel function.
  • context.WithDeadline(parent Context, d time.Time): Creates a child context with a deadline.
  • context.WithTimeout(parent Context, timeout time.Duration): Creates a child context with a timeout.
  • context.WithValue(parent Context, key, val any): Creates a child context with a value.

Main implementations of Context:

  • emptyCtx: Basic, non-cancelable, without values. Used by Background and TODO.
  • cancelCtx: Supports cancellation. Consists of a base context and a cancel channel.
  • timerCtx: Supports deadline/timeout. Contains cancelCtx and a timer.
  • valueCtx: Supports passing values. Contains a base context and key-value pairs.

When a parent context is canceled, all child contexts are automatically canceled. This allows propagation of cancel signals or deadlines through the call chain.

Example of using context.WithCancel:

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// Create a root context and a cancel function
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // Ensure cancellation on main exit

	go func() {
		select {
		case <-ctx.Done(): // Wait for context cancellation
			fmt.Println("Task canceled:", ctx.Err())
			return
		case <-time.After(5 * time.Second): // Or finish after 5 seconds
			fmt.Println("Task completed")
		}
	}()

	// Cancel the context from another place
	time.Sleep(1 * time.Second)
	fmt.Println("Canceling context...")
	cancel()

	// Give the goroutine time to handle cancellation
	time.Sleep(100 * time.Millisecond)
}

Example of using context.WithValue:

package main

import (
	"context"
	"fmt"
)

type userIDKey string

func processRequest(ctx context.Context) {
	if userID, ok := ctx.Value(userIDKey("UserID")).(string); ok {
		fmt.Println("Processing request for User ID:", userID)
	} else {
		fmt.Println("User ID not found in context")
	}
}

func main() {
	ctx := context.WithValue(context.Background(), userIDKey("UserID"), "12345")
	processRequest(ctx)
}

Contexts are thread-safe and immutable, which is important for concurrent code. They are not meant for passing raw parameters to functions but rather for meta-information that influences process execution.