Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions smartthings_local/protocol/dtls_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions tests/test_dtls_session_mid_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions tests/test_dtls_session_reader_death.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions tests/test_mid_registry_retransmission.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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")

Expand All @@ -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)
Expand Down