-
Notifications
You must be signed in to change notification settings - Fork 76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: asyncio
LoopExecutor and async fsspec source
#992
Changes from 25 commits
fbe57df
7578d35
cfb3c45
c588cea
7dd99fb
3cf0866
b7aad49
8b218e4
3624f87
62bcd51
3d4435c
96f9a6e
ae5bcef
b508df9
803158b
549ae7d
7bc543c
202783a
c020ae2
300c8fe
69ea245
4f01c1e
0a161bc
c615eca
ef1f022
ffbd7cd
ba7a6bf
80049db
a6500fc
5c2c9ae
49ba900
d57f499
1baa3f4
bcc9e7b
5be63b0
ff07ab3
1aefc5f
a710ea0
0a1c46b
08349cb
0210d0f
60b892a
17ec460
4fc0206
8b96409
b5065ca
bbd4ecc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,9 @@ | |
These classes implement a *subset* of Python's Future and Executor interfaces. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
import asyncio | ||
import os | ||
import queue | ||
import sys | ||
|
@@ -224,7 +227,7 @@ def num_workers(self) -> int: | |
return len(self._workers) | ||
|
||
@property | ||
def workers(self): | ||
def workers(self) -> list[Worker]: | ||
""" | ||
A list of workers (:doc:`uproot.source.futures.Worker`). | ||
""" | ||
|
@@ -315,7 +318,7 @@ class ResourceWorker(Worker): | |
executes. | ||
""" | ||
|
||
def __init__(self, work_queue, resource): | ||
def __init__(self, work_queue: queue.Queue, resource): | ||
super().__init__(work_queue) | ||
self._resource = resource | ||
|
||
|
@@ -469,3 +472,45 @@ def __exit__(self, exception_type, exception_value, traceback): | |
self.shutdown() | ||
self._resource.__exit__(exception_type, exception_value, traceback) | ||
self._closed = True | ||
|
||
|
||
class LoopExecutor: | ||
def __repr__(self): | ||
return f"<LoopExecutor at 0x{id(self):012x}>" | ||
|
||
def __init__(self): | ||
self._loop = asyncio.new_event_loop() | ||
# TODO: remove daemon=True (or not?) | ||
self._thread = threading.Thread(target=self._run, daemon=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I agree, it should be possible to do this cleanup correctly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I only added this as part of the quick fix. The |
||
self.start() | ||
|
||
def start(self): | ||
self._thread.start() | ||
return self | ||
|
||
def shutdown(self): | ||
self._loop.call_soon_threadsafe(self._loop.stop) | ||
self._thread.join() | ||
|
||
def _run(self): | ||
asyncio.set_event_loop(self._loop) | ||
try: | ||
self._loop.run_forever() | ||
finally: | ||
self._loop.run_until_complete(self._loop.shutdown_asyncgens()) | ||
self._loop.close() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably need to run cleanups here too, and shield these from cancellation # Find all running tasks:
pending = asyncio.all_tasks()
# Run loop until tasks done:
loop.run_until_complete(
asyncio.gather(*[
asyncio.shield(t) for t in pending
]
)
) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is mostly my fault. I don't understand why there isn't a canned "loop-in-a-thread" in the stdlib or elsewhere. So we're designing one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nsmith I wouldn't ascribe fault — asyncio is a bit of an ugly beast (I use The intention with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We effectively want this: https://github.com/python/cpython/blob/3.8/Lib/asyncio/runners.py#L8-L73 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes! The previous error was an unhandled exception, and I would not be surprised if we have another one. |
||
|
||
def __enter__(self): | ||
self.start() | ||
return self | ||
|
||
def __exit__(self, exc_type, exc_val, exc_tb): | ||
self.shutdown() | ||
|
||
@property | ||
def loop(self) -> asyncio.AbstractEventLoop: | ||
return self._loop | ||
|
||
def submit(self, coroutine, *args) -> asyncio.Future: | ||
coroutine_object = coroutine(*args) | ||
return asyncio.run_coroutine_threadsafe(coroutine_object, self._loop) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is some of the problematic code. This fix was added by @nsmith- and fixed one of the race conditions. I am not sure if this is still the problem though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, the issue that was fixed was not a race condition but a initialization routine accessing some data that is only (apparently) valid when accessed from the thread running the event loop. So we schedule the initialization in that loop and wait for it here.