grafana/k6 · warning

context is done before all '%s' events were processed

Error message

context is done before all '%s' events were processed

What it means

The internal event system (internal/event/system.go) fans each emitted event out to all subscribers and returns a wait function that blocks until every subscriber has signaled done (doneCount == totalSubs). If the context passed to the wait function is cancelled first, it fails with "context is done before all '<type>' events were processed" — typically because the run was aborted (signal, shutdown) while a slow or stuck subscriber was still processing.

Source

Thrown at internal/event/system.go:117

	}

	s.logger.WithFields(logrus.Fields{
		"subscribers": totalSubs,
		"event":       event.Type,
	}).Trace("Emitted event")

	return func(ctx context.Context) error {
		var doneCount int
		for {
			if doneCount == totalSubs {
				close(doneCh)
				return nil
			}
			select {
			case <-doneCh:
				doneCount++
			case <-ctx.Done():
				return fmt.Errorf("context is done before all '%s' events were processed", event.Type)
			}
		}
	}
}

// Unsubscribe closes the Event channel and removes the subscription with ID
// subID.
func (s *System) Unsubscribe(subID uint64) {
	s.subMx.Lock()
	defer s.subMx.Unlock()
	var seen bool
	for _, sub := range s.subscribers {
		if evtCh, ok := sub[subID]; ok {
			if !seen {
				close(evtCh)
			}
			delete(sub, subID)
			seen = true

View on GitHub (pinned to 93accf6570)

Solutions

  1. Make every subscriber non-blocking: drain channels promptly and always signal done, including on error paths
  2. Give the drain step its own timeout context independent of the run context so aborts don't cut off event processing mid-wait
  3. For embedders, treat this error as 'canceled', log it, and continue shutdown rather than failing hard
  4. If observed with stock k6 cloud runs, report the reproduction at https://github.com/grafana/k6/issues

Example fix

// before (embedding k6, same context aborts mid-wait)
wait := events.Emit(ctx, evt)
return wait(ctx)
// after (independent bounded drain context)
wait := events.Emit(ctx, evt)
drainCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := wait(drainCtx); err != nil {
	log.Printf("event drain cancelled: %v", err) // non-fatal
}
Defensive patterns

Strategy: try-catch

Validate before calling

// For embedders: bound the drain wait with a context independent of the run context
drainCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := wait(drainCtx); err != nil {
	log.Printf("event drain cancelled: %v", err) // treat as cancellation, not fatal
}

Try / catch

Go: after wait(ctx), check errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded); treat as a graceful-cancel path — log it and continue shutdown instead of failing the run. Reserve hard failure for subscriber panics.

Prevention

When it happens

Trigger: Emitting an event (e.g. the execution/wakeup events used by the cloud output) and waiting on the run context, which gets cancelled by an abort; a subscriber that blocks in its handler or never drains its channel so doneCount never reaches totalSubs before ctx.Done() fires.

Common situations: Cloud-output runs aborted mid-flight while the event pipeline still has buffered work; embedding k6 as a library and subscribing to the event System without promptly consuming the event channel.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/fc054f85a806f55f. Report an issue: GitHub.