playwright-python icon indicating copy to clipboard operation
playwright-python copied to clipboard

fix: asyncio.create_task race issue in page and brower_context

Open x0day opened this issue 2 years ago • 9 comments

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.

image

x0day avatar Apr 16 '23 03:04 x0day

@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.

x0day avatar Apr 19 '23 01:04 x0day

@mxschmitt done. please check again.

x0day avatar Apr 25 '23 02:04 x0day

the bots seem all unhappy and timing out.

mxschmitt avatar May 02 '23 12:05 mxschmitt

I'm on vacation these days and will check again later.

x0day avatar May 03 '23 09:05 x0day

@mxschmitt ready for review, this PR contains:

  1. fix asyncio.create_task fire-and-forget usage include keep reference to the task for gc issues like Task was destroyed but it is pending!.
  2. we can't unify it like javascript because sync api is broken when listener is a coroutine.

x0day avatar May 07 '23 10:05 x0day

@mxschmitt any thoughts about this? should I split the patch for AsyncIOEventEmitter on another PR or something else?

x0day avatar May 16 '23 13:05 x0day

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?

mxschmitt avatar May 25 '23 23:05 mxschmitt

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 avatar May 31 '23 17:05 mxschmitt

@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.

x0day avatar Jun 01 '23 13:06 x0day