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.