Skip to content

Commit 5eef7cd

Browse files
committed
Stop the GIL test mistaking a loaded machine for a held GIL
1 parent cb65007 commit 5eef7cd

1 file changed

Lines changed: 25 additions & 31 deletions

File tree

‎tests/test_output_blocking.py‎

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,12 @@
99

1010
from .common import TestCase
1111

12-
# The main thread sleeps in 1 ms slices while another thread is stuck in the
13-
# RTMP handshake. It wakes roughly a thousand times if the GIL is free and a
14-
# handful of times if it is not, so this threshold sits well clear of both.
1512
WINDOW = 1.0
16-
MIN_TICKS = 100
17-
18-
19-
# A writer that never blocks has nothing to time out, so give up once the
20-
# socket has swallowed more than any plausible buffer.
13+
HANDSHAKE_TIMEOUT = 10.0
2114
MAX_FRAMES = 500
2215

2316

2417
class SilentServer:
25-
"""Accepts connections and then neither reads nor writes.
26-
27-
An RTMP handshake never completes against it, and a socket written to it
28-
fills up and stays full.
29-
"""
30-
3118
def __init__(self) -> None:
3219
self.sock = socket.socket()
3320
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -115,17 +102,33 @@ def run() -> None:
115102
return thread, raised
116103

117104
def test_start_encoding_releases_the_gil(self) -> None:
118-
thread, _ = self._push(WINDOW * 3)
105+
"""Other threads run while the handshake is stuck.
119106
120-
ticks = 0
121-
deadline = time.monotonic() + WINDOW
122-
while time.monotonic() < deadline:
123-
ticks += 1
107+
The server records a connection from Python, so if the handshake held
108+
the GIL, neither it nor this thread could run until the handshake timed
109+
out. Counting how often this thread wakes instead would confuse a held
110+
GIL with a machine too loaded to schedule it.
111+
"""
112+
start = time.monotonic()
113+
thread, _ = self._push(HANDSHAKE_TIMEOUT)
114+
115+
deadline = start + HANDSHAKE_TIMEOUT
116+
while not self.server.accepted and time.monotonic() < deadline:
124117
time.sleep(0.001)
118+
elapsed = time.monotonic() - start
125119

126-
assert thread.is_alive(), "the handshake completed, so nothing was blocking"
127-
assert ticks > MIN_TICKS, f"main thread only ran {ticks} times"
128-
thread.join(WINDOW * 8)
120+
assert self.server.accepted, (
121+
f"no connection was seen for {elapsed:.1f}s: the handshake held the "
122+
"GIL, or never connected"
123+
)
124+
assert thread.is_alive(), "the handshake ended before anything else could run"
125+
assert elapsed < HANDSHAKE_TIMEOUT / 2, f"nothing else ran for {elapsed:.1f}s"
126+
127+
# Hanging up ends the handshake, so passing does not cost the timeout.
128+
for conn in self.server.accepted:
129+
conn.close()
130+
thread.join(HANDSHAKE_TIMEOUT)
131+
assert not thread.is_alive(), "hanging up did not end the handshake"
129132

130133
def test_start_encoding_honours_the_timeout(self) -> None:
131134
thread, raised = self._push(WINDOW)
@@ -244,19 +247,10 @@ class TestSeekableOutputIgnoresTheTimeout(TestCase):
244247
"""A local file is written on its own schedule, not a peer's."""
245248

246249
def test_faststart_close_is_not_cut_short(self) -> None:
247-
"""``movflags=faststart`` rewrites the file when the trailer is written.
248-
249-
How long that takes grows with the file, so a timeout meant for a
250-
stalled peer would abandon it part-written. The file has to survive
251-
both the write and a read back.
252-
"""
253250
path = self.sandboxed("faststart.mp4")
254251
with av.open(
255252
path,
256253
"w",
257-
# Small enough that any interruptible write trips it, so the test
258-
# turns on whether a file is subject to the timeout at all rather
259-
# than on how fast the disk is.
260254
timeout=(None, 1e-9),
261255
container_options={"movflags": "faststart"},
262256
) as container:

0 commit comments

Comments
 (0)