Hi Jochen,

For the first issue, "`channel.close()` is never called [if an
exception is thrown]": I believe this is the reason for `defer`
existing now. I rewrote your `async` block to the following, and it
should solve this particular issue:

```
async
{
     defer channel.close() // Must be before resource bound code
     for (int i = 1; i <= 3; I++)
     {
         someCall()
         channel.send(i * i)
     }
}
```

For the second issue, "exception[s] caused by `someCall()` is
swallowed": This is quite interesting and a good call out because it
_could_ be potentially surprising behavior depending on how a user
interprets what a standalone `async` closure means.

>From my understanding and investigation of such, the lack of the
exception propagating out appears to not be a bug per se but rather a
deliberate design decision for how standalone `async` usages are
intended work: Essentially, they are interpreted as the user
explicitly invoking the closure in a "fire-and-forget" style that
expressly disregards any results/errors the closure may or may not
provide.

Otherwise, the user _must_ retain the `Awaitable` object instance
returned by `async` and appropriately `await` it where relevant in
their code.

With that ind mind, you pretty much had it: I rewrote your code to the
following and it works as intended, at least in so far as I believe
what the design intends:

```
def channel = AsyncChannel.create(2)

// send data to channel in another thread
def promise = async
{
     defer channel.close()
     for (int i = 1; i <= 3; I++)
     {
         someCall()
         channel.send(i * i)
     }
}

// print all values send till the channel closed
for await (value in channel)
{
     println value
}

await promise
```

I wonder if that "looks ugly" or not then?

The bigger open question though is: "What should a standalone `async`
closure usage be interpreted as meaning?" or to put more detailed:
"When the user does `async { ... }` did they most likely mean to
ignore any thrown exceptions in that closure since they wanted
fire-and-forget semantics OR do they still want exceptions to bubble
up-and-out, no matter what?”.

Personally, I lean towards the later since I think it's less
surprising and less astonishing to _not_ ever swallow errors /
silently fail. However, I could see the rationale for the other side
too.

Curious to hear others' thoughts on this too.

Thanks,
Matt


------ Original Message ------
>From "Jochen Theodorou via dev" <[email protected]>
To [email protected]
Cc "Jochen Theodorou" <[email protected]>
Date 10/16/2026 6:49:28 PM
Subject missing finally handling in async/await

>
>
>Hi all,
>
>
>
>```groovy
>
>def channel = AsyncChannel.create(2)
>
>
>
>// send data to channel in another thread
>
>async {
>
>      for (int i = 1; i <= 3; i++) {
>
>          someCall()
>
>          channel.send(i * i)
>
>      }
>
>      channel.close()
>
>}
>
>
>
>// print all values send till the channel closed
>
>for await (value in channel) {
>
>      println value
>
>}
>
>```
>
>
>
>This code has two problems is `someCall()` throws an exception:
>
>* `channel.close()` is never called
>
>* the exception caused by `someCall()` is swallowed and it is left to
>
>the global async exception handler to do something with it. The fix
>
>looks ugly:
>
>
>
>```groovy
>
>def channel = AsyncChannel.create(2)
>
>
>
>// send data to channel in another thread
>
>def promise = async {
>
>      try {
>
>          for (int i = 1; i <= 3; i++) {
>
>              someCall()
>
>              channel.send(i * i)
>
>          }
>
>      } finally {
>
>          // make sure the channel still closes in case of an exception
>
>          channel.close()
>
>      }
>
>}
>
>
>
>// print all values send till the channel closed
>
>for await (value in channel) {
>
>      println value
>
>}
>
>
>
>// throw exception if there was any
>
>await promise
>
>```
>
>
>
>`for await` cannot solve the problem here, because `promise` carries the
>
>exception, not `channel`. `async {...} finally {...}` could work, but I
>
>hope somebody has a better suggestion. Especially since the example here
>
>has a shared construct, but that may not always be the case. And then
>
>this would mean to have to move things, that could stay in the async, to
>
>outside of the async. So I am not really happy with that idea
>
>
>
>bye Jochen

Reply via email to