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