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
- 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()
- 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)
- Give the scenario an explicit finite duration so cancellation happens at a predictable point
- 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
- Never write an unbounded while(true) around receive() without a cancellation exit
- Close gRPC clients/streams explicitly as soon as the receive loop finishes
- Keep long-lived subscriptions in setup() or bound the scenario with an explicit duration so cancellation is predictable
- Treat 'canceled by client (k6)' and 'EOF' as expected outcomes in assertions; only fail on other errors
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
- encrypted pkcs8 formatted key is not supported
- failed to decode PEM key
- gRPC exporter endpoint is required
- grpc protocol does not support URL path
- load must be called in the init context
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)