Skip to content

Commit 2f8041c

Browse files
committed
fix: forward cancelled tasks to the inner task
1 parent 417a5b0 commit 2f8041c

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

playwright/_impl/_connection.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ class ProtocolCallback:
124124
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
125125
self.stack_trace: traceback.StackSummary = traceback.StackSummary()
126126
self.future = loop.create_future()
127+
# The outer task can get cancelled by the user, this forwards the cancellation to the inner task.
128+
current_task = asyncio.current_task()
129+
130+
def cb(task: asyncio.Task) -> None:
131+
if current_task:
132+
current_task.remove_done_callback(cb)
133+
if task.cancelled():
134+
self.future.cancel()
135+
136+
if current_task:
137+
current_task.add_done_callback(cb)
138+
self.future.add_done_callback(
139+
lambda _: current_task.remove_done_callback(cb)
140+
if current_task
141+
else None
142+
)
127143

128144

129145
class RootChannelOwner(ChannelOwner):

tests/async/test_asyncio.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright (c) Microsoft Corporation.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License")
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import asyncio
15+
import contextlib
16+
17+
from playwright.async_api import async_playwright
18+
19+
20+
def test_should_cancel_underlying_calls(browser_name: str):
21+
handler_exception = None
22+
23+
async def main():
24+
loop = asyncio.get_running_loop()
25+
26+
def handler(loop, context):
27+
nonlocal handler_exception
28+
handler_exception = context["exception"]
29+
30+
async with async_playwright() as p:
31+
loop.set_exception_handler(handler)
32+
browser = await p[browser_name].launch()
33+
page = await browser.new_page()
34+
task = asyncio.create_task(page.wait_for_selector("will-never-find"))
35+
# make sure that the wait_for_selector message was sent to the server (driver)
36+
await asyncio.sleep(1)
37+
task.cancel()
38+
with contextlib.suppress(asyncio.CancelledError):
39+
await task
40+
await browser.close()
41+
42+
asyncio.run(main())
43+
44+
assert handler_exception is None

0 commit comments

Comments
 (0)