Skip to content

shutdown_default_executor / wait_for_tstate_lock deadlock (?) after Ctrl+C #111358

Open
@xbeastx

Description

@xbeastx

Bug report

Bug description:

Blocking function running in thread executor will produce deadlock on shutdown after Ctrl+C pressed.

Here is minimal reproducing example:

import asyncio
import time

def blocking():
    print("sleep start")
    time.sleep(1000)
    print("sleep end")

async def main():
    await asyncio.to_thread(blocking)

asyncio.run(main())

Steps to reproduce:

  1. Run script
  2. Try press Ctrl+C when blocking func sleeps | Nothing produced
  3. If press Ctrl+C again | Will print KeyboardInterrupt exception but script will still hang
  4. If press Ctrl+C again | Will print one more KeyboardInterrupt and will shutdown.

(Tested on 3.9<=python<=3.12 on Ubuntu 22.04 all have same behaviour)

Output:

$ python3.11 test.py 
sleep start
^C^CTraceback (most recent call last):
  File "/usr/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/ilya/test.py", line 13, in main
    await asyncio.to_thread(blocking)
  File "/usr/lib/python3.11/asyncio/threads.py", line 25, in to_thread
    return await loop.run_in_executor(None, func_call)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/runners.py", line 123, in run
    raise KeyboardInterrupt()
KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ilya/test.py", line 15, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.11/asyncio/runners.py", line 189, in run
    with Runner(debug=debug) as runner:
  File "/usr/lib/python3.11/asyncio/runners.py", line 63, in __exit__
    self.close()
  File "/usr/lib/python3.11/asyncio/runners.py", line 73, in close
    loop.run_until_complete(loop.shutdown_default_executor())
  File "/usr/lib/python3.11/asyncio/base_events.py", line 640, in run_until_complete
    self.run_forever()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File "/usr/lib/python3.11/asyncio/base_events.py", line 1884, in _run_once
    event_list = self._selector.select(timeout)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/selectors.py", line 468, in select
    fd_event_list = self._selector.poll(timeout, max_ev)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt
^CException ignored in: <module 'threading' from '/usr/lib/python3.11/threading.py'>
Traceback (most recent call last):
  File "/usr/lib/python3.11/threading.py", line 1560, in _shutdown
    atexit_call()
  File "/usr/lib/python3.11/concurrent/futures/thread.py", line 31, in _python_exit
    t.join()
  File "/usr/lib/python3.11/threading.py", line 1119, in join
    self._wait_for_tstate_lock()
  File "/usr/lib/python3.11/threading.py", line 1139, in _wait_for_tstate_lock
    if lock.acquire(block, timeout):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt: 

Additional:
Found a blog post https://www.roguelynn.com/words/asyncio-sync-and-threaded/ with looks like similar behavior back in 2019 where someone is trying to workaround this.

CPython versions tested on:

3.9, 3.10, 3.11, 3.12

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions