Skip to content

Commit aa75e5c

Browse files
Nakakiyo092claude
andcommitted
Honor --timeout precisely with non-blocking recv polling (#42)
The wait loop polled every rx_stack with recv(block=True, timeout=0.01). With ~249 stacks per sweep (11bits functional, 29bits broadcast) the user-supplied --timeout was effectively rounded up to a multiple of ~2.5 seconds; --timeout 1 did not bound the wait at one second. Switch to recv(block=False) with a 1 ms sleep between sweeps. An empty sweep over 249 stacks now takes microseconds and the deadline is honored within ~1 ms granularity. No semantic change to message handling: recv(block=False) returns exactly the same payloads as the blocking form when data is available. Also: time.time() -> time.monotonic() for the duration measurement (immune to wall-clock adjustments), drop the now-redundant `waiting` sentinel in favor of `payload is None`, and remove the `# TODO: #42` marker now that this change resolves it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent fbab757 commit aa75e5c

1 file changed

Lines changed: 19 additions & 23 deletions

File tree

src/reader.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -267,30 +267,26 @@ def _read_did(did, tx_stack, rx_stacks, addr_type, mode_label,
267267
tx_stack.send(request.get_payload(), addr_type)
268268

269269
try:
270-
# Wait for response
271-
waiting = True
272-
start_time = time.time()
273-
while waiting:
274-
# Response timeout
275-
if time.time() - start_time > timeout:
276-
payload = None
277-
break
278-
279-
# Check response for all stacks
270+
# Wait for response.
271+
# Non-blocking recv(): a sweep over hundreds of rx_stacks takes microseconds
272+
# when empty, so the user-supplied timeout is honored within ~1 ms granularity.
273+
# monotonic() is immune to wall-clock adjustments mid-wait.
274+
payload = None
275+
deadline = time.monotonic() + timeout
276+
while payload is None and time.monotonic() < deadline:
280277
for rx_stack in rx_stacks:
281-
# TODO: #42
282-
payload = rx_stack.recv(block=True, timeout=0.01)
283-
if payload is not None:
284-
# Compare only the header portion of the received payload against
285-
# the expected positive-response bytes. The remainder is data.
286-
if payload[:len(response)] == response.get_payload():
287-
# Positive response
288-
waiting = False
289-
break
290-
else:
291-
# No Negative response handling. See the DESIGN.md.
292-
pass
293-
278+
received = rx_stack.recv(block=False)
279+
if received is None:
280+
continue
281+
# Compare only the header portion of the received payload against
282+
# the expected positive-response bytes. The remainder is data.
283+
if received[:len(response)] == response.get_payload():
284+
# Positive response
285+
payload = received
286+
break
287+
# Non-matching payload (e.g., negative response). See DESIGN.md.
288+
if payload is None:
289+
time.sleep(0.001)
294290
except Exception as err:
295291
print(err)
296292
return None

0 commit comments

Comments
 (0)