Skip to content

Instantly share code, notes, and snippets.

@satyajitnayk
Created January 28, 2024 05:29
Show Gist options
  • Save satyajitnayk/d389f25d2404249a2563841fe99c5f59 to your computer and use it in GitHub Desktop.
Save satyajitnayk/d389f25d2404249a2563841fe99c5f59 to your computer and use it in GitHub Desktop.
usages of context in golang

Context in Golang

Introduction

In Golang, the Context package provides a way to propagate deadlines, cancelation signals, and other request-scoped values across API boundaries and between goroutines. It's a powerful tool for managing concurrency and ensuring that operations are completed within a certain context.

Why Use Context?

1. Propagating Deadlines and Cancellation Signals

In real-life applications, operations often need to be canceled or completed within a certain timeframe. For example, in web servers, you may want to cancel a request if it's taking too long to process, or in microservices architectures, you might need to terminate a request if downstream services are unresponsive.

2. Passing Request-Scoped Values

Context allows you to carry deadlines, cancellation signals, and other request-scoped values across API boundaries and between goroutines without having to explicitly pass them as parameters.

3. Graceful Shutdowns

Context can also be used for graceful shutdowns. During application shutdown, you can use context to signal to all active goroutines that they should finish their work and exit cleanly.

Different Typs of context supported by context package:

1. context.WithValue

Description: context.WithValue allows passing request-scoped values across API boundaries. However, it's not recommended for passing request-scoped values across independent goroutines.

Example:

Suppose you have an HTTP server where you need to pass user authentication details or request-specific data across the call stack.

package main

import (
	"context"
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	ctx := context.WithValue(r.Context(), "userID", 123)
	// Passes user ID across the call stack
	anotherFunction(ctx)
}

func anotherFunction(ctx context.Context) {
	userID := ctx.Value("userID").(int)
	fmt.Println("User ID:", userID)
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

2. context.WithCancel

Description: context.WithCancel returns a derived context and a cancel function. Calling the cancel function cancels the context, which propagates the cancellation signal to all contexts derived from it.

Example:

You might want to cancel operations when a certain condition is met or when a parent operation is canceled.

package main

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

func main() {
	parentCtx := context.Background()
	ctx, cancel := context.WithCancel(parentCtx)

	go func() {
		time.Sleep(2 * time.Second)
		cancel() // Cancel the context after 2 seconds
	}()

	select {
	case <-ctx.Done():
		fmt.Println("Operation canceled")
	}
}

3. context.WithTimeout

Description: context.WithTimeout returns a derived context and a cancel function. The derived context is automatically canceled after the specified timeout duration.

Example:

Useful when you want to limit the time taken by an operation.

package main

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

func main() {
	parentCtx := context.Background()
	ctx, cancel := context.WithTimeout(parentCtx, 3*time.Second)
	defer cancel()

	select {
	case <-ctx.Done():
		fmt.Println("Operation timed out")
	}
}

4. context.WithDeadline

Description: context.WithDeadline returns a derived context and a cancel function. The derived context is automatically canceled when the deadline expires.

Example:

Helpful when you have a fixed deadline for an operation.

package main

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

func main() {
	parentCtx := context.Background()
	deadline := time.Now().Add(5 * time.Second)
	ctx, cancel := context.WithDeadline(parentCtx, deadline)
	defer cancel()

	select {
	case <-ctx.Done():
		fmt.Println("Operation deadline exceeded")
	}
}

Real-Life Usages of Context:

1. HTTP Servers

func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
    defer cancel()

    // Perform some operation that may take time
    // If the operation exceeds the deadline, it will be canceled
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

In this example, we create a new context with a timeout of 5 seconds derived from the incoming request's context. If the operation inside the handler function takes longer than 5 seconds, it will be canceled automatically.

2. Background Operations

func doSomething(ctx context.Context) {
    // Perform some operation
    select {
    case <-ctx.Done():
        // Operation canceled
        return
    default:
        // Continue with the operation
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // Perform background operation
    go doSomething(ctx)

    // Cancel operation after some time
    time.Sleep(3 * time.Second)
    cancel()
}

In this example, we create a background operation using a goroutine and pass a context to it. We then cancel the operation after 3 seconds, causing the operation inside the goroutine to exit gracefully.

3. Database Operations

func fetchFromDB(ctx context.Context, db *sql.DB) {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()

    rows, err := db.QueryContext(ctx, "SELECT * FROM users")
    if err != nil {
        // Handle error
        return
    }
    defer rows.Close()

    // Process rows
}

In database operations, using context allows for managing timeouts effectively. In this example, we create a new context with a timeout of 2 seconds. If the database query takes longer than the specified duration, it will be canceled automatically.

4. File I/O Operations

func readFile(ctx context.Context, filename string) ([]byte, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var result []byte
    buffer := make([]byte, 1024)

    for {
        select {
        case <-ctx.Done():
            return nil, ctx.Err()
        default:
            n, err := file.Read(buffer)
            if err == io.EOF {
                return result, nil
            }
            if err != nil {
                return nil, err
            }
            result = append(result, buffer[:n]...)
        }
    }
}

In file I/O operations, context can be used to cancel or set deadlines for reading or writing operations. In this example, we read from a file, checking the context for cancellation signals periodically. If the context is canceled, the function returns early with an appropriate error.

Conclusion

Context in Go is a powerful mechanism for managing concurrency, deadlines, and request-scoped values. By using context effectively, you can write more robust and scalable applications that gracefully handle timeouts, cancellations, and other context-related concerns.

Credit: Information taken from https://pkg.go.dev/context

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment