Skip to content

Commit ba5d0da

Browse files
committed
fix: prevent isolated request cancellation from aborting tests (#1407)
1 parent a6e71f0 commit ba5d0da

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

tests/test_speed_dns_errors.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
import asyncio
22
import socket
33
import unittest
4+
from contextlib import asynccontextmanager
5+
from types import SimpleNamespace
6+
from unittest.mock import AsyncMock, patch
47

58
from aiohttp import ClientSession, TCPConnector
69

710
from utils.speed import create_speed_test_session, _install_aiohttp_dns_error_filter
811

912

13+
class MemoryArtifactWriter:
14+
def __init__(self, *_args, **_kwargs):
15+
self.count = 0
16+
17+
def write(self, _record):
18+
self.count += 1
19+
20+
def close(self):
21+
pass
22+
23+
24+
@asynccontextmanager
25+
async def speed_session(_concurrency):
26+
yield object()
27+
28+
29+
def speed_test_config():
30+
return SimpleNamespace(
31+
open_ipv6=False,
32+
speed_test_mode="full",
33+
open_full_speed_test=True,
34+
open_filter_resolution=False,
35+
open_stream_screenshot=False,
36+
performance_settings=SimpleNamespace(
37+
speed_test_concurrency=1,
38+
probe_concurrency=1,
39+
),
40+
speed_test_target=1,
41+
speed_test_timeout=1,
42+
)
43+
44+
1045
class FailingResolver:
1146
async def resolve(self, host, port=0, family=socket.AF_INET):
1247
await asyncio.sleep(0.02)
@@ -59,3 +94,64 @@ async def test_unrelated_asyncio_exception_uses_previous_handler(self):
5994
loop.call_exception_handler(context)
6095

6196
self.assertEqual(contexts, [context])
97+
98+
async def test_isolated_request_cancellation_does_not_cancel_batch(self):
99+
from utils.channel import test_speed
100+
101+
channel_data = {
102+
"Test": {
103+
"Channel": [{
104+
"url": "http://unresolvable.invalid/live",
105+
"host": "unresolvable.invalid",
106+
"resolution": None,
107+
"ipv_type": "ipv4",
108+
"origin": "subscribe",
109+
}]
110+
}
111+
}
112+
with (
113+
patch("utils.channel.config", speed_test_config()),
114+
patch("utils.channel.ArtifactWriter", MemoryArtifactWriter),
115+
patch("utils.channel.create_speed_test_session", speed_session),
116+
patch("utils.channel.get_speed", AsyncMock(side_effect=asyncio.CancelledError)),
117+
patch("utils.channel.mark_url_bad"),
118+
patch("utils.channel.mark_url_good"),
119+
):
120+
result = await test_speed(channel_data)
121+
122+
item = result["Test"]["Channel"][0]
123+
self.assertEqual(item["test_status"], "request_error")
124+
self.assertEqual(item["error_type"], "CancelledError")
125+
126+
async def test_real_batch_cancellation_still_propagates(self):
127+
from utils.channel import test_speed
128+
129+
started = asyncio.Event()
130+
131+
async def wait_for_cancellation(*_args, **_kwargs):
132+
started.set()
133+
await asyncio.Event().wait()
134+
135+
channel_data = {
136+
"Test": {
137+
"Channel": [{
138+
"url": "http://example.invalid/live",
139+
"host": "example.invalid",
140+
"resolution": None,
141+
"ipv_type": "ipv4",
142+
"origin": "subscribe",
143+
}]
144+
}
145+
}
146+
147+
with (
148+
patch("utils.channel.config", speed_test_config()),
149+
patch("utils.channel.ArtifactWriter", MemoryArtifactWriter),
150+
patch("utils.channel.create_speed_test_session", speed_session),
151+
patch("utils.channel.get_speed", wait_for_cancellation),
152+
):
153+
task = asyncio.create_task(test_speed(channel_data))
154+
await started.wait()
155+
task.cancel()
156+
with self.assertRaises(asyncio.CancelledError):
157+
await task

utils/channel.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,20 @@ async def worker():
10061006
http_semaphore=http_semaphore,
10071007
probe_semaphore=probe_semaphore,
10081008
)
1009+
except asyncio.CancelledError:
1010+
task = asyncio.current_task()
1011+
if task is not None and task.cancelling():
1012+
raise
1013+
result = {
1014+
"speed": 0,
1015+
"delay": -1,
1016+
"resolution": None,
1017+
"fps": None,
1018+
"video_codec": None,
1019+
"audio_codec": None,
1020+
"test_status": "request_error",
1021+
"error_type": "CancelledError",
1022+
}
10091023
except TimeoutError:
10101024
result = {
10111025
"speed": 0,

0 commit comments

Comments
 (0)