omnilib / aiosqlite

asyncio bridge to the standard sqlite3 module

Home Page:https://aiosqlite.omnilib.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

InvalidStateError raised

pazzarpj opened this issue · comments

Description

TL;DR I think there is a race condition in setting the future's result using get_loop(future).call_soon_threadsafe(future.set_result, result) causing the InvalidStateError to be raised if the future is already cancelled/timeout

I am running sqlite on my embedded PC for pulling messages from the SQLite database

    async def get_all_unsent(self, query_batch: int = 100) -> typing.AsyncGenerator:
        _id = 0
        while True:
            results_q = await self.conn.execute(
                "SELECT * from mqtt WHERE mqtt.id > ? ORDER BY id LIMIT ?",
                (_id, query_batch),
            )
            headings = [desc[0] for desc in results_q.description]
            results = await results_q.fetchall()
            if not results:
                break
            for res in results:
                res = {key: value for key, value in zip(headings, res)}
                _id = res.pop("id")
                yield res

In my logs I got the following errors

handle: <Handle Future.set_result(<sqlite3.Curs...at 0xa0e887a0>)>
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
asyncio.base_futures.InvalidStateError: invalid state
2020-09-02 23:37:41,690 MainThread      ERROR    base_events    :1604     Exception in callback Future.set_result(<sqlite3.Curs...at 0xa0da0b60>)
handle: <Handle Future.set_result(<sqlite3.Curs...at 0xa0da0b60>)>
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
asyncio.base_futures.InvalidStateError: invalid state
2020-09-02 23:44:08,571 MainThread      ERROR    base_events    :1604     Exception in callback Future.set_result(<sqlite3.Curs...at 0xa0da9460>)
handle: <Handle Future.set_result(<sqlite3.Curs...at 0xa0da9460>)>
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
asyncio.base_futures.InvalidStateError: invalid state

Looking through the code I noticed that there is a possible race condition due to the main run loop running in a thread. If the future is cancelled before the threadsafe call_soon sets it, we would get an InvalidStateError.

def run(self) -> None:
        """
        Execute function calls on a separate thread.
        :meta private:
        """
        while self._running:
            try:
                future, function = self._tx.get(timeout=0.1)
            except Empty:
                continue

            try:
                LOG.debug("executing %s", function)
                result = function()
                LOG.debug("returning %s", result)
                get_loop(future).call_soon_threadsafe(future.set_result, result)
            except BaseException as e:
                LOG.exception("returning exception %s", e)
                get_loop(future).call_soon_threadsafe(future.set_exception, e)

One unanswered question I have is that I didn't do anything to explicitly cancel or set the future's result and so I'm not sure how it got into this InvalidState.

Details

  • OS: Debian 8
  • Python version: 3.7.4
  • aiosqlite version: 0.15.0
  • Can you repro on 'main' branch? Hard to reproduce
  • Can you repro in a clean virtualenv? Hard to reproduce

I looked into it further and it is not my query to get all unsent messages but my insert query. I have a delayed insert that I can cancel before the query completes.

I'm now pretty sure that the issue is with the call_soon_threadsafe directly calling future.set_result and future.set_exception.