1919import subprocess
2020import sys
2121from abc import ABC , abstractmethod
22- from pathlib import Path
2322from typing import Callable , Dict , Optional , Union
2423
2524import websockets
2827from websockets .client import connect as websocket_connect
2928
3029from playwright ._impl ._api_types import Error
31- from playwright ._impl ._driver import get_driver_env
30+ from playwright ._impl ._driver import compute_driver_executable , get_driver_env
3231from playwright ._impl ._helper import ParsedMessagePayload
3332
3433
@@ -96,12 +95,9 @@ def deserialize_message(self, data: Union[str, bytes]) -> ParsedMessagePayload:
9695
9796
9897class PipeTransport (Transport ):
99- def __init__ (
100- self , loop : asyncio .AbstractEventLoop , driver_executable : Path
101- ) -> None :
98+ def __init__ (self , loop : asyncio .AbstractEventLoop ) -> None :
10299 super ().__init__ (loop )
103100 self ._stopped = False
104- self ._driver_executable = driver_executable
105101
106102 def request_stop (self ) -> None :
107103 assert self ._output
@@ -110,19 +106,6 @@ def request_stop(self) -> None:
110106
111107 async def wait_until_stopped (self ) -> None :
112108 await self ._stopped_future
113- # In Python 3.7, self._proc.wait() hangs because it does not use ThreadedChildWatcher
114- # which is used in Python 3.8+. Hence waiting for child process is skipped in Python 3.7.
115- # See https://bugs.python.org/issue35621
116- # See https://stackoverflow.com/questions/28915607/does-asyncio-support-running-a-subprocess-from-a-non-main-thread/28917653#28917653
117- if sys .version_info >= (3 , 8 ):
118- await self ._proc .wait ()
119- else :
120- import psutil
121-
122- try :
123- psutil .Process (self ._proc .pid ).wait ()
124- except psutil .NoSuchProcess :
125- pass
126109
127110 async def connect (self ) -> None :
128111 self ._stopped_future : asyncio .Future = asyncio .Future ()
@@ -131,14 +114,30 @@ async def connect(self) -> None:
131114 if sys .platform == "win32" and sys .stdout is None :
132115 creationflags = subprocess .CREATE_NO_WINDOW
133116
117+ original_child_watcher : Optional [asyncio .AbstractChildWatcher ] = None
118+ # In Python 3.7, self._proc.wait() hangs because it does not use ThreadedChildWatcher
119+ # which is used in Python 3.8+. This is unix specific and also takes care about
120+ # cleaning up zombie processes. See https://bugs.python.org/issue35621
121+ if (
122+ sys .version_info [0 ] == 3
123+ and sys .version_info [1 ] == 7
124+ and sys .platform != "win32"
125+ ):
126+ from ._py37ThreadedChildWatcher import ThreadedChildWatcher # type: ignore
127+
128+ watcher = ThreadedChildWatcher ()
129+ original_child_watcher = asyncio .get_child_watcher ()
130+ asyncio .set_child_watcher (watcher )
134131 try :
135132 # For pyinstaller
136133 env = get_driver_env ()
137134 if getattr (sys , "frozen" , False ):
138135 env .setdefault ("PLAYWRIGHT_BROWSERS_PATH" , "0" )
139136
137+ (driver_executable , cli_entrypoint ) = compute_driver_executable ()
140138 self ._proc = await asyncio .create_subprocess_exec (
141- str (self ._driver_executable ),
139+ str (driver_executable ),
140+ cli_entrypoint ,
142141 "run-driver" ,
143142 stdin = asyncio .subprocess .PIPE ,
144143 stdout = asyncio .subprocess .PIPE ,
@@ -150,6 +149,9 @@ async def connect(self) -> None:
150149 except Exception as exc :
151150 self .on_error_future .set_exception (exc )
152151 raise exc
152+ finally :
153+ if original_child_watcher :
154+ asyncio .set_child_watcher (original_child_watcher )
153155
154156 self ._output = self ._proc .stdin
155157
@@ -159,22 +161,30 @@ async def run(self) -> None:
159161 while not self ._stopped :
160162 try :
161163 buffer = await self ._proc .stdout .readexactly (4 )
164+ if self ._stopped :
165+ break
162166 length = int .from_bytes (buffer , byteorder = "little" , signed = False )
163167 buffer = bytes (0 )
164168 while length :
165169 to_read = min (length , 32768 )
166170 data = await self ._proc .stdout .readexactly (to_read )
171+ if self ._stopped :
172+ break
167173 length -= to_read
168174 if len (buffer ):
169175 buffer = buffer + data
170176 else :
171177 buffer = data
178+ if self ._stopped :
179+ break
172180
173181 obj = self .deserialize_message (buffer )
174182 self .on_message (obj )
175183 except asyncio .IncompleteReadError :
176184 break
177185 await asyncio .sleep (0 )
186+
187+ await self ._proc .wait ()
178188 self ._stopped_future .set_result (None )
179189
180190 def send (self , message : Dict ) -> None :
0 commit comments