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

call_soon_threadsafe is not really threadsafe #408

Closed
hehaha opened this issue Apr 12, 2021 · 4 comments
Closed

call_soon_threadsafe is not really threadsafe #408

hehaha opened this issue Apr 12, 2021 · 4 comments

Comments

@hehaha
Copy link

hehaha commented Apr 12, 2021

  • uvloop version: 0.15
  • Python version: 3.8.6
  • Platform: Ubuntu 18.04.3
  • Can you reproduce the bug with PYTHONASYNCIODEBUG in env?: yes
  • Does uvloop behave differently from vanilla asyncio? How?: yes

I have been confused about this bug for about two weeks. In my situation, I create some threads and every thread run a new loop separately. At the same time, I have another thread to invoke loop.call_soon_threadsafe to add tasks to loops in other threads. It ran well at first. However loops would close after a-few-hour running and raised RuntimeError('Event loop stopped before Future completed.'). Actually some tasks still left in the loop and I haven't closed the loop yet.

Because this problem is really hard to reproduce . I took a long time to figure out how it happened and checked if I invoked loop.close in somewhere. I added some log at close and found the loop stopped suddenly without close invocation. So I went on and checked libuv docs. There is a suspicious sentence If the loop is alive an iteration is started, otherwise the loop will exit immediately. So, when is a loop considered to be alive? If a loop has active and ref’d handles, active requests or closing handles it’s considered to be alive.. I added some log in uv.uv_run and it verified that the loop stoped because uv__loop_alive return False!

I dove into uvloop and libuv carefully. Disappointedly I found nothing. Code are rigorous and elegant. So I logged around every init and stop when uvloop invoke uv_handle_t.Just like this
截屏2021-04-12 20 34 24

After a few hours execution and 200 thousand lines log file generated, I reproduced the bug again! And the highlight line is where the problem happened.
截屏2021-04-12 16 26 59
Because libuv isn't thread safe. The uv_loop_t.active_handles will in race condition when we invoke call_soon_threadsafe in a thread and the loop runs in another thread simultaneously. Actually the loop.handle_async.send will activate the idle handle in the loop thread. So we don't need to activate the idle handle at call_soon_threadsafe

Here is my pull request. Approve please😊

@hehaha
Copy link
Author

hehaha commented May 11, 2021

Can you please help me and review the PR? And if it's possible to release a new version to fix this problem? @fantix @graingert

@fantix
Copy link
Member

fantix commented Jul 12, 2021

Sorry for the late reply and thank you for the careful debugging! The PR removed the start of the idle handler in call_soon_threadsafe() because starting an idle handler is not thread-safe and may potentially overtake other handles scheduled at the same time leading to an early loop exit, meanwhile the async handler will anyway start the idle handler properly from the loop thread?

-        if not self.handler_idle.running:
-            self.handler_idle.start()

That does make sense to me. It would be great if we could have a test to cover the case though.

@hehaha
Copy link
Author

hehaha commented Jul 13, 2021

Thanks God for you reply me finally! It's do better to have a test case.

@fantix fantix mentioned this issue Jul 13, 2021
@fantix
Copy link
Member

fantix commented Jul 13, 2021

Fixed in uvloop 0.15.3, please feel free to reopen if the issue persists.

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

No branches or pull requests

2 participants