grafana/k6 · warning

canceled by client (k6)

Error message

canceled by client (k6)

What it means

k6's gRPC Stream wrapper returns this error when a Receive()/ReceiveConverted() call fails because k6 itself canceled the iteration's context, rather than the server closing the stream. The stream context is canceled when the scenario endTime is reached, the VU iteration is interrupted, or the test is stopped (Ctrl+C / graceful stop). It is distinct from io.EOF, which means the server closed the stream cleanly, and it signals local cancellation, not a server-side failure.

Solutions

  1. Treat ErrCanceled as an expected end-of-stream signal: catch the error from stream.receive() and break the loop when the message matches 'canceled by client (k6)' (in Go: errors.Is(err, grpcext.ErrCanceled)), then call client.close()
  2. Restructure the script so the stream is closed deterministically before the iteration can be interrupted (close right after the receive loop, or in setup/teardown for shared streams)
  3. Give the scenario an explicit finite duration so cancellation happens at a predictable point
  4. Shorten server-side push intervals or iterate per message so iterations end naturally instead of being cut off

Example fix

// before
while (true) {
  const msg = stream.receive(); // throws "canceled by client (k6)" when the iteration is cut off
}
// after
while (true) {
  try {
    const msg = stream.receive();
  } catch (e) {
    if (String(e && e.message).includes('canceled by client (k6)')) break; // expected at shutdown
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Type guard

// k6 script (JS)
function isK6Canceled(e) {
  return !!e && String(e.message || e).includes('canceled by client (k6)');
}
// Go
// var IsCanceled = errors.Is(err, grpcext.ErrCanceled)

Try / catch

Wrap every stream.receive() call in try/catch; treat this error (and io.EOF) as a normal end-of-stream: break the loop, then call client.close() in a finally block so the gRPC stream is released before the iteration ends.

Prevention

When it happens

Trigger: Calling stream.receive() (k6/net/grpc) on a server-streaming or bidirectional RPC after the iteration context has been canceled: the scenario duration expired, the test was stopped with Ctrl+C, or gracefulStop interrupted the iteration while Receive() was pending or before the loop exited.

Common situations: A script opens a server-streaming subscription whose server never closes the stream, so the loop only ends when k6 kills the iteration; tests aborted mid-stream; long-lived bidi channels that outlive the iteration budget.

Related errors


AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18). Data as JSON: /api/errors/9c733a52ab6aa4ef. Report an issue: GitHub.

Appendix: source

Thrown at internal/lib/netext/grpcext/stream.go:30

	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/dynamicpb"
	"google.golang.org/protobuf/types/known/emptypb"
)

// Stream is the wrapper around the grpc.ClientStream
// with some handy methods.
type Stream struct {
	method                 string
	methodDescriptor       protoreflect.MethodDescriptor
	discardResponseMessage bool
	raw                    grpc.ClientStream
	marshaler              protojson.MarshalOptions
	unmarshaler            protojson.UnmarshalOptions
}

// ErrCanceled canceled by client (k6)
var ErrCanceled = errors.New("canceled by client (k6)")

// ReceiveConverted receives a converted message from the stream
// if the stream has been closed successfully, it returns io.EOF
// if the stream has been cancelled, it returns ErrCanceled
func (s *Stream) ReceiveConverted() (any, error) {
	raw, err := s.receive()
	if err != nil && !errors.Is(err, io.EOF) {
		return nil, err
	}

	if s.discardResponseMessage {
		return struct{}{}, err
	}

	msg, errConv := convert(s.marshaler, raw)
	if errConv != nil {
		return nil, errConv
	}

View on GitHub (pinned to 01ffac6f24)