micro/go-micro · error

could not get next

Error message

could not get next

What it means

etcdWatcher.Next() receives watch events over a channel. When the server sends a watch response marked Canceled (the watch was closed server-side, e.g. the watched lease expired or the watch was compacted/closed), Next returns 'could not get next' instead of a result.

Source

Thrown at registry/etcd/watcher.go:52

	if len(wo.Service) > 0 {
		watchPath = servicePath(wo.Service) + "/"
	}

	return &etcdWatcher{
		stop:    stop,
		w:       r.client.Watch(ctx, watchPath, clientv3.WithPrefix(), clientv3.WithPrevKV()),
		client:  r.client,
		timeout: timeout,
	}, nil
}

func (ew *etcdWatcher) Next() (*registry.Result, error) {
	for wresp := range ew.w {
		if wresp.Err() != nil {
			return nil, wresp.Err()
		}
		if wresp.Canceled {
			return nil, errors.New("could not get next")
		}
		for _, ev := range wresp.Events {
			service := decode(ev.Kv.Value)
			var action string

			switch ev.Type {
			case clientv3.EventTypePut:
				if ev.IsCreate() {
					action = "create"
				} else if ev.IsModify() {
					action = "update"
				}
			case clientv3.EventTypeDelete:
				action = "delete"

				// get service from prevKv
				service = decode(ev.PrevKv.Value)
			}

View on GitHub (pinned to 24529f1404)

Solutions

  1. Create a new watcher via registry.Watch() and resume watching when this error occurs
  2. Check etcd server logs for compaction or permission errors that canceled the watch
  3. Re-register the service / renew leases so the watched keys stay alive
  4. Wrap Next in a loop that treats this error as a signal to reconnect rather than abort

Example fix

// before
for {
  r, err := w.Next()
  if err != nil { return err } // watcher dies permanently
}
// after
for {
  r, err := w.Next()
  if err != nil {
    time.Sleep(time.Second)
    w, err = registry.Watch() // recreate watcher and continue
    continue
  }
  handle(r)
}
Defensive patterns

Strategy: retry

Try / catch

for {
  r, err := w.Next()
  if err != nil {
    log.Printf("watch broken: %v; restarting", err)
    time.Sleep(backoff)
    w = newWatcher() // recreate after cancellation
    continue
  }
  handle(r)
}

Prevention

When it happens

Trigger: Iterating watcher.Next() while the underlying etcd watch is canceled — etcd returns wresp.Canceled=true (e.g. watch on a compacted revision, permission revoked, or server closing the watch) — and the code returns the error for that response.

Common situations: Long-running watchers surviving etcd cluster restarts or compaction; watching a service whose registration lease expired causing the watch to be torn down; auth token/permission changes mid-watch.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/2bc683b9b6fe944d. Report an issue: GitHub.