Skip to content

fix(gun): map connection-level gun errors to internal status#548

Merged
polvalente merged 2 commits into
elixir-grpc:masterfrom
smartinio:smartinio/gun-connection-errors
Jun 30, 2026
Merged

fix(gun): map connection-level gun errors to internal status#548
polvalente merged 2 commits into
elixir-grpc:masterfrom
smartinio:smartinio/gun-connection-errors

Conversation

@smartinio

@smartinio smartinio commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Problem

The Gun adapter reports some connection errors as GRPC.Status.unknown().

For example:

  1. Gun receives an HTTP/2 connection error, e.g. a preface timeout.
  2. Gun sends {:gun_error, conn_pid, reason} to the stream response process.
  3. StreamResponseProcess treats it as an unexpected message.
  4. The Gun adapter maps it to GRPC.Status.unknown() instead of a connection error.

UNKNOWN is the wrong status here because the adapter has already received a concrete Gun connection error. Exposing it as UNKNOWN loses that information and makes the failure look like an unexpected adapter message instead of a transport error. Userland code handling gRPC errors then only sees opaque status 2, even though Gun reported a connection-level failure. In our case we decide if a request is retriable based on this, and would not want to broaden it to UNKNOWN unnecessarily.

Root cause

StreamResponseProcess handles stream-level Gun errors with a stream ref:

{:gun_error, conn_pid, stream_ref, reason}

but not connection-level Gun errors without a stream ref:

{:gun_error, conn_pid, reason}

The connection-level message falls through to unexpected_message, even though Gun's own await helpers classify the same message as connection_error.

Fix

Keep the fix scoped to the Gun adapter.

  • Treat connection-level Gun errors as {:connection_error, reason}
  • Keep stream-level Gun errors mapped as {:stream_error, reason}
  • Ignore later terminal messages after the stream response process has already accepted a terminal message

With this change, connection-level Gun errors returned through reply_to map to GRPC.Status.internal() instead of GRPC.Status.unknown().

Test plan

  • Added regression test for connection-level Gun errors delivered before await/2
  • Added regression test for connection-level Gun errors delivered while await/2 is waiting
  • Added adapter-level test that checks connection-level Gun errors become internal RPC errors
  • Added tests for duplicate terminal messages and terminal trailers followed by connection errors

@polvalente polvalente left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution (with thorough tests!)

@sleipnir I believe we should treat this as a simple bugfix instead of a breaking change even if there are implicit contract changes (and users can easily handle the new response as needed)

@polvalente polvalente merged commit ef43867 into elixir-grpc:master Jun 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants