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

  1. Check connection.close_reason() / the open_bi ConnectionError for the real cause
  2. Verify the StreamHandlerFactory accepts the given ALPN protocol and doesn't immediately close
  3. Ensure the QuinnServer handle is kept alive for the whole test (Harness stores _server)
  4. 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

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


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)