# Structured Concurrency Kickoff

**URL:** <https://trio.discourse.group/t/structured-concurrency-kickoff/55>\
**Category:** Structured concurrency\
**Created:** [February 10, 2019, 7:06am UTC](https://trio.discourse.group/t/structured-concurrency-kickoff/55 "2019-02-10T07:06:15Z")\
**Posts on this page:** 1\
**Showing post:** 15

<div class="post-metadata">

**Author:** ![njs](https://yyz2.discourse-cdn.com/free1/user_avatar/trio.discourse.group/njs/32/11_2.png) [@njs](https://trio.discourse.group/u/njs)\
**Post date:** [February 13, 2019, 6:18am UTC](https://trio.discourse.group/t/structured-concurrency-kickoff/55/15 "2019-02-13T06:18:43Z")

</div>

> [@sustrik](#):
>
> // lifetime is automatically bound to the enclosing scope

I actually like the reified lifetimes that nurseries give you. I just posted some of the reasons why in the [ZIO thread](https://trio.discourse.group/t/zio-scala-library/72/3). And of course you also need them for objects that encapsulate a nursery. E.g. in Trio you write:

```python
async with open_websocket("https://...") as ws:
    message = await ws.receive()
    await ws.send("reply")

```

Here the `async with open_websocket` internally opens a nursery, whose scope extends over the `open_websocket`’s block. The `ws` object internally holds a reference to this nursery, so it’s helpful that it is an object :-). But code inside the `async with` block doesn’t have any way to access this nursery directly. (For example, it can’t spawn new tasks into it.) So reified lifetime objects are really useful for encapsulation and abstraction.

Also note that you can construct a reified nursery from an implicit nursery, though it’s kind of awkward:

```python
# In a made-up language with `go` statements that are implicitly scoped to the
# surrounding function
def pseudo_nursery_manager(nursery):
    while True:
        thunk = nursery.receive()
        go thunk

```

Now whenever I want a nursery object that I can pass around, I write:

```python
def uses_pseudo_nursery():
    nursery = open_channel()
    go pseudo_nursery_manager(nursery)
    # These both get to spawn tasks into my psuedo-nursery by
    # sending on the channel
    foo(nursery)
    bar(nursery)

```

Doing things this way is clunky and awkward, but you haven’t actually stopped people from shooting themselves in the foot if they really try :-).

Anyway… disallowing “dynamic” / “heap-allocated” nurseries actually works pretty well for us. And you have to admit: heap-allocated nurseries are a wishy-washy compromise that lets unstructured control-flow leak into your language. Have the courage of your convictions 😉

I think the key thing you need to make Trio’s approach practical is to have a language that lets users define their own “block types”. So like in Python, anyone can invent a new kind of `with` block. And that’s what allows `open_nursery` to be encapsulated inside some user-defined function like `open_websocket` – they just have to make their function a `with` block.

In many modern languages, the way you would do this is instead to use some kind of closure/block syntax. Like in JS you’d probably make the primitive

```js
withNursery(nursery => {
    # ... code that uses nursery ...
})

```

And then for the websocket, you’d do:

```js
withWebsocket(url, ws => {
    # ... code that uses ws ...
})

```

There are similar idiomatic features in Ruby, Swift, Rust, etc. I think this is the key feature that C is missing, that’s making your life difficult.

> [@sustrik](#):
>
> So, my thinking was: Can’t we route the Ctrl+C event **always** to the main thread?

This is actually what Trio does when it gets a Ctrl+C at an awkward time where it can’t deliver it immediately – it sets a flag, and then uses its cancellation system to inject the `KeyboardInterrupt` into the main task at the next available opportunity. But, this isn’t for the reason you suggest :-). Trio does it this way because it needs to deliver it to _some_ task, and the main task is guaranteed to always be there, so it’s a convenient choice. But it doesn’t help with the issue you’re thinking of: in Trio the main task is not really special with respect to exception handling, and the `KeyboardInterrupt` could still get lost, if one of the main task’s children crashes while the `KeyboardInterrupt` is propagating.

> [@sustrik](#):
>
> As for the common vocabulary, that’s what I’ve tried to do, but as already said, I am not sure my list is exhaustive.

Oh sorry, when I said “common vocabulary”, I meant, a generic way for all Trio apps to talk about it – so if my app has an embedded HTTP server, an embedded websocket server, and something else, and they’re all written by different third parties, then it’s very helpful if there’s a standard uniform way to say “All right all of you, do a graceful shutdown”.

> [@sustrik](#):
>
> On the technical level, I would advice against making “soft cancel” part of the language. I’ve tried that and it resulted in a lot of complexity and even more importantly it made the entire “structured” thing much less obvious and intuitive.

I’d like to hear more about this! I was thinking it seemed like a pretty small and natural extension, that fits naturally with the “structured thing”; since we already have a way to deliver a cancellation at a branch of the task tree, extend that mechanism to deliver soft-cancellations as well.

> [@sustrik](#):
>
> Why not simply say that graceful shutdown is to be handled by the user. The user can open a channel to the thread, send it a “shutdown in 10 secs” message, then wait for 10 secs and cancel it by exiting the scope

We’ve experimented with “user space” implementations of this. But a channel is pretty awkward here. Take our accept loop:

```python
while True:
    conn = listener.accept()
    nursery.start_soon(handler, conn)

```

The `accept` call might block indefinitely. But when the graceful shutdown is requested, we want the `accept` call to exit immediately (while any `handler`s are allowed to keep running of course). So if we use a channel for this, then it means we need some kind of accept-a-socket-or-else-receive-from-a-channel operation, which is really difficult. (For regular OS sockets it’s _possible_, if you go all [concurrent ML](https://wingolog.org/archives/2017/06/29/a-new-concurrent-ml), but that’s a whole pile of complexity that you don’t need just for this use case, and it doesn’t necessarily work for cases where `listener` is more complicated than a bare OS socket.)

Instead, we’d write:

```python
while True:
    with cancel_if_graceful_shutdown_requested as cancel_scope:
        conn = listener.accept()
        nursery.start_soon(handler, conn)
    if cancel_scope.was_cancelled:
         # Graceful shutdown requested
        break

```

> [@sustrik](#):
>
> EDIT: Actually, this is a problem I’ve banged my head against for a year or so before I realized how to solve it. I’ll write a separate post about it.

Looking forward to it 🙂

---

_[View the full topic](https://trio.discourse.group/t/structured-concurrency-kickoff/55)._
