Skip to content

Commit cb8215d

Browse files
acul71cursoragent
andauthored
fix(quic): satisfy mypy 2.2 comparison-overlap in stream read loop (#1374)
Use is_reset() in the QUICStream.read() wait loop so concurrent peer resets remain detectable without tripping mypy 2.2 comparison-overlap after the entry state guard. Fixes #1373 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent fd4bf0b commit cb8215d

3 files changed

Lines changed: 77 additions & 1 deletion

File tree

libp2p/transport/quic/stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ async def read(self, n: int | None = None) -> bytes:
251251
return data
252252

253253
# Check if stream was reset (unblocking read loop)
254-
if self._state == StreamState.RESET:
254+
if self.is_reset():
255255
raise QUICStreamResetError(
256256
f"Stream {self.stream_id} was reset"
257257
)

newsfragments/1373.internal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed CI lint failure on ``main`` caused by mypy 2.2 ``comparison-overlap`` in the QUIC stream read loop. Reads blocked on ``_receive_event`` still raise ``QUICStreamResetError`` when the stream is reset concurrently.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Unit tests for QUICStream behavior."""
2+
3+
from unittest.mock import Mock
4+
5+
import pytest
6+
from multiaddr.multiaddr import Multiaddr
7+
import trio
8+
from trio.testing import wait_all_tasks_blocked
9+
10+
from libp2p.crypto.ed25519 import create_new_key_pair
11+
from libp2p.peer.id import ID
12+
from libp2p.transport.quic.config import QUICTransportConfig
13+
from libp2p.transport.quic.connection import QUICConnection
14+
from libp2p.transport.quic.exceptions import QUICStreamResetError
15+
from libp2p.transport.quic.stream import QUICStream, StreamDirection
16+
17+
18+
@pytest.fixture
19+
def quic_connection() -> QUICConnection:
20+
mock_quic = Mock()
21+
mock_quic.next_event.return_value = None
22+
mock_quic.datagrams_to_send.return_value = []
23+
mock_quic.get_timer.return_value = None
24+
mock_quic.connect = Mock()
25+
mock_quic.close = Mock()
26+
mock_quic.send_stream_data = Mock()
27+
mock_quic.reset_stream = Mock()
28+
29+
mock_transport = Mock()
30+
mock_transport._config = QUICTransportConfig()
31+
32+
private_key = create_new_key_pair().private_key
33+
peer_id = ID.from_pubkey(private_key.get_public_key())
34+
35+
return QUICConnection(
36+
quic_connection=mock_quic,
37+
remote_addr=("127.0.0.1", 4001),
38+
remote_peer_id=None,
39+
local_peer_id=peer_id,
40+
is_initiator=True,
41+
maddr=Multiaddr("/ip4/127.0.0.1/udp/4001/quic"),
42+
transport=mock_transport,
43+
resource_scope=None,
44+
security_manager=Mock(),
45+
)
46+
47+
48+
@pytest.mark.trio
49+
async def test_read_raises_reset_error_when_reset_during_wait(
50+
quic_connection: QUICConnection,
51+
) -> None:
52+
"""read() must detect peer reset while blocked on _receive_event."""
53+
stream = QUICStream(
54+
connection=quic_connection,
55+
stream_id=1,
56+
direction=StreamDirection.INBOUND,
57+
remote_addr=("127.0.0.1", 4001),
58+
)
59+
60+
reset_error: QUICStreamResetError | None = None
61+
62+
async def read_and_capture() -> None:
63+
nonlocal reset_error
64+
try:
65+
await stream.read()
66+
except QUICStreamResetError as exc:
67+
reset_error = exc
68+
69+
async with trio.open_nursery() as nursery:
70+
nursery.start_soon(read_and_capture)
71+
await wait_all_tasks_blocked()
72+
await stream.handle_reset(error_code=1)
73+
74+
assert reset_error is not None
75+
assert "reset" in str(reset_error).lower()

0 commit comments

Comments
 (0)