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

  1. Pass a long-lived (application lifecycle) context to Watch instead of a request context
  2. Call watcher.Stop() before canceling the context during shutdown
  3. 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

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.