Skip to content

fix(quic): close inbound connection when handler rejects it#499

Merged
lucassaldanha merged 2 commits into
libp2p:developfrom
lucassaldanha:investigate-peer-warn
Jun 24, 2026
Merged

fix(quic): close inbound connection when handler rejects it#499
lucassaldanha merged 2 commits into
libp2p:developfrom
lucassaldanha:investigate-peer-warn

Conversation

@lucassaldanha

@lucassaldanha lucassaldanha commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Problem

Teku nodes log a flood of noisy warnings on the QUIC inbound path:

An exceptionCaught() event was fired, and it reached at the tail of the pipeline...
tech.pegasys.teku.networking.p2p.libp2p.PeerAlreadyConnectedException: Already connected to peer ...
    at ...PeerManager.onConnectedPeer
    at ...BroadcastConnectionHandler.handleConnection
    at io.libp2p.transport.quic.QuicTransport$...channelActive$4.invoke(QuicTransport.kt:531)
    at io.libp2p.transport.quic.QuicTransport.routeInboundHandshake$libp2p(QuicTransport.kt:135)
    at io.libp2p.transport.quic.QuicTransport$...channelActive(QuicTransport.kt:506)

Problem/Fix

When the handler rejects the connection by throwing PeerAlreadyConnectedException this is ok for a duplicate connection to an already-connected peer. We just need to handle it better.

This is already handled in a similar way for QUIC dialing (outbound). And in the TCP path as well.

The inbound QUIC server path invokes the application connection handler
synchronously inside channelActive. When the handler rejects the
connection by throwing (e.g. PeerAlreadyConnectedException for a
duplicate/simultaneous peer), the handshake-waiter handler has already
removed itself, so the exception reaches the Netty pipeline tail and
logs a noisy 'exceptionCaught reached tail of pipeline' warning while
leaking the rejected QuicChannel until idle timeout.

Catch the exception in routeInboundHandshake and close the channel,
mirroring the dial paths which already close on handler failure.
Narrow the inbound rejection guard from catch(Throwable) to
catch(Exception) so fatal Errors (OutOfMemoryError, LinkageError, ...)
propagate instead of being downgraded to a routine connection close.
The library cannot distinguish a deliberate rejection from an
application bug, so logging stays at debug to avoid reintroducing noise.
@lucassaldanha lucassaldanha merged commit 31e0e98 into libp2p:develop Jun 24, 2026
2 checks passed
@lucassaldanha lucassaldanha deleted the investigate-peer-warn branch June 24, 2026 01:22
lucassaldanha added a commit that referenced this pull request Jun 24, 2026
The inbound QUIC server path invokes the application connection handler
synchronously inside channelActive. When the handler rejects the
connection by throwing (e.g. PeerAlreadyConnectedException for a
duplicate/simultaneous peer), the handshake-waiter handler has already
removed itself, so the exception reaches the Netty pipeline tail and
logs a noisy 'exceptionCaught reached tail of pipeline' warning while
leaking the rejected QuicChannel until idle timeout.

Catch the exception in routeInboundHandshake and close the channel,
mirroring the dial paths which already close on handler failure. Narrow
the guard to catch(Exception) rather than Throwable so fatal Errors
(OutOfMemoryError, LinkageError, ...) propagate instead of being
downgraded to a routine connection close; logging stays at debug.
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.

1 participant