Skip to content

Commit d39fbc9

Browse files
committed
fix(haos): distinguish queued websocket sends
1 parent 25deea4 commit d39fbc9

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

tests/haos_image_build/build_image.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,10 +776,16 @@ def _send_with_deadline(
776776
raise ConnectionError(f"WebSocket is not connected for {operation}")
777777
_remaining_deadline_budget(deadline, operation)
778778
send_errors: list[Exception] = []
779+
dispatch_lock = threading.Lock()
780+
send_state = {"cancelled": False, "started": False}
779781

780782
def send() -> None:
781783
try:
782-
_remaining_deadline_budget(deadline, operation)
784+
with dispatch_lock:
785+
if send_state["cancelled"]:
786+
return
787+
_remaining_deadline_budget(deadline, operation)
788+
send_state["started"] = True
783789
connection.send(message)
784790
except Exception as exc:
785791
send_errors.append(exc)
@@ -788,6 +794,12 @@ def send() -> None:
788794
worker.start()
789795
worker.join(max(0.0, deadline - time.monotonic()))
790796
if worker.is_alive():
797+
with dispatch_lock:
798+
if not send_state["started"]:
799+
send_state["cancelled"] = True
800+
raise TimeoutError(
801+
f"{operation} exceeded its deadline before dispatch"
802+
)
791803
try:
792804
connection.close_socket()
793805
except (OSError, RuntimeError) as exc:

tests/src/unit/test_haos_supervisor_wait.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,52 @@ def stalled_send(_: str) -> None:
130130
assert ws._ws is None
131131

132132

133+
def test_supervisor_api_queued_send_timeout_does_not_dispatch() -> None:
134+
"""A queued worker is cancelled without reporting an unknown outcome."""
135+
ws = HAWebSocket(
136+
"http://127.0.0.1:18123",
137+
OAuthCredentials(access_token="access", refresh_token="refresh"),
138+
)
139+
socket = Mock()
140+
ws._ws = socket
141+
queued_targets: list[Any] = []
142+
143+
class QueuedThread:
144+
def __init__(self, *, target: Any, **_: Any) -> None:
145+
queued_targets.append(target)
146+
147+
def start(self) -> None:
148+
pass
149+
150+
def join(self, timeout: float | None = None) -> None:
151+
del timeout
152+
153+
def is_alive(self) -> bool:
154+
return True
155+
156+
with (
157+
patch(
158+
"tests.haos_image_build.build_image.time.monotonic",
159+
side_effect=[10.0, 10.0, 11.0],
160+
),
161+
patch(
162+
"tests.haos_image_build.build_image.threading.Thread",
163+
QueuedThread,
164+
),
165+
pytest.raises(
166+
TimeoutError,
167+
match="exceeded its deadline before dispatch",
168+
),
169+
):
170+
ws.supervisor_api("/core/update", method="post", data={}, timeout=1.0)
171+
172+
queued_targets[0]()
173+
socket.send.assert_not_called()
174+
socket.close_socket.assert_not_called()
175+
socket.recv.assert_not_called()
176+
assert ws._ws is socket
177+
178+
133179
@pytest.mark.parametrize(
134180
("error", "expected_code", "expected_message"),
135181
[

0 commit comments

Comments
 (0)