You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/05/01 10:10:08 UTC
(groovy-website) branch asf-site updated: draft blog posts (tweaked)
This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 434e4d3 draft blog posts (tweaked)
434e4d3 is described below
commit 434e4d3d8e14eea2ce5a6242063b9b72f7afa05d
Author: Paul King <pa...@asert.com.au>
AuthorDate: Fri May 1 20:09:59 2026 +1000
draft blog posts (tweaked)
---
site/src/site/blog/groovy-mina.adoc | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/site/src/site/blog/groovy-mina.adoc b/site/src/site/blog/groovy-mina.adoc
index 7bbc337..cdbcf27 100644
--- a/site/src/site/blog/groovy-mina.adoc
+++ b/site/src/site/blog/groovy-mina.adoc
@@ -158,15 +158,20 @@ Here is the entire MINA-facing surface of our server:
----
class TickerHandler extends IoHandlerAdapter {
static final String INBOX = 'inbox'
+ static final String TASK = 'task'
TickerRegistry registry
@Override
void sessionOpened(IoSession session) {
var inbox = AsyncChannel.create(64) // bounded => back-pressure
session.setAttribute(INBOX, inbox)
- async {
+ var task = async {
new TickerProtocol(session: session, inbox: inbox, registry: registry).run()
}
+ task.whenComplete { _, throwable ->
+ if (throwable) exceptionCaught(session, throwable)
+ }
+ session.setAttribute(TASK, task)
}
@Override
@@ -177,12 +182,14 @@ class TickerHandler extends IoHandlerAdapter {
@Override
void sessionClosed(IoSession session) {
session.getAttribute(INBOX)?.close()
+ session.getAttribute(TASK)?.cancel()
}
@Override
void exceptionCaught(IoSession session, Throwable cause) {
cause.printStackTrace()
session.getAttribute(INBOX)?.close()
+ session.getAttribute(TASK)?.cancel()
session.closeNow()
}
}
@@ -200,7 +207,12 @@ blocks in normal operation.
* `async { }` launches the per-session task on a virtual thread (on JDK 21+). On JDK 17--20
it falls back to a cached thread pool. Either way, there is no `ExecutorFilter` and no SEDA
tuning: virtual threads handle the I/O parking for us.
-* `sessionClosed` closes the channel. The `for await` loop reading from it will then exit
+* The `Awaitable` returned by `async { }` is observed via `whenComplete`, so any failure
+inside the protocol task surfaces through `exceptionCaught` instead of vanishing into a
+dropped future. The same `Awaitable` is stashed on the session so that `sessionClosed` and
+`exceptionCaught` can cancel it as a backstop -- binding the protocol task's lifetime to
+the session.
+* `sessionClosed` closes the channel. The `for` loop reading from it will then exit
cleanly, and the per-session async task will return.
== The protocol, top-to-bottom
@@ -454,8 +466,9 @@ plus a single `runProtocol` method that reads top-to-bottom.
Each Groovy 6 feature earned its place:
-* `async { }` and `for await` flatten the per-session loop, with virtual threads doing the
-parking on blocking I/O.
+* `async { }`, `for` over channels, and `for await` over reactive streams flatten the
+per-session protocol into top-to-bottom code, with virtual threads doing the parking on
+blocking I/O.
* `AsyncChannel` bridges between MINA's callback-driven world and the linear protocol
loop, with bounded buffers providing natural back-pressure.
* `BroadcastChannel` and its `asPublisher()` view give us pub/sub broadcast for free, with