go-kratos/kratos · warning
watch context canceled: %v
Error message
watch context canceled: %v
What it means
watcher.Next blocks on either a resolve event or the context passed to Discovery.Watch; when that context is canceled or times out, Next returns 'watch context canceled: <cause>'. Expected on shutdown, surprising when a request-scoped context was passed to Watch.
Source
Thrown at contrib/registry/discovery/impl_discover.go:67
cancelCtx: ctx,
}, nil
}
type watcher struct {
resolve *Resolve
cancelCtx context.Context
serviceName string
}
func (w *watcher) Next() ([]*registry.ServiceInstance, error) {
event := w.resolve.Watch()
select {
case <-event:
// change event come
case <-w.cancelCtx.Done():
return nil, fmt.Errorf("watch context canceled: %v", w.cancelCtx.Err())
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
ins, ok := w.resolve.fetch(ctx)
if !ok {
return nil, errors.New("Discovery.GetService fetch failed")
}
out := filterInstancesByZone(ins, w.resolve.d.config.Zone)
if len(out) == 0 {
return nil, fmt.Errorf("Discovery.GetService(%s) not found", w.serviceName)
}
return out, nil
}
View on GitHub (pinned to 668db92c2c)
Solutions
- Pass a long-lived (application lifecycle) context to Watch instead of a request context
- Call watcher.Stop() before canceling the context during shutdown
- Treat context.Canceled from Next() as a normal stop, not an error to alert on
Example fix
// before
w, _ := disc.Watch(reqCtx, "user-service") // reqCtx canceled when handler returns
// after
w, _ := disc.Watch(appCtx, "user-service") // long-lived
go func() {
defer w.Stop()
for {
if _, err := w.Next(); err != nil {
return
}
}
}() Defensive patterns
Strategy: validation
Validate before calling
if err := ctx.Err(); err != nil {
return err // do not enter Next() with an already-canceled context
}
ins, err := w.Next() Try / catch
ins, err := w.Next()
if err != nil {
if errors.Is(ctx.Err(), context.Canceled) || errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil // expected shutdown: stop cleanly
}
return err
} Prevention
- Pass app-lifecycle contexts to Watch, never request contexts
- Call watcher.Stop() before canceling the context
- Treat context.Canceled from Next() as normal shutdown
When it happens
Trigger: watcher.Next() running while the context given to Discovery.Watch(ctx, ...) gets canceled or exceeds its deadline, so the select takes the cancelCtx.Done() branch.
Common situations: Passing a request/handler context into Watch that dies when the handler returns; shutdown ordering that cancels the context before Stop; short-lived consumers with tight deadlines.
Related errors
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/b64011c3f6b9a724.
Report an issue: GitHub.