Skip to content

Commit b9755e2

Browse files
authored
fix(loop): create separate loop for sync playwright execution (microsoft#354)
1 parent c606344 commit b9755e2

11 files changed

+99
-129
lines changed

playwright/accessibility.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class Accessibility:
5757
def __init__(self, channel: Channel) -> None:
5858
self._channel = channel
5959
self._loop = channel._connection._loop
60+
self._dispatcher_fiber = channel._connection._dispatcher_fiber
6061

6162
async def snapshot(
6263
self, interestingOnly: bool = None, root: ElementHandle = None

playwright/connection.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
import asyncio
1616
import sys
1717
import traceback
18+
from pathlib import Path
1819
from typing import Any, Callable, Dict, Optional, Union
1920

2021
from greenlet import greenlet
2122
from pyee import AsyncIOEventEmitter
2223

2324
from playwright.helper import ParsedMessagePayload, parse_error
24-
from playwright.sync_base import dispatcher_fiber
2525
from playwright.transport import Transport
2626

2727

@@ -74,6 +74,7 @@ def __init__(
7474
) -> None:
7575
super().__init__(loop=parent._loop)
7676
self._loop: asyncio.AbstractEventLoop = parent._loop
77+
self._dispatcher_fiber: Any = parent._dispatcher_fiber
7778
self._type = type
7879
self._guid = guid
7980
self._connection: Connection = (
@@ -116,33 +117,30 @@ def __init__(self, connection: "Connection") -> None:
116117

117118
class Connection:
118119
def __init__(
119-
self,
120-
input: asyncio.StreamReader,
121-
output: asyncio.StreamWriter,
122-
object_factory: Any,
123-
loop: asyncio.AbstractEventLoop,
120+
self, dispatcher_fiber: Any, object_factory: Any, driver_executable: Path
124121
) -> None:
125-
self._transport = Transport(input, output, loop)
122+
self._dispatcher_fiber: Any = dispatcher_fiber
123+
self._transport = Transport(driver_executable)
126124
self._transport.on_message = lambda msg: self._dispatch(msg)
127125
self._waiting_for_object: Dict[str, Any] = {}
128126
self._last_id = 0
129-
self._loop = loop
130127
self._objects: Dict[str, ChannelOwner] = {}
131128
self._callbacks: Dict[int, ProtocolCallback] = {}
132-
self._root_object = RootChannelOwner(self)
133129
self._object_factory = object_factory
134130
self._is_sync = False
135131

136-
def run_sync(self) -> None:
132+
async def run_as_sync(self) -> None:
137133
self._is_sync = True
138-
self._transport.run_sync()
134+
await self.run()
139135

140-
def run_async(self) -> None:
141-
self._transport.run_async()
136+
async def run(self) -> None:
137+
self._loop = asyncio.get_running_loop()
138+
self._root_object = RootChannelOwner(self)
139+
await self._transport.run()
142140

143141
def stop_sync(self) -> None:
144142
self._transport.stop()
145-
dispatcher_fiber().switch()
143+
self._dispatcher_fiber.switch()
146144

147145
def stop_async(self) -> None:
148146
self._transport.stop()

playwright/file_chooser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
) -> None:
2828
self._page = page
2929
self._loop = page._loop
30+
self._dispatcher_fiber = page._dispatcher_fiber
3031
self._element_handle = element_handle
3132
self._is_multiple = is_multiple
3233

playwright/input.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class Keyboard:
2020
def __init__(self, channel: Channel) -> None:
2121
self._channel = channel
2222
self._loop = channel._connection._loop
23+
self._dispatcher_fiber = channel._connection._dispatcher_fiber
2324

2425
async def down(self, key: str) -> None:
2526
await self._channel.send("keyboardDown", locals_to_params(locals()))
@@ -41,6 +42,7 @@ class Mouse:
4142
def __init__(self, channel: Channel) -> None:
4243
self._channel = channel
4344
self._loop = channel._connection._loop
45+
self._dispatcher_fiber = channel._connection._dispatcher_fiber
4446

4547
async def move(self, x: float, y: float, steps: int = None) -> None:
4648
await self._channel.send("mouseMove", locals_to_params(locals()))
@@ -83,6 +85,7 @@ class Touchscreen:
8385
def __init__(self, channel: Channel) -> None:
8486
self._channel = channel
8587
self._loop = channel._connection._loop
88+
self._dispatcher_fiber = channel._connection._dispatcher_fiber
8689

8790
async def tap(self, x: float, y: float) -> None:
8891
await self._channel.send("touchscreenTap", locals_to_params(locals()))

playwright/main.py

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from playwright.path_utils import get_file_dirname
2929
from playwright.playwright import Playwright
3030
from playwright.sync_api import Playwright as SyncPlaywright
31-
from playwright.sync_base import dispatcher_fiber, set_dispatcher_fiber
3231

3332

3433
def compute_driver_executable() -> Path:
@@ -39,47 +38,43 @@ def compute_driver_executable() -> Path:
3938
return package_path / "driver" / "playwright-cli"
4039

4140

42-
async def run_driver_async() -> Connection:
43-
driver_executable = compute_driver_executable()
44-
45-
proc = await asyncio.create_subprocess_exec(
46-
str(driver_executable),
47-
"run-driver",
48-
stdin=asyncio.subprocess.PIPE,
49-
stdout=asyncio.subprocess.PIPE,
50-
stderr=sys.stderr,
51-
limit=32768,
52-
)
53-
assert proc.stdout
54-
assert proc.stdin
55-
connection = Connection(
56-
proc.stdout, proc.stdin, create_remote_object, asyncio.get_event_loop()
57-
)
58-
return connection
59-
60-
61-
def run_driver() -> Connection:
62-
loop = asyncio.get_event_loop()
63-
if loop.is_running():
64-
raise Error("Can only run one Playwright at a time.")
65-
return loop.run_until_complete(run_driver_async())
66-
67-
6841
class SyncPlaywrightContextManager:
6942
def __init__(self) -> None:
70-
self._connection = run_driver()
7143
self._playwright: SyncPlaywright
7244

7345
def __enter__(self) -> SyncPlaywright:
46+
def greenlet_main() -> None:
47+
loop = None
48+
own_loop = None
49+
try:
50+
loop = asyncio.get_running_loop()
51+
except RuntimeError:
52+
loop = asyncio.new_event_loop()
53+
own_loop = loop
54+
55+
if loop.is_running():
56+
raise Error("Can only run one Playwright at a time.")
57+
58+
loop.run_until_complete(self._connection.run_as_sync())
59+
60+
if own_loop:
61+
loop.run_until_complete(loop.shutdown_asyncgens())
62+
loop.close()
63+
64+
dispatcher_fiber = greenlet(greenlet_main)
65+
self._connection = Connection(
66+
dispatcher_fiber, create_remote_object, compute_driver_executable()
67+
)
68+
7469
g_self = greenlet.getcurrent()
7570

7671
def callback_wrapper(playwright_impl: Playwright) -> None:
7772
self._playwright = SyncPlaywright(playwright_impl)
7873
g_self.switch()
7974

8075
self._connection.call_on_object_with_known_name("Playwright", callback_wrapper)
81-
set_dispatcher_fiber(greenlet(lambda: self._connection.run_sync()))
82-
dispatcher_fiber().switch()
76+
77+
dispatcher_fiber.switch()
8378
playwright = self._playwright
8479
playwright.stop = self.__exit__ # type: ignore
8580
return playwright
@@ -96,8 +91,12 @@ def __init__(self) -> None:
9691
self._connection: Connection
9792

9893
async def __aenter__(self) -> AsyncPlaywright:
99-
self._connection = await run_driver_async()
100-
self._connection.run_async()
94+
self._connection = Connection(
95+
None, create_remote_object, compute_driver_executable()
96+
)
97+
loop = asyncio.get_running_loop()
98+
self._connection._loop = loop
99+
loop.create_task(self._connection.run())
101100
playwright = AsyncPlaywright(
102101
await self._connection.wait_for_object_with_known_name("Playwright")
103102
)
@@ -113,8 +112,7 @@ async def __aexit__(self, *args: Any) -> None:
113112

114113
if sys.platform == "win32":
115114
# Use ProactorEventLoop in 3.7, which is default in 3.8
116-
loop = asyncio.ProactorEventLoop()
117-
asyncio.set_event_loop(loop)
115+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
118116

119117

120118
def main() -> None:

playwright/sync_api.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ def expect_event(
577577
page.setDefaultTimeout(timeout) methods.
578578
"""
579579
return EventContextManager(
580-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
580+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
581581
)
582582

583583
def isClosed(self) -> bool:
@@ -3198,7 +3198,7 @@ def expect_load_state(
31983198
page.setDefaultTimeout(timeout) methods.
31993199
"""
32003200
return EventContextManager(
3201-
self._loop, self._impl_obj.waitForLoadState(state, timeout)
3201+
self, self._impl_obj.waitForLoadState(state, timeout)
32023202
)
32033203

32043204
def expect_navigation(
@@ -3227,7 +3227,7 @@ def expect_navigation(
32273227
page.setDefaultTimeout(timeout) methods.
32283228
"""
32293229
return EventContextManager(
3230-
self._loop, self._impl_obj.waitForNavigation(url, waitUntil, timeout)
3230+
self, self._impl_obj.waitForNavigation(url, waitUntil, timeout)
32313231
)
32323232

32333233

@@ -5561,7 +5561,7 @@ def expect_event(
55615561
page.setDefaultTimeout(timeout) methods.
55625562
"""
55635563
return EventContextManager(
5564-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
5564+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
55655565
)
55665566

55675567
def expect_console_message(
@@ -5590,7 +5590,7 @@ def expect_console_message(
55905590
"""
55915591
event = "console"
55925592
return EventContextManager(
5593-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
5593+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
55945594
)
55955595

55965596
def expect_download(
@@ -5619,7 +5619,7 @@ def expect_download(
56195619
"""
56205620
event = "download"
56215621
return EventContextManager(
5622-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
5622+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
56235623
)
56245624

56255625
def expect_file_chooser(
@@ -5648,7 +5648,7 @@ def expect_file_chooser(
56485648
"""
56495649
event = "filechooser"
56505650
return EventContextManager(
5651-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
5651+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
56525652
)
56535653

56545654
def expect_load_state(
@@ -5676,7 +5676,7 @@ def expect_load_state(
56765676
page.setDefaultTimeout(timeout) methods.
56775677
"""
56785678
return EventContextManager(
5679-
self._loop, self._impl_obj.waitForLoadState(state, timeout)
5679+
self, self._impl_obj.waitForLoadState(state, timeout)
56805680
)
56815681

56825682
def expect_navigation(
@@ -5705,7 +5705,7 @@ def expect_navigation(
57055705
page.setDefaultTimeout(timeout) methods.
57065706
"""
57075707
return EventContextManager(
5708-
self._loop, self._impl_obj.waitForNavigation(url, waitUntil, timeout)
5708+
self, self._impl_obj.waitForNavigation(url, waitUntil, timeout)
57095709
)
57105710

57115711
def expect_popup(
@@ -5734,7 +5734,7 @@ def expect_popup(
57345734
"""
57355735
event = "popup"
57365736
return EventContextManager(
5737-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
5737+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
57385738
)
57395739

57405740
def expect_request(
@@ -5763,7 +5763,7 @@ def expect_request(
57635763
page.setDefaultTimeout(timeout) methods.
57645764
"""
57655765
return EventContextManager(
5766-
self._loop, self._impl_obj.waitForRequest(url, predicate, timeout)
5766+
self, self._impl_obj.waitForRequest(url, predicate, timeout)
57675767
)
57685768

57695769
def expect_response(
@@ -5792,7 +5792,7 @@ def expect_response(
57925792
page.setDefaultTimeout(timeout) methods.
57935793
"""
57945794
return EventContextManager(
5795-
self._loop, self._impl_obj.waitForResponse(url, predicate, timeout)
5795+
self, self._impl_obj.waitForResponse(url, predicate, timeout)
57965796
)
57975797

57985798
def expect_worker(
@@ -5821,7 +5821,7 @@ def expect_worker(
58215821
"""
58225822
event = "worker"
58235823
return EventContextManager(
5824-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
5824+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
58255825
)
58265826

58275827

@@ -6241,7 +6241,7 @@ def expect_event(
62416241
page.setDefaultTimeout(timeout) methods.
62426242
"""
62436243
return EventContextManager(
6244-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
6244+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
62456245
)
62466246

62476247
def expect_page(
@@ -6270,7 +6270,7 @@ def expect_page(
62706270
"""
62716271
event = "page"
62726272
return EventContextManager(
6273-
self._loop, self._impl_obj.waitForEvent(event, predicate, timeout)
6273+
self, self._impl_obj.waitForEvent(event, predicate, timeout)
62746274
)
62756275

62766276

0 commit comments

Comments
 (0)