Skip to content

Commit f3393a2

Browse files
committed
Fix relay pipe hang on Linux/CPython 3.14 and clear Sonar index-access finding
- relay._pipe: poll readability with select() + timeout instead of a bare blocking recv(). On Linux + CPython 3.14 a cross-thread dst.shutdown() no longer reliably wakes a recv() parked on the same socket, so _pair_and_pump's join() hung forever when one peer disconnected (test_pair_and_pump_exits_ when_one_side_closes failed only on 3.14). Polling guarantees the thread re-checks the stop flag and exits. - test_platform_backend_binding: slice access instead of events[0] index (clears the remaining Sonar new-code reliability finding).
1 parent 3a0ffd2 commit f3393a2

2 files changed

Lines changed: 19 additions & 1 deletion

File tree

je_auto_control/utils/remote_desktop/relay.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"""
2323
from __future__ import annotations
2424

25+
import select
2526
import socket
2627
import threading
2728
from typing import Dict, Optional, Tuple
@@ -36,6 +37,13 @@
3637
# accept() 的輪詢間隔,用來定期檢查 _shutdown 旗標。
3738
# How long accept() blocks before re-checking the _shutdown flag.
3839
_ACCEPT_POLL_TIMEOUT_S = 0.5
40+
# How long a pipe thread waits for readable data before re-checking the stop
41+
# flag. A blocking recv() can only be woken by the opposite thread's
42+
# dst.shutdown(); on some platforms/Python versions (observed on Linux +
43+
# CPython 3.14) that shutdown does not reliably interrupt a recv() already
44+
# parked on the same socket, so the join() hangs forever. Polling readability
45+
# with select() guarantees the thread re-checks stop_event and exits.
46+
_PIPE_POLL_TIMEOUT_S = 0.5
3947

4048

4149
class RelayError(RuntimeError):
@@ -58,6 +66,16 @@ def _pipe(src: socket.socket, dst: socket.socket,
5866
"""Forward bytes from ``src`` to ``dst`` until either closes."""
5967
try:
6068
while not stop_event.is_set():
69+
# Wait for readability with a timeout instead of a bare blocking
70+
# recv() so the loop always circles back to re-check stop_event
71+
# even if a cross-thread shutdown() fails to wake the recv().
72+
try:
73+
readable, _, _ = select.select([src], [], [],
74+
_PIPE_POLL_TIMEOUT_S)
75+
except (OSError, ValueError):
76+
break # src closed under us
77+
if not readable:
78+
continue
6179
data = src.recv(_BUFFER_SIZE)
6280
if not data:
6381
break

test/unit_test/headless/test_platform_backend_binding.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def test_scroll_moves_the_cursor_when_coords_are_given(monkeypatch, platform):
136136

137137
auto_control_mouse.mouse_scroll(5, x=100, y=200)
138138

139-
assert events[0] == ("move", 100, 200)
139+
assert events[:1] == [("move", 100, 200)]
140140

141141

142142
@pytest.mark.parametrize("platform", ["win32", "darwin", "linux"])

0 commit comments

Comments
 (0)