Visitar URL original
🐛 Give async flush_all a deadline and chain the cause of a failed read · aisk/python-memcache@68ef79d · GitHub
Skip to content

Commit 68ef79d

Browse files
committed
🐛 Give async flush_all a deadline and chain the cause of a failed read
1 parent 8a30d43 commit 68ef79d

4 files changed

Lines changed: 43 additions & 4 deletions

File tree

‎memcache/experiment/async_meta_client.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,13 @@ async def execute(self, command: MetaCommand, timeout: float | None) -> MetaResu
128128
with anyio.fail_after(timeout):
129129
return await connection.execute_meta_command(command)
130130

131-
async def flush(self, delay: int) -> None:
131+
async def flush(self, delay: int, timeout: float | None) -> None:
132132
async with self._borrow() as connection:
133-
await connection.flush_all(delay)
133+
if timeout is None:
134+
await connection.flush_all(delay)
135+
return
136+
with anyio.fail_after(timeout):
137+
await connection.flush_all(delay)
134138

135139
async def close(self) -> None:
136140
self._closed = True
@@ -735,4 +739,4 @@ async def flush_all(self, delay: int = 0) -> None:
735739
positive("delay", delay)
736740
async with anyio.create_task_group() as tasks:
737741
for server in self._servers:
738-
tasks.start_soon(server.flush, delay)
742+
tasks.start_soon(server.flush, delay, self.default_timeout)

‎memcache/experiment/result.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,13 @@ def __init__(
135135
@property
136136
def value(self) -> T:
137137
if self.status is not GetStatus.HIT:
138+
# Chained so a FAILED result surfaces the ConnectionRefusedError or
139+
# TimeoutError behind it. Without this the traceback reads like a
140+
# usage mistake and the real cause is lost exactly when it matters.
138141
raise ResultValueError(
139142
"key %r has no value to read: %s"
140143
% (self.key, _NO_VALUE_REASONS.get(self.status, "the read failed"))
141-
)
144+
) from self.error
142145
if self._value is _NO_VALUE:
143146
raise ResultValueError(
144147
"key %r was a hit but the read did not ask for the value; "

‎tests/test_async_meta_client.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,24 @@ async def test_batch_spanning_chunks_maps_every_response(client, monkeypatch):
237237
results = await client.batch([Get("achunk-%d" % i) for i in range(40)])
238238
assert [r.status for r in results] == [GetStatus.HIT] * 40
239239
assert [r.value for r in results] == ["v%d" % i for i in range(40)]
240+
241+
242+
@pytest.mark.asyncio
243+
async def test_flush_all_honours_the_client_timeout():
244+
"""flush_all used to have no deadline at all and could hang forever."""
245+
import socket
246+
import threading
247+
248+
listener = socket.socket()
249+
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
250+
listener.bind(("127.0.0.1", 0))
251+
listener.listen(4)
252+
port = listener.getsockname()[1]
253+
threading.Thread(
254+
target=lambda: [listener.accept() for _ in range(4)], daemon=True
255+
).start()
256+
257+
silent = AsyncMetaClient(("127.0.0.1", port), timeout=0.3)
258+
with pytest.raises(TimeoutError):
259+
await silent.flush_all()
260+
listener.close()

‎tests/test_meta_client.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,17 @@ def send(self, commands, timeout=None):
211211
assert all(r.status is not MutationStatus.AMBIGUOUS for r in settled)
212212

213213

214+
def test_failed_result_value_chains_the_real_error():
215+
"""Reading .value on a FAILED result must not hide why it failed."""
216+
client = MetaClient(("localhost", 1), timeout=0.3)
217+
result = client.batch([Get("k")])[0]
218+
assert isinstance(result.error, OSError)
219+
with pytest.raises(ResultValueError) as info:
220+
result.value
221+
assert info.value.__cause__ is result.error
222+
client.close()
223+
224+
214225
def test_server_failure_is_isolated_in_batch():
215226
client = MetaClient([("localhost", 11211), ("localhost", 1)], timeout=0.2)
216227
good = bad = None

0 commit comments

Comments
 (0)