diff --git a/smartthings_local/protocol/dtls_session.py b/smartthings_local/protocol/dtls_session.py index 4b88153..5c09dc2 100644 --- a/smartthings_local/protocol/dtls_session.py +++ b/smartthings_local/protocol/dtls_session.py @@ -104,9 +104,9 @@ _BLOCK_MAX_ATTEMPTS = 3 _BLOCK_ACK_TIMEOUT = 4.0 -# How often a block wait re-checks that the reader is still alive. Short -# enough that a mid-transfer reader death fails fast instead of burning -# the whole per-block timeout, long enough to stay off the CPU. +# How often a request wait re-checks that the reader is still alive. Short +# enough that a mid-exchange reader death fails fast instead of burning +# the whole per-attempt timeout, long enough to stay off the CPU. _BLOCK_LIVENESS_POLL_S = 0.25 # Inter-request pacing: minimum seconds between CoAP CON sends on one session. @@ -1095,7 +1095,7 @@ def _exchange_block(self, tok, path_segs, query, num, szx, deadline): remaining if acknowledged else min(_BLOCK_ACK_TIMEOUT, max(0.1, remaining)) ) - if not self._wait_for_block(ev, per_wait): + if not self._wait_live(ev, per_wait): if acknowledged: raise SessionTimeoutError() break @@ -1115,8 +1115,16 @@ def _exchange_block(self, tok, path_segs, query, num, szx, deadline): self._unregister_pending_request(tok, mid, exchange) raise SessionTimeoutError() - def _wait_for_block(self, ev, per_wait): - """Wait for a block response while checking reader liveness.""" + def _wait_live(self, ev, per_wait): + """Wait for one response, giving up early if the reader dies + underneath us. + + Only the reader thread can resolve a token, so once it is gone + the wait can never succeed. Polling in slices turns what would + be a full per-attempt timeout into an immediate SessionClosedError, + which is the same fail-fast contract get() and post() get from + _check_live() at entry — it just has to hold for the whole + exchange, not only its first moment.""" deadline = time.monotonic() + per_wait while True: slice_s = min( diff --git a/tests/test_dtls_session_mid_registry.py b/tests/test_dtls_session_mid_registry.py index b0cf6a5..3a1ed31 100644 --- a/tests/test_dtls_session_mid_registry.py +++ b/tests/test_dtls_session_mid_registry.py @@ -111,7 +111,7 @@ def send(datagram): requests.append((mid, token)) session._dispatch_coap(build_coap(TYPE_ACK, 0, mid, b"", [])) - def wait_for_block(_event, timeout): + def wait_live(_event, timeout): waits.append(timeout) mid, token = requests[-1] session._dispatch_coap( @@ -120,7 +120,7 @@ def wait_for_block(_event, timeout): return True session._send_dgram = send - session._wait_for_block = wait_for_block + session._wait_live = wait_live assert session.get(["device", "0"], timeout=1.0) == (0x45, b"ok") assert len(requests) == 1 @@ -215,7 +215,7 @@ def fail_send(_datagram): else: session._send_dgram = lambda _datagram: None if operation == "get": - session._wait_for_block = lambda _event, _timeout: False + session._wait_live = lambda _event, _timeout: False expected = SessionTimeoutError timeout = 0.0 diff --git a/tests/test_dtls_session_reader_death.py b/tests/test_dtls_session_reader_death.py index 1fe3bed..388cbb9 100644 --- a/tests/test_dtls_session_reader_death.py +++ b/tests/test_dtls_session_reader_death.py @@ -460,7 +460,7 @@ def respond(datagram): sess._dispatch_coap(build_empty_ack(mid)) pending['token'] = token - def wait_for_block(_event, per_wait): + def wait_live(_event, per_wait): waits.append(per_wait) token = pending['token'] if len(waits) == 1: @@ -486,7 +486,7 @@ def wait_for_block(_event, per_wait): return True sess._send_dgram = respond - sess._wait_for_block = wait_for_block + sess._wait_live = wait_live sess.pace = lambda: None assert sess.get(['oic', 'res'], timeout=0.2) == ( 0x45, diff --git a/tests/test_mid_registry_retransmission.py b/tests/test_mid_registry_retransmission.py index b96740f..ef30c1c 100644 --- a/tests/test_mid_registry_retransmission.py +++ b/tests/test_mid_registry_retransmission.py @@ -61,7 +61,7 @@ def send(datagram): # Answer only once the third attempt is on the wire, so two full # retransmissions have to happen first. - def wait_for_block(_event, _timeout): + def wait_live(_event, _timeout): if len(sent) < 3: return False _mtype, _code, mid, token, _options, _payload = parse_coap(sent[-1]) @@ -70,7 +70,7 @@ def wait_for_block(_event, _timeout): ) return True - session._wait_for_block = wait_for_block + session._wait_live = wait_live assert session.get(["device", "0"], timeout=5.0) == (0x45, b"ok") @@ -85,7 +85,7 @@ def wait_for_block(_event, _timeout): def test_exhausted_attempts_still_release_the_single_registration(): session = _session() session._send_dgram = lambda _datagram: None - session._wait_for_block = lambda _event, _timeout: False + session._wait_live = lambda _event, _timeout: False with pytest.raises(SessionTimeoutError): session.get(["device", "0"], timeout=0.05)