Skip to content

Commit 42839fc

Browse files
authored
Reraise ConnectionLostError on uvloop caused RuntimeError when writing heartbeat (#28)
1 parent 7c58157 commit 42839fc

File tree

6 files changed

+49
-9
lines changed

6 files changed

+49
-9
lines changed

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
path: ~/.cache/uv
1919
key: publish-${{ hashFiles('pyproject.toml') }}
2020
- uses: extractions/setup-just@v2
21-
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
21+
- run: curl -LsSf https://astral.sh/uv/0.2.11/install.sh | sh
2222
- run: just publish
2323
env:
2424
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
with:
2727
path: ~/.cache/uv
2828
key: check-types-${{ hashFiles('pyproject.toml') }}
29-
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
29+
- run: curl -LsSf https://astral.sh/uv/0.2.11/install.sh | sh
3030
- uses: extractions/setup-just@v2
3131
- run: just install check-types
3232

@@ -41,7 +41,7 @@ jobs:
4141
with:
4242
path: ~/.cache/uv
4343
key: lint-${{ hashFiles('pyproject.toml') }}
44-
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
44+
- run: curl -LsSf https://astral.sh/uv/0.2.11/install.sh | sh
4545
- uses: extractions/setup-just@v2
4646
- run: just install lint
4747

@@ -62,7 +62,7 @@ jobs:
6262
with:
6363
path: ~/.cache/uv
6464
key: ${{ github.job }}-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
65-
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
65+
- run: curl -LsSf https://astral.sh/uv/0.2.11/install.sh | sh
6666
- uses: extractions/setup-just@v2
6767
- run: just install test -vv
6868

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ ARG PYTHON_VERSION
22
FROM python:${PYTHON_VERSION}-slim-bullseye
33

44
# hadolint ignore=DL3013, DL3042
5-
RUN --mount=type=cache,target=~/.cache/pip pip install uv
5+
RUN --mount=type=cache,target=~/.cache/pip pip install uv==0.2.11
66

77
WORKDIR /app
88

stompman/connection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ async def close(self) -> None:
5656
await self.writer.wait_closed()
5757

5858
def write_heartbeat(self) -> None:
59-
return self.writer.write(NEWLINE)
59+
with _reraise_connection_lost(RuntimeError):
60+
return self.writer.write(NEWLINE)
6061

6162
async def write_frame(self, frame: AnyClientFrame) -> None:
6263
with _reraise_connection_lost(RuntimeError):

tests/integration.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import asyncio
22
import os
3+
from collections.abc import AsyncGenerator
4+
from contextlib import asynccontextmanager
35
from uuid import uuid4
46

57
import pytest
@@ -49,7 +51,35 @@ async def consume() -> None:
4951
task_group.create_task(produce())
5052

5153

52-
async def test_raises_connection_lost_error(server: stompman.ConnectionParameters) -> None:
54+
@asynccontextmanager
55+
async def closed_client(server: stompman.ConnectionParameters) -> AsyncGenerator[stompman.Client, None]:
56+
async with stompman.Client(servers=[server], read_timeout=10, connection_confirmation_timeout=10) as client:
57+
await client._connection.close()
58+
yield client
59+
60+
61+
async def test_raises_connection_lost_error_in_aexit(server: stompman.ConnectionParameters) -> None:
62+
with pytest.raises(ConnectionLostError):
63+
async with closed_client(server):
64+
pass
65+
66+
67+
async def test_raises_connection_lost_error_in_write_frame(server: stompman.ConnectionParameters) -> None:
68+
client = await closed_client(server).__aenter__() # noqa: PLC2801
69+
70+
with pytest.raises(ConnectionLostError):
71+
await client._connection.write_frame(stompman.ConnectFrame(headers={"accept-version": "", "host": ""}))
72+
73+
with pytest.raises(ConnectionLostError):
74+
await client.__aexit__(None, None, None)
75+
76+
77+
@pytest.mark.parametrize("anyio_backend", [("asyncio", {"use_uvloop": True})])
78+
async def test_raises_connection_lost_error_in_write_heartbeat(server: stompman.ConnectionParameters) -> None:
79+
client = await closed_client(server).__aenter__() # noqa: PLC2801
80+
81+
with pytest.raises(ConnectionLostError):
82+
client._connection.write_heartbeat()
83+
5384
with pytest.raises(ConnectionLostError):
54-
async with stompman.Client(servers=[server], read_timeout=10, connection_confirmation_timeout=10) as consumer:
55-
await consumer._connection.close()
85+
await client.__aexit__(None, None, None)

tests/test_connection.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ class MockWriter:
108108
await connection.close()
109109

110110

111+
async def test_connection_write_heartbeat_runtime_error(monkeypatch: pytest.MonkeyPatch) -> None:
112+
class MockWriter:
113+
write = mock.Mock(side_effect=RuntimeError)
114+
115+
connection = await make_mocked_connection(monkeypatch, mock.Mock(), MockWriter())
116+
with pytest.raises(ConnectionLostError):
117+
connection.write_heartbeat()
118+
119+
111120
async def test_connection_write_frame_connection_error(monkeypatch: pytest.MonkeyPatch) -> None:
112121
class MockWriter:
113122
write = mock.Mock()

0 commit comments

Comments
 (0)