| name | golang-context-patterns |
| description | Context usage patterns for Go including cancellation, timeouts, deadlines, and database transactions. Use when handling HTTP requests, database operations, or implementing cancellation and timeout logic. Use when this capability is needed. |
| metadata | {"author":"codeready-toolchain"} |
Go Context Patterns
Context usage patterns for Go following 2025-2026 best practices.
Context Basics
Context carries:
- Cancellation signals
- Deadlines and timeouts
- Request-scoped values
Golden rule: Always pass context as first parameter.
func DoWork(ctx context.Context, data string) error {
}
HTTP Handler Context
Extract from request:
func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
result, err := s.service.ProcessData(ctx, data)
if err != nil {
if errors.Is(err, context.Canceled) {
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(result)
}
Database Transaction Context Pattern
Critical pattern for TARSy:
func (s *SessionService) CreateSession(ctx context.Context, req CreateSessionRequest) (*ent.AlertSession, error) {
writeCtx, cancel := context.WithTimeoutCause(
context.Background(), 5*time.Second,
fmt.Errorf("create session %s: db write timed out", req.SessionID),
)
defer cancel()
tx, err := s.client.Tx(writeCtx)
if err != nil {
return nil, fmt.Errorf("failed to start transaction: %w", err)
}
defer func() { _ = tx.Rollback() }()
session, err := tx.AlertSession.Create().
SetID(req.SessionID).
Save(writeCtx)
if err != nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
if err := tx.Commit(); err != nil {
return nil, fmt.Errorf("failed to commit: %w", err)
}
return session, nil
}
Why background context for database operations:
- HTTP request context might be cancelled if client disconnects
- Database writes should complete even if client disconnects
- Use separate timeout to prevent hanging forever
Context Timeout Patterns
WithTimeoutCause for operations with deadline:
func FetchData(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeoutCause(ctx, 10*time.Second,
fmt.Errorf("fetch %s timed out", url),
)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
WithDeadlineCause for specific time:
func ProcessByDeadline(ctx context.Context, deadline time.Time) error {
ctx, cancel := context.WithDeadlineCause(ctx, deadline,
fmt.Errorf("processing missed deadline %s", deadline),
)
defer cancel()
return doWork(ctx)
}
Context Cancellation
WithCancelCause for manual cancellation:
func ProcessWithCancel(ctx context.Context) error {
ctx, cancel := context.WithCancelCause(ctx)
defer cancel(nil)
go func() {
if err := backgroundWork(ctx); err != nil {
cancel(fmt.Errorf("background work failed: %w", err))
}
}()
return mainWork(ctx)
}
Checking for cancellation:
func LongOperation(ctx context.Context) error {
for i := range 1000 {
select {
case <-ctx.Done():
return context.Cause(ctx)
default:
}
processItem(i)
}
return nil
}
Database Query Context
Using context for queries:
func (s *SessionService) GetSession(ctx context.Context, id string) (*ent.AlertSession, error) {
session, err := s.client.AlertSession.
Query().
Where(alertsession.IDEQ(id)).
Only(ctx)
if err != nil {
return nil, err
}
return session, nil
}
Query with timeout:
func (s *SessionService) ListSessions(ctx context.Context, limit int) ([]*ent.AlertSession, error) {
queryCtx, cancel := context.WithTimeoutCause(ctx, 3*time.Second,
fmt.Errorf("list sessions query timed out"),
)
defer cancel()
sessions, err := s.client.AlertSession.
Query().
Limit(limit).
All(queryCtx)
if err != nil {
return nil, err
}
return sessions, nil
}
Goroutine Context Propagation
Pass context to goroutines:
func ProcessConcurrently(ctx context.Context, items []string) error {
errChan := make(chan error, len(items))
for _, item := range items {
go func() {
errChan <- processItem(ctx, item)
}()
}
for range items {
if err := <-errChan; err != nil {
return err
}
}
return nil
}
Cancelling goroutines on error:
func ProcessWithEarlyExit(ctx context.Context, items []string) error {
ctx, cancel := context.WithCancelCause(ctx)
defer cancel(nil)
errChan := make(chan error, len(items))
for _, item := range items {
go func() {
err := processItem(ctx, item)
if err != nil {
cancel(err)
}
errChan <- err
}()
}
for range items {
if err := <-errChan; err != nil {
return err
}
}
return nil
}
Context AfterFunc
Run cleanup when context is cancelled:
func WatchResource(ctx context.Context, res *Resource) {
stop := context.AfterFunc(ctx, func() {
res.Release()
})
defer stop()
}
Context Best Practices
DO:
- Always pass context as first parameter
- Use
context.Background() as root context
- Use
WithTimeoutCause/WithCancelCause/WithDeadlineCause — always attach a cause
- Use
context.Cause(ctx) to retrieve the cause on cancellation
- Use background context with timeout for database writes that should complete
- Check
ctx.Done() in long-running loops
- Propagate context through call chains
DON'T:
- Store context in structs (pass as parameter instead)
- Pass
nil context (use context.TODO() if unsure)
- Use context for optional function parameters
- Create new background context when you have a valid parent context
- Ignore context cancellation errors
TARSy-Specific Patterns
HTTP → Service → Database:
func (h *Handler) CreateSession(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, err := h.sessionService.CreateSession(ctx, req)
}
func (s *SessionService) CreateSession(httpCtx context.Context, req CreateSessionRequest) (*ent.AlertSession, error) {
writeCtx, cancel := context.WithTimeoutCause(
context.Background(), 5*time.Second,
fmt.Errorf("create session: db write timed out"),
)
defer cancel()
tx, err := s.client.Tx(writeCtx)
}
Background job context:
func (s *SessionService) CleanupOldSessions(ctx context.Context) error {
cleanupCtx, cancel := context.WithTimeoutCause(
context.Background(), 30*time.Second,
fmt.Errorf("session cleanup timed out"),
)
defer cancel()
count, err := s.SoftDeleteOldSessions(cleanupCtx, 90)
if err != nil {
return fmt.Errorf("cleanup failed: %w", err)
}
log.Printf("Cleaned up %d sessions", count)
return nil
}
Transaction Context Guidelines
Standard pattern for TARSy services:
func (s *Service) WriteOperation(httpCtx context.Context, data Data) error {
writeCtx, cancel := context.WithTimeoutCause(
context.Background(), 5*time.Second,
fmt.Errorf("write operation: db timed out"),
)
defer cancel()
tx, err := s.client.Tx(writeCtx)
if err != nil {
return fmt.Errorf("failed to start transaction: %w", err)
}
defer func() { _ = tx.Rollback() }()
result, err := tx.Entity.Create().
SetData(data).
Save(writeCtx)
if err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
Quick Reference
Context creation:
context.Background()
context.TODO()
context.WithCancelCause(parent)
context.WithTimeoutCause(parent, d, err)
context.WithDeadlineCause(parent, t, err)
context.AfterFunc(ctx, fn)
Context checking:
<-ctx.Done()
context.Cause(ctx)
errors.Is(err, context.Canceled)
errors.Is(err, context.DeadlineExceeded)
Common timeouts:
- HTTP requests: 10-30 seconds
- Database queries: 3-5 seconds
- Database writes: 5-10 seconds
- Background jobs: 30-60 seconds
Source: codeready-toolchain/claw-operator — distributed by TomeVault.