Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: asyncio.create_task race issue in page and brower_context #1864

Closed
wants to merge 12 commits into from

Conversation

x0day
Copy link
Contributor

@x0day x0day commented Apr 16, 2023

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

Copy link
Member

@mxschmitt mxschmitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify this PR by mirroring our upstream logic:

https://github.com/microsoft/playwright/blob/d7b3836752fbb996a4eb7dd6e36c3970912ac2e4/packages/playwright-core/src/client/page.ts#L193

This means inside

self._connection.wrap_api_call(

we try-catch basically inside the lambda.

This means inside

@x0day
Copy link
Contributor Author

x0day commented Apr 19, 2023

@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 x0day requested a review from mxschmitt April 24, 2023 03:49
@x0day x0day changed the title fix: setNetworkInterceptionPatterns race issue fix: asyncio.create_task race issue in page and brower_context Apr 25, 2023
@x0day
Copy link
Contributor Author

x0day commented Apr 25, 2023

@mxschmitt done. please check again.

@mxschmitt
Copy link
Member

the bots seem all unhappy and timing out.

@x0day
Copy link
Contributor Author

x0day commented May 3, 2023

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

@x0day
Copy link
Contributor Author

x0day commented May 7, 2023

@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
Copy link
Contributor Author

x0day commented May 16, 2023

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

@mxschmitt
Copy link
Member

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
Copy link
Member

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 #1864 (review). This would fix the actual problem you reported in the first place. Appreciate the efforts, thanks!

@x0day
Copy link
Contributor Author

x0day commented Jun 1, 2023

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

if self._is_sync:
for listener in object._channel.listeners(method):
# Each event handler is a potentilly blocking context, create a fiber for each
# and switch to them in order, until they block inside and pass control to each
# other and then eventually back to dispatcher as listener functions return.
g = greenlet(listener)
if should_replace_guids_with_channels:
g.switch(self._replace_guids_with_channels(params))
else:
g.switch(params)

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. jfhbrook/pyee#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:

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.

Copy link

@A1exKH A1exKH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code LGTM.
Please resolve conflicts for this PR.

@mxschmitt
Copy link
Member

This PR looks stale and doesn't link a specific GitHub issue which it fixes. I'll close it for now but don't hesitate to re-create it with a test and linked issue. Thanks for your collaboration!

@mxschmitt mxschmitt closed this Sep 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants