EpicGames/lore · error
Failed to setup bidirectional channel
Error message
Failed to setup bidirectional channel
What it means
This expect wraps connection.open_bi() on the freshly established client connection in serve_and_connect. Failing here means the connection was not actually usable for streams — the handshake raced a server-side close, timed out, or was reset.
Solutions
- Check connection.close_reason() / the open_bi ConnectionError for the real cause
- Verify the StreamHandlerFactory accepts the given ALPN protocol and doesn't immediately close
- Ensure the QuinnServer handle is kept alive for the whole test (Harness stores _server)
- Confirm server transport config allows bidirectional streams (max_concurrent_bidi_streams > 0)
Example fix
// before
let (send, recv) = connection.open_bi().await.expect("Failed to setup bidirectional channel");
// after
let (send, recv) = connection.open_bi().await
.unwrap_or_else(|e| panic!("Failed to setup bidirectional channel: {e:?} (reason: {:?})", connection.close_reason())); Defensive patterns
Strategy: try-catch
Validate before calling
// after connect(), before open_bi
assert!(connection.close_reason().is_none(), "connection died during handshake: {:?}", connection.close_reason());
assert_eq!(connection.handshake_data().map(|d| d.protocol).unwrap_or_default(), protocol.as_bytes()); Type guard
fn handshake_ok(conn: &quinn::Connection, alpn: &[u8]) -> bool {
conn.handshake_data().map(|d| d.protocol == alpn.to_vec()).unwrap_or(false)
} Try / catch
match connection.open_bi().await {
Ok(pair) => pair,
Err(e) => panic!("Failed to setup bidirectional channel: {e:?} (reason: {:?})", connection.close_reason()),
} Prevention
- Assert ALPN and handshake data before opening streams
- Keep the QuinnServer handle alive for the entire test (Harness._server)
- Ensure the StreamHandlerFactory accepts the test protocol rather than closing on accept
- Verify server transport config permits bidirectional streams
When it happens
Trigger: Server dropped the connection immediately after accepting (factory/handler panic at accept), TLS handshake failed despite connect() resolving, idle/timeout transport error before streams could open, or server max concurrent streams is 0.
Common situations: ALPN negotiated but the StreamHandlerFactory rejects the protocol and closes the connection; server cert issues surfacing after connect; test racing server shutdown because the QuinnServer handle was dropped; transport config with zero bidirectional stream limits.
Related errors
- Failed to open additional stream
- Failed to create client endpoint
- Failed to write header
- Failed flush
- Failed to read response
AI-assisted analysis of EpicGames/lore@074eb0b0d1 (2026-09-13).
Data as JSON: /api/errors/95c1ba29c826df15.
Report an issue: GitHub.
Appendix: source
Thrown at lore-server/src/quic/stream_handler.rs:908
crypto_config.alpn_protocols = vec![protocol.as_bytes().into()];
let client_config = ClientConfig::new(Arc::new(
QuicClientConfig::try_from(crypto_config).expect("Failed client config"),
));
let client_addr: SocketAddr = "0.0.0.0:0".parse().unwrap();
let mut endpoint = Endpoint::client(client_addr).expect("Failed to create client endpoint");
endpoint.set_default_client_config(client_config);
let connection = endpoint
.connect(server_addr, "localhost")
.unwrap()
.await
.unwrap();
let (send, recv) = connection
.open_bi()
.await
.expect("Failed to setup bidirectional channel");
Harness {
send,
recv,
connection,
_server: server,
_endpoint: endpoint,
}
}
const MOCK_PROTOCOL: &str = "mock-test/0.1";
const MOCK_MAX_CHUNK: usize = 4096;
/// How long [`MockBehaviour::Block`] holds a permit: longer than any test runs, and unrelated
/// to the timeouts under test so changing one does not move the other.
const BLOCKING_HANDLER_SLEEP: Duration = Duration::from_secs(3600);
/// Time a refusal that involves no waiting is allowed to take, with room for a loaded host.View on GitHub (pinned to 074eb0b0d1)