|
9 | 9 |
|
10 | 10 | from .common import TestCase |
11 | 11 |
|
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. |
15 | 12 | 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 |
21 | 14 | MAX_FRAMES = 500 |
22 | 15 |
|
23 | 16 |
|
24 | 17 | 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 | | - |
31 | 18 | def __init__(self) -> None: |
32 | 19 | self.sock = socket.socket() |
33 | 20 | self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
@@ -115,17 +102,33 @@ def run() -> None: |
115 | 102 | return thread, raised |
116 | 103 |
|
117 | 104 | def test_start_encoding_releases_the_gil(self) -> None: |
118 | | - thread, _ = self._push(WINDOW * 3) |
| 105 | + """Other threads run while the handshake is stuck. |
119 | 106 |
|
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: |
124 | 117 | time.sleep(0.001) |
| 118 | + elapsed = time.monotonic() - start |
125 | 119 |
|
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" |
129 | 132 |
|
130 | 133 | def test_start_encoding_honours_the_timeout(self) -> None: |
131 | 134 | thread, raised = self._push(WINDOW) |
@@ -244,19 +247,10 @@ class TestSeekableOutputIgnoresTheTimeout(TestCase): |
244 | 247 | """A local file is written on its own schedule, not a peer's.""" |
245 | 248 |
|
246 | 249 | 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 | | - """ |
253 | 250 | path = self.sandboxed("faststart.mp4") |
254 | 251 | with av.open( |
255 | 252 | path, |
256 | 253 | "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. |
260 | 254 | timeout=(None, 1e-9), |
261 | 255 | container_options={"movflags": "faststart"}, |
262 | 256 | ) as container: |
|
0 commit comments