Skip to content

call_soon_threadsafe is not really threadsafe #408

Closed
@hehaha

Description

@hehaha
  • 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😊

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions