-
-
Notifications
You must be signed in to change notification settings - Fork 719
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
[WIP] Async stream handlers #4474
Changes from all commits
36e026e
d682c13
d80cf8d
6b90ee8
bebc06e
4f1944b
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 |
---|---|---|
|
@@ -3,20 +3,25 @@ | |
from contextlib import suppress | ||
import logging | ||
import threading | ||
import typing | ||
import weakref | ||
|
||
from .core import CommClosedError | ||
from .metrics import time | ||
from .utils import sync, TimeoutError, parse_timedelta | ||
from .protocol.serialize import to_serialize | ||
|
||
if typing.TYPE_CHECKING: | ||
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 found adding type annotations to the 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. Does 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 had to remove 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. @jakirkham it doesn't help - you still need those imports. |
||
from .scheduler import Scheduler | ||
from .worker import Worker | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class PubSubSchedulerExtension: | ||
""" Extend Dask's scheduler with routes to handle PubSub machinery """ | ||
|
||
def __init__(self, scheduler): | ||
def __init__(self, scheduler: "Scheduler"): | ||
self.scheduler = scheduler | ||
self.publishers = defaultdict(set) | ||
self.subscribers = defaultdict(set) | ||
|
@@ -35,7 +40,7 @@ def __init__(self, scheduler): | |
|
||
self.scheduler.extensions["pubsub"] = self | ||
|
||
def add_publisher(self, comm=None, name=None, worker=None): | ||
async def add_publisher(self, comm=None, name=None, worker=None): | ||
logger.debug("Add publisher: %s %s", name, worker) | ||
self.publishers[name].add(worker) | ||
return { | ||
|
@@ -44,7 +49,7 @@ def add_publisher(self, comm=None, name=None, worker=None): | |
and len(self.client_subscribers[name]) > 0, | ||
} | ||
|
||
def add_subscriber(self, comm=None, name=None, worker=None, client=None): | ||
async def add_subscriber(self, comm=None, name=None, worker=None, client=None): | ||
if worker: | ||
logger.debug("Add worker subscriber: %s %s", name, worker) | ||
self.subscribers[name].add(worker) | ||
|
@@ -62,7 +67,7 @@ def add_subscriber(self, comm=None, name=None, worker=None, client=None): | |
) | ||
self.client_subscribers[name].add(client) | ||
|
||
def remove_publisher(self, comm=None, name=None, worker=None): | ||
async def remove_publisher(self, comm=None, name=None, worker=None): | ||
if worker in self.publishers[name]: | ||
logger.debug("Remove publisher: %s %s", name, worker) | ||
self.publishers[name].remove(worker) | ||
|
@@ -71,7 +76,7 @@ def remove_publisher(self, comm=None, name=None, worker=None): | |
del self.subscribers[name] | ||
del self.publishers[name] | ||
|
||
def remove_subscriber(self, comm=None, name=None, worker=None, client=None): | ||
async def remove_subscriber(self, comm=None, name=None, worker=None, client=None): | ||
if worker: | ||
logger.debug("Remove worker subscriber: %s %s", name, worker) | ||
self.subscribers[name].remove(worker) | ||
|
@@ -100,14 +105,14 @@ def remove_subscriber(self, comm=None, name=None, worker=None, client=None): | |
del self.subscribers[name] | ||
del self.publishers[name] | ||
|
||
def handle_message(self, name=None, msg=None, worker=None, client=None): | ||
async def handle_message(self, name=None, msg=None, worker=None, client=None): | ||
for c in list(self.client_subscribers[name]): | ||
try: | ||
self.scheduler.client_comms[c].send( | ||
{"op": "pubsub-msg", "name": name, "msg": msg} | ||
) | ||
except (KeyError, CommClosedError): | ||
self.remove_subscriber(name=name, client=c) | ||
await self.remove_subscriber(name=name, client=c) | ||
|
||
if client: | ||
for sub in self.subscribers[name]: | ||
|
@@ -119,7 +124,7 @@ def handle_message(self, name=None, msg=None, worker=None, client=None): | |
class PubSubWorkerExtension: | ||
""" Extend Dask's Worker with routes to handle PubSub machinery """ | ||
|
||
def __init__(self, worker): | ||
def __init__(self, worker: "Worker"): | ||
self.worker = worker | ||
self.worker.stream_handlers.update( | ||
{ | ||
|
@@ -136,15 +141,15 @@ def __init__(self, worker): | |
|
||
self.worker.extensions["pubsub"] = self # circular reference | ||
|
||
def add_subscriber(self, name=None, address=None, **info): | ||
async def add_subscriber(self, name=None, address=None, **info): | ||
for pub in self.publishers[name]: | ||
pub.subscribers[address] = info | ||
|
||
def remove_subscriber(self, name=None, address=None): | ||
async def remove_subscriber(self, name=None, address=None): | ||
for pub in self.publishers[name]: | ||
del pub.subscribers[address] | ||
|
||
def publish_scheduler(self, name=None, publish=None): | ||
async def publish_scheduler(self, name=None, publish=None): | ||
self.publish_to_scheduler[name] = publish | ||
|
||
async def handle_message(self, name=None, msg=None): | ||
|
@@ -384,6 +389,8 @@ def __init__(self, name, worker=None, client=None): | |
pubsub = self.worker.extensions["pubsub"] | ||
elif self.client: | ||
pubsub = self.client.extensions["pubsub"] | ||
else: | ||
raise ValueError("Must include a worker or client") | ||
self.loop.add_callback(pubsub.subscribers[name].add, self) | ||
|
||
msg = {"op": "pubsub-add-subscriber", "name": self.name} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ | |
import pkgutil | ||
import base64 | ||
import tblib.pickling_support | ||
from typing import Awaitable, Callable, TypeVar | ||
import xml.etree.ElementTree | ||
|
||
try: | ||
|
@@ -289,6 +290,21 @@ def quiet(): | |
return results | ||
|
||
|
||
T = TypeVar("T") | ||
|
||
|
||
def asyncify(func: Callable[..., T]) -> Callable[..., Awaitable[T]]: | ||
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. In some places I've used an async wrapper to have a lighter footprint on the API, but more functions could just be made async in the first place. 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 almost raised a question about this until I looked at how these methods were used. I agree that making them sync by default is probably the right choice. This seemed like a good solution to me. |
||
""" | ||
Wrap a synchronous function in an async one | ||
""" | ||
|
||
@functools.wraps(func) | ||
async def wrapped(*args, **kwargs): | ||
return func(*args, **kwargs) | ||
|
||
return wrapped | ||
|
||
|
||
def sync(loop, func, *args, callback_timeout=None, **kwargs): | ||
""" | ||
Run coroutine in loop running in separate thread. | ||
|
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.
as a personal esthetic preference, I find that using
from typing import ...
is always a nicer option. It does lead to collisions withfrom collections.abc import ...
, which can however be worked around after you drop Python 3.6 support, addfrom __future__ import annotations
everywhere, and you run a Python 3.9-compatible version of mypy (on python 3.7+).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.
I generally agree with you here @crusaderky. In this case I kept it this way because the thing I'm using is
typing.TYPE_CHECKING
, and I didn't really want that floating around the module namespace. No strong preference either way, though.