playwright-python
playwright-python copied to clipboard
fix: asyncio.create_task race issue in page and brower_context
we should consider ._channel.send have race condition issue with page close event in some Page's method.
Page._update_interception_patterns is just a situation i encountered.
This problem is triggered when the Page.unroute method is called when the page is about to close.
@mxschmitt There are too many calls to asyncio.create_task in the implementation of python, and these calls may cause race issues, such as the processing of dialog. I want to re-check the implementation of this part of the page and unify it with reference to the implementation of JavaScript.
@mxschmitt done. please check again.
the bots seem all unhappy and timing out.
I'm on vacation these days and will check again later.
@mxschmitt ready for review, this PR contains:
- fix
asyncio.create_taskfire-and-forget usage includekeep reference to the taskfor gc issues likeTask was destroyed but it is pending!. - we can't unify it like javascript because sync api is broken when listener is a coroutine.
@mxschmitt any thoughts about this? should I split the patch for AsyncIOEventEmitter on another PR or something else?
Sorry for being slow on this, the last few weeks were busy on my end and we landed a bunch of related changes in the 1.34 release, so we tried not put another change which might introduce regressions into it.
I will get back to you in a few days. Would it be possible to add a test/s for it?
I was talking with my colleagues about it and it still seems, instead of introducing so much complexity, it would be much nicer to do it as the way I explained in https://github.com/microsoft/playwright-python/pull/1864#pullrequestreview-1389006004. This would fix the actual problem you reported in the first place. Appreciate the efforts, thanks!
@mxschmitt sorry for delay, it can't try await ... /except in listener. if we change handler to async def. it will broken sync api here.
https://github.com/microsoft/playwright-python/blob/e6a7a37ee7e5331bf6ff9c7c08e6b56e566219c2/playwright/_impl/_connection.py#L409-L418
probably should add _emit_sync in base channel, and replace asyncio.create_task everywhere. this is the correct way for use asyncio.create_task in sync method.
and remove the fix for pyee.AsyncIOEventEmitter, wait for upstream vote or patch. https://github.com/jfhbrook/pyee/issues/120
def _emit_sync(self, coro: Coroutine, ignore_errors: bool = True) -> None:
fut = asyncio.ensure_future(coro, loop=self._loop)
def cb(f: asyncio.Task) -> None:
self._running_tasks.remove(f)
if f.cancelled():
return
exc: Optional[BaseException] = f.exception()
if exc and not ignore_errors:
self.emit("error", exc)
self._running_tasks.add(fut)
fut.add_done_callback(cb)
Reference:
- https://github.com/python/cpython/issues/88831
- https://stackoverflow.com/questions/71938799/python-asyncio-create-task-really-need-to-keep-a-reference
- https://docs.python.org/3/library/asyncio-task.html
Important Save a reference to the result of this function, to avoid a task disappearing mid-execution. The event loop only keeps weak references to tasks. A task that isn’t referenced elsewhere may get garbage collected at any time, even before it’s done. For reliable “fire-and-forget” background tasks, gather them in a collection.