Skip to content

run_task assert fails when other thread appends Handle to _ready #19

@bh-tneukom

Description

@bh-tneukom

Hi!

Thanks for the great library! We might have found a small issue that causes the assert

ntodo = len(loop._ready)
task = asyncio.create_task(coro, **kwargs)
# if task._source_traceback:
# del task._source_traceback[-1]
assert len(loop._ready) == ntodo + 1
handle = loop._ready.pop()

to trigger when running functions in a thread using run_in_executor. With some bad luck the thread finishes exactly when the task is created and a new handle is added causing len(loop._ready) == ntodo + 2.

Uncommenting the monkey patch of the _ready deque shows what's happening:

import asyncio
import sys
import threading

import qtinter
from PySide6 import QtWidgets


class CrashWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setLayout(QtWidgets.QVBoxLayout())

        btn = QtWidgets.QPushButton('Crash!')
        btn.clicked.connect(qtinter.asyncslot(self._run_in_executor))
        btn.clicked.connect(qtinter.asyncslot(self._set_event))
        btn.clicked.connect(qtinter.asyncslot(self._print_hello_world))
        self.layout().addWidget(btn)

    def _wait_until_event(self):
        self._event.wait()
        print('Joining')

    async def _run_in_executor(self):
        self._event = threading.Event()
        self._executor_task = asyncio.get_event_loop().run_in_executor(None, self._wait_until_event)
        await self._executor_task

    async def _set_event(self):
        self._event.set()

    async def _print_hello_world(self):
        print('Hello World')


if __name__ == '__main__':
    print(f'Main thread id: {threading.get_ident()}')
    app = QtWidgets.QApplication(sys.argv)

    with qtinter.using_asyncio_from_qt():
        # Useful for debugging
        # import collections
        # class MonkeyDeque(collections.deque):
        #     def append(self, __x):
        #         print(f'Adding {__x} to deque in thread {threading.get_ident()}')
        #         super().append(__x)
        # loop = asyncio.get_event_loop()
        # loop._ready = MonkeyDeque(loop._ready)

        widget = CrashWidget()
        widget.show()

        app.exec()

Log

Main thread id: 26280
...
Adding <Handle _chain_future.<locals>._set_state(<Future pendi...ask_wakeup()]>, <Future at 0x...rned NoneType>) at C:\dev\envs\py311\Lib\asyncio\futures.py:381> to deque in thread 17728
...
Traceback (most recent call last):
  File "C:\dev\projects\the_project\venv\Lib\site-packages\qtinter\_helpers.py", line 120, in handle
    transform(method, args, *extra)
  File "C:\dev\projects\the_project\venv\Lib\site-packages\qtinter\_slots.py", line 29, in _run_coroutine_function
    task = task_runner(coro)  # TODO: set name and context
           ^^^^^^^^^^^^^^^^^
  File "C:\dev\projects\the_project\venv\Lib\site-packages\qtinter\_tasks.py", line 30, in run_task
    assert len(loop._ready) == ntodo + 1
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

The _chain_future Handle is added not from the main thread. The assert fails immediately after that.

It takes a couple of tries to trigger the assert.

Best
Tobias

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions