-
Notifications
You must be signed in to change notification settings - Fork 114
Description
Problem
ConnectionError::is_h3_no_error() currently only checks for H3-layer NO_ERROR, but misses QUIC-layer NO_ERROR closes that are wrapped in the Undefined variant. This causes graceful connection closes to be logged/handled as errors.
Real-World Impact
When using h3-quinn, normal client disconnects (e.g., from curl) are logged as warnings instead of debug messages:
⚠️ H3 connection closed with error: Remote(Undefined(ConnectionClosed(
ConnectionClose { error_code: NO_ERROR, frame_type: None, reason: b"" }
))) ip:127.0.0.1Notice error_code: NO_ERROR - this is a graceful close, not an error.
Current Implementation
ConnectionError::is_h3_no_error() in h3/src/error/error.rs:
pub fn is_h3_no_error(&self) -> bool {
match self {
ConnectionError::Local { error: LocalError::Application { code: Code::H3_NO_ERROR, .. } } => true,
ConnectionError::Remote(ConnectionErrorIncoming::ApplicationClose { error_code })
if *error_code == Code::H3_NO_ERROR.value() => true,
_ => false, // ← Undefined variant falls through!
}
}This only checks ApplicationClose, not Undefined which contains QUIC-layer errors.
Related Context
PR #315 fixed the same issue for StreamError::is_h3_no_error() by checking the RemoteTerminate variant. This is the connection-level equivalent.
The Challenge
The Undefined variant contains a trait object:
ConnectionErrorIncoming::Undefined(Arc<dyn std::error::Error + Send + Sync>)The error code is buried inside the boxed error (e.g., quinn::ConnectionError::ConnectionClosed(ConnectionClose { error_code: ... })), but:
- The concrete type is erased (trait object)
- Fields are not accessible through the
Errortrait - h3 is transport-agnostic (can't depend on quinn)
Possible Approaches
I can see a few options, but I'm not sure which aligns with h3's design philosophy:
-
String parsing (pragmatic but fragile):
ConnectionError::Remote(ConnectionErrorIncoming::Undefined(err)) => { format!("{:?}", err).contains("NO_ERROR") }
-
Architectural change: Expose error code in
ConnectionErrorIncomingstructure -
Trait method: Add a method that QUIC implementations can use to signal clean close
-
Something better I haven't thought of?
Question
How should this be properly solved? I'm happy to implement a fix following your guidance.
Thanks!