-
Notifications
You must be signed in to change notification settings - Fork 190
Description
Summary
Python yamux has a listener-specific bug when handling incoming connections, specifically with TCP transport. The bug manifests as an IncompleteReadError in the handle_incoming method after successful connection establishment and ping/pong exchange.
Evidence
Test Results Pattern
| Test | Direction | Transport | Result |
|---|---|---|---|
jvm-v1.2 x python-v0.4 (tcp, noise, yamux) |
JVM dialer β Python listener | TCP | β FAILS |
python-v0.4 x jvm-v1.2 (tcp, noise, yamux) |
Python dialer β JVM listener | TCP | β SUCCEEDS |
Key Observations:
- Python yamux listener (TCP): 1/1 failures (100%)
- Python yamux dialer (TCP): 0/1 failures (0%)
- Mplex works in both directions, confirming this is yamux-specific
Error Details
Error Location: libp2p/stream_muxer/yamux/yamux.py - handle_incoming method
Error Message:
Error in handle_incoming for peer <peer_id>: Transport: unknown. IncompleteReadError: Connection closed during read operation: expected 2 bytes but received 0 bytes. This may indicate the peer closed the connection prematurely, a network issue, or a transport-specific problem (e.g., WebSocket message boundary handling).
Sequence of Events:
- β Connection established successfully
- β Noise handshake completes
- β Ping/pong exchange succeeds
- β Error occurs in
handle_incoming(yamux frame parsing)- Error:
IncompleteReadError: expected 2 bytes but received 0 bytes - This happens after the ping/pong succeeds
- Suggests yamux is trying to read the next frame header but the connection is closed
- Error:
Root Cause Hypothesis
The error occurs in Python's yamux handle_incoming method when it tries to read the next yamux frame header (2 bytes). The connection appears to be closed prematurely, possibly because:
- The peer closes the connection after sending the pong, but Python yamux listener is still trying to read the next frame
- Python yamux listener doesn't properly handle the case where the connection is closed while waiting for the next frame
- Frame boundary issue: The yamux frame header read fails because the connection was closed at the wrong time
The fact that it works when Python is the dialer suggests the issue is specific to how Python yamux handles incoming connections (listener side) vs outgoing connections (dialer side).
Investigation Recommendations
1. Check Python Yamux Listener Implementation
File: libp2p/stream_muxer/yamux/yamux.py
Focus Areas:
- Incoming stream handling when Python is listener
- Yamux frame parsing for incoming connections
- Stream initialization for inbound streams
- Error handling during listener operations
2. Compare with Working Dialer Implementation
Since Python yamux dialer works correctly:
- Compare dialer vs listener code paths
- Identify differences in stream initialization
- Check for missing error handling in listener path
3. Test with Other Implementations
To confirm if this is implementation-specific or general:
- Test Python yamux listener with other implementations (Go, JS, Nim, Rust v0.54+)
- If it fails with multiple implementations, it's a Python yamux listener bug
- If it only fails with specific implementations, it may be a compatibility issue
Logs
Full logs are available from the transport-interop test suite:
- Failing test:
jvm-v1.2 x python-v0.4 (tcp, noise, yamux) - Successful test:
python-v0.4 x jvm-v1.2 (tcp, noise, yamux)
Priority
Medium-High
- Not blocking (only affects specific implementation combinations)
- But fixing would improve Python yamux reliability
- Could prevent similar issues with other implementations
Related
This issue was discovered during interoperability testing. Given yamux's buggy history in py-libp2p, this listener bug should be investigated and fixed.