-
-
Notifications
You must be signed in to change notification settings - Fork 346
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
Is there an easier way to cancel groups of tasks? #141
Comments
That logic looks very like the nursery logic... Would it work to make a dedicated nursery for these children? So "this nursery's children" would be your group? async def dedicated_supervisor_task(q):
async with trio.open_nursery() as nursery:
await q.put(nursery)
# Hold this nursery open until it's cancelled
nursery.spawn(trio.sleep_forever)
async def connection_listener(q):
supervisor_nursery = await q.get()
while True:
conn = await accept_next_connection()
supervisor_nursery.spawn(connection_handler, conn)
async def main():
async with trio.open_nursery() as nursery:
q = trio.Queue(1)
nursery.spawn(dedicated_supervisor_task, q)
nursery.spawn(connection_listener, q) Then There are a few slightly awkward things about this that we might want to polish if it's a useful idiom. But the first question is: is it useful? The exact version above doesn't really accomplish anything useful; I don't know why you want a group of tasks like this, so I just took a set of tasks that in this case could have gone directly under I've been thinking about something like this for cases like "graceful shutdown" in a web server, where you want to stop accepting new connections but let the existing ones finish. One way to do that would be to put listener tasks into one group, and connection handler tasks into another, and then do a graceful shutdown by cancelling the listener tasks then waiting for the connection handler tasks to finish (or optionally cancelling them too if they haven't finished after a timeout). |
That isn't quite my use case but probably could be adapted. I need a set of tasks associated with each connection to a listener, so I would need to create a nursery per connection. Do nurseries have much overhead? A disadvantage of the nursery approach is that it gives up control of shutdown order. Behind the scenes how does the nursery end the tasks? Does it inject a cancel or just abandon the tasks? I keep looking for a task.cancel() or nursery.task_cancel(task) method without any luck :-) I am intrigued by the idea of grouping tasks. How might that look? |
Ah, right, then I think a very natural approach would be something like async def connection_handler(sock):
with sock:
async with trio.open_nursery as nursery:
nursery.spawn(first_worker, nursery) # spawns more tasks into this nursery as needed (adjust to fit)
Not really. And if it becomes a problem I'd rather work on cutting down the overhead than try to create something that's almost-the-same but different :-)
Hmm, interesting point. I made the conscious decision to initially support only "group" cancellation with cancel scopes to keep things simple internally and keep cancellation and tasks more orthogonal, but I can see how targeting individual tasks might be useful for e.g. rest-for-one supervisors. Plus whatever your mysterious use case is :-)
Eek, yes, it cancels them. Trio never ever ever abandons tasks, that's like the number one rule that the entire rest of the design is warped to fit :-).
As alluded to above, there is none (currently) – just |
I've been able to greatly simplify my websocket example code (#124) using nurseries to manage per-connection tasks. I still think it might be useful to add a task.cancel() method for fine-grained control but will save that for another issue (when I can think of a good use case :-) |
I'm struggling with something similar, I think.
I built the following based on the suggestion above: async def generate_task_holder_nurseries(queue):
while True:
async with trio.open_nursery() as nursery:
await queue.put(nursery)
# Wait until the nursery is cancelled
nursery.start_soon(trio.sleep_forever)
async with trio.open_nursery() as nursery:
task_holder_nursery_queue = trio.Queue(1)
nursery.start_soon(generate_task_holder_nurseries, task_holder_nursery_queue)
current_task_holder = None
async for message in websocket:
if current_task_holder:
current_task_holder.cancel_scope.cancel()
current_task_holder = await task_holder_nursery_queue.get()
for foobar in message:
current_task_holder.start_soon(task, foobar) That seems to work, but is there/should there be an easier way? I also think there might still be some KeyboardInterrupt/cancellation issues I need to handle correctly, so it's not even that straightforward. |
Another way would be to explicitly call the iterator.
|
@smurfix Clever! |
@miracle2k except for forgetting the |
Another way would be for the tasks to pass back a local cancel scope for later cancellation, i.e.
The advantage (or disadvantage – depends on your actual usecase) is that this way doesn't wait for all the old tasks to clean themselves up before starting new tasks. |
I am struggling with a good way to manage groups of tasks. The best I've come up with seems a bit convoluted, involving a factory function. Something along the lines of:
Am I missing an easy way to manage groups of tasks (eg associated with each connection to a server)?
Is it possible to cancel a task directly instead of tracking separate cancel_scopes? Something like:
The text was updated successfully, but these errors were encountered: