[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# Cancellation, Timeouts & Deadlines
|
||||
|
||||
## Cancellation
|
||||
|
||||
`context.WithCancel` returns a derived context and a `cancel` function. When `cancel()` is called, the context's `Done()` channel is closed, signaling all listeners to stop.
|
||||
|
||||
```go
|
||||
func processItems(ctx context.Context, items []Item) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel() // always defer cancel to free resources
|
||||
|
||||
errCh := make(chan error, len(items))
|
||||
for _, item := range items {
|
||||
go func(item Item) {
|
||||
errCh <- processOne(ctx, item)
|
||||
}(item)
|
||||
}
|
||||
|
||||
for range items {
|
||||
if err := <-errCh; err != nil {
|
||||
cancel() // cancel remaining goroutines on first error
|
||||
return fmt.Errorf("processing items: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Why `defer cancel()` matters
|
||||
|
||||
Every `WithCancel`, `WithTimeout`, and `WithDeadline` creates cancellation state; timeout/deadline contexts also use timer resources. `cancel()` MUST be called on all control-flow paths unless the function explicitly returns or transfers ownership of both the context and cancel function. In ordinary scoped work, defer cancel immediately.
|
||||
|
||||
```go
|
||||
// ✗ Bad — cancel is never called, resources leak
|
||||
func fetch(ctx context.Context) error {
|
||||
ctx, _ = context.WithTimeout(ctx, 5*time.Second)
|
||||
return doWork(ctx)
|
||||
}
|
||||
|
||||
// ✓ Good — scoped work, defer cancel immediately
|
||||
func fetch(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
return doWork(ctx)
|
||||
}
|
||||
```
|
||||
|
||||
## Timeouts and Deadlines
|
||||
|
||||
### `context.WithTimeout` — relative duration
|
||||
|
||||
```go
|
||||
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return s.repo.FindByID(ctx, id)
|
||||
}
|
||||
```
|
||||
|
||||
### `context.WithDeadline` — absolute point in time
|
||||
|
||||
```go
|
||||
func (s *BatchService) ProcessBatch(ctx context.Context, batch Batch) error {
|
||||
// The batch must complete by its SLA deadline
|
||||
ctx, cancel := context.WithDeadline(ctx, batch.SLADeadline)
|
||||
defer cancel()
|
||||
|
||||
for _, item := range batch.Items {
|
||||
if err := s.process(ctx, item); err != nil {
|
||||
return fmt.Errorf("processing batch item %s: %w", item.ID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Nested timeouts take the shorter deadline
|
||||
|
||||
If a parent context has a 5s timeout and you create a child with 10s, the child still expires at 5s. The shorter deadline always wins.
|
||||
|
||||
```go
|
||||
// Parent has 2s timeout — child's 10s is effectively ignored
|
||||
parentCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
childCtx, childCancel := context.WithTimeout(parentCtx, 10*time.Second)
|
||||
defer childCancel()
|
||||
// childCtx expires after 2s, not 10s
|
||||
```
|
||||
|
||||
## Listening for Cancellation
|
||||
|
||||
### The `select` pattern
|
||||
|
||||
Use `ctx.Done()` in a `select` statement to react to cancellation alongside other work:
|
||||
|
||||
```go
|
||||
func poll(ctx context.Context, interval time.Duration) error {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err() // context.Canceled or context.DeadlineExceeded
|
||||
case <-ticker.C:
|
||||
if err := doWork(ctx); err != nil {
|
||||
return fmt.Errorf("polling: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Checking cancellation in loops
|
||||
|
||||
For CPU-bound work, periodically check `ctx.Err()`:
|
||||
|
||||
```go
|
||||
func processLargeDataset(ctx context.Context, items []Item) error {
|
||||
for i, item := range items {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("processing interrupted after %d/%d items: %w", i, len(items), ctx.Err())
|
||||
}
|
||||
process(item)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## `context.AfterFunc` (Go 1.21+)
|
||||
|
||||
Registers a callback that runs in its own goroutine when the context is cancelled. Useful for cleanup without blocking the main flow.
|
||||
|
||||
```go
|
||||
func watchResource(ctx context.Context, res *Resource) {
|
||||
stop := context.AfterFunc(ctx, func() {
|
||||
// Runs in a new goroutine when ctx is cancelled
|
||||
res.Release()
|
||||
})
|
||||
|
||||
// If you no longer need the callback, cancel it:
|
||||
// stop() returns true if the callback was successfully cancelled
|
||||
_ = stop
|
||||
}
|
||||
```
|
||||
|
||||
## `context.WithoutCancel` (Go 1.21+)
|
||||
|
||||
Creates a child context that is not cancelled when the parent is. Use this for background work that must continue after the request completes — like async logging, audit trails, or enqueuing follow-up tasks.
|
||||
|
||||
```go
|
||||
func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
order, err := h.orderService.Create(ctx, req)
|
||||
if err != nil {
|
||||
// handle error
|
||||
return
|
||||
}
|
||||
|
||||
// Audit log must complete even if the client disconnects.
|
||||
// WithoutCancel preserves context values (trace_id) but detaches cancellation.
|
||||
auditCtx := context.WithoutCancel(ctx)
|
||||
go h.auditService.LogOrderCreated(auditCtx, order)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
```
|
||||
|
||||
Without `WithoutCancel`, you'd have to choose between `ctx` (which gets cancelled when the handler returns, killing your background work) and `context.Background()` (which loses trace_id and other values). `WithoutCancel` gives you the best of both: values are preserved, but cancellation is detached.
|
||||
Reference in New Issue
Block a user