When investigating the source code of qasync/asyncio while working on #113, I found out about the following difference in behaviour when the loop closes.
asyncio.BaseEventLoop.close does not wait for its concurrent.futures.ThreadPoolExecutor to shut down:
if executor is not None:
self._default_executor = None
executor.shutdown(wait=False)
qasync.QEventLoop.close waits for its qasync.QThreadExecutor to shut down (because argument wait=True by default)
if self.__default_executor is not None:
self.__default_executor.shutdown()
Was this intended?
I could see issues happening if an app closes while some thread was in the middle of operation, so this could serve as an intended safeguard.
On the other Hand, this behaviour causes the app to hang on exit if there's a long-lasting blocking function running in executor (uncontrolled by developer, invoked by other library, for instance getaddrinfo which hangs if a domain is internal, known to device, but currently unavailable).
An additional thing to consider, is that in newer python versions, a "fix" was added to concurrent.futures.ThreadPoolExecutor that prevents an interpreter from fully shutting down until all ThreadPoolExecutor child threads complete. But it's using an atexit hook that joins those threads.
Another thing to consider, is that in newer python versions, a "fix" was added to concurrent.futures.ThreadPoolExecutor that prevents an interpreter from fully shutting down until all ThreadPoolExecutor child threads complete. But it's using an atexit hook that joins those threads. Doing it similarly would require a more drastic change to the code.
When investigating the source code of qasync/asyncio while working on #113, I found out about the following difference in behaviour when the loop closes.
asyncio.BaseEventLoop.close
does not wait for itsconcurrent.futures.ThreadPoolExecutor
to shut down:qasync.QEventLoop.close
waits for itsqasync.QThreadExecutor
to shut down (because argument wait=True by default)Was this intended?
I could see issues happening if an app closes while some thread was in the middle of operation, so this could serve as an intended safeguard.
On the other Hand, this behaviour causes the app to hang on exit if there's a long-lasting blocking function running in executor (uncontrolled by developer, invoked by other library, for instance
getaddrinfo
which hangs if a domain is internal, known to device, but currently unavailable).An additional thing to consider, is that in newer python versions, a "fix" was added to
concurrent.futures.ThreadPoolExecutor
that prevents an interpreter from fully shutting down until all ThreadPoolExecutor child threads complete. But it's using anatexit
hook thatjoins
those threads.Another thing to consider, is that in newer python versions, a "fix" was added to
concurrent.futures.ThreadPoolExecutor
that prevents an interpreter from fully shutting down until all ThreadPoolExecutor child threads complete. But it's using anatexit
hook thatjoins
those threads. Doing it similarly would require a more drastic change to the code.