[ 
https://issues.apache.org/jira/browse/GROOVY-9381?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18067186#comment-18067186
 ] 

ASF GitHub Bot commented on GROOVY-9381:
----------------------------------------

Copilot commented on code in PR #2387:
URL: https://github.com/apache/groovy/pull/2387#discussion_r2968075727


##########
src/main/java/groovy/concurrent/AwaitableAdapterRegistry.java:
##########
@@ -0,0 +1,343 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+import org.apache.groovy.runtime.async.GroovyPromise;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+/**
+ * Central registry for {@link AwaitableAdapter} instances.
+ * <p>
+ * On class-load, adapters are discovered via {@link ServiceLoader} from
+ * {@code META-INF/services/groovy.concurrent.AwaitableAdapter}. A built-in
+ * adapter is always present as the lowest-priority fallback, handling:
+ * <ul>
+ *   <li>{@link CompletableFuture} and {@link CompletionStage}</li>
+ *   <li>{@link Future} (adapted via a blocking wrapper)</li>
+ * </ul>
+ * <p>
+ * JDK {@link java.util.concurrent.Flow.Publisher} support is provided by
+ * the separately registered
+ * {@link org.apache.groovy.runtime.async.FlowPublisherAdapter}, which is
+ * auto-discovered via ServiceLoader.  Third-party frameworks (Reactor,
+ * RxJava, etc.) may register their own higher-priority adapters.
+ * <p>
+ * Additional adapters can be registered at runtime via {@link #register}.
+ *
+ * @see AwaitableAdapter
+ * @since 6.0.0
+ */
+public class AwaitableAdapterRegistry {
+
+    private static final List<AwaitableAdapter> ADAPTERS = new 
CopyOnWriteArrayList<>();
+
+    /**
+     * Per-class adapter cache for {@link #toAwaitable(Object)}.
+     * Uses {@link ClassValue} for lock-free, GC-friendly per-class memoization
+     * that does not prevent class unloading — superior to
+     * {@code ConcurrentHashMap<Class<?>, V>} which holds strong references
+     * that can cause ClassLoader leaks in container environments.
+     * <p>
+     * Rebuilt via volatile reference swap when adapters are registered or
+     * removed (an extremely low-frequency operation).
+     */
+    private static volatile ClassValue<AwaitableAdapter> awaitableCache = 
buildAwaitableCache();
+
+    /**
+     * Per-class adapter cache for {@link #toAsyncStream(Object)}.
+     * Same caching strategy as {@link #awaitableCache}.
+     */
+    private static volatile ClassValue<AwaitableAdapter> streamCache = 
buildStreamCache();
+
+    /**
+     * Optional executor supplier for blocking Future adaptation, to avoid
+     * starving the common pool. Defaults to null; when set, the provided
+     * executor is used instead of {@code CompletableFuture.runAsync}'s 
default.
+     */
+    private static volatile Executor blockingExecutor;
+
+    static {
+        // SPI-discovered adapters
+        for (AwaitableAdapter adapter : 
ServiceLoader.load(AwaitableAdapter.class)) {
+            ADAPTERS.add(adapter);
+        }
+        // Built-in fallback (lowest priority)
+        ADAPTERS.add(new BuiltInAdapter());
+    }
+
+    private AwaitableAdapterRegistry() { }
+
+    /**
+     * Registers an adapter with higher priority than existing ones.
+     * <p>
+     * Invalidates the per-class adapter caches so that subsequent lookups
+     * re-evaluate adapter priority order.
+     *
+     * @param adapter the adapter to register; must not be {@code null}
+     * @return an {@link AutoCloseable} that, when closed, removes this adapter
+     *         from the registry. Useful for test isolation.
+     */
+    public static AutoCloseable register(AwaitableAdapter adapter) {
+        ADAPTERS.add(0, adapter);
+        invalidateCaches();
+        return () -> { ADAPTERS.remove(adapter); invalidateCaches(); };
+    }

Review Comment:
   `register`/`unregister` accept `null` despite the Javadoc stating “must not 
be null”. Registering `null` will later trigger NPEs during adapter 
scans/caching. Add an explicit null check (e.g., 
`Objects.requireNonNull(adapter, "...")`) to fail fast with a clear message.



##########
src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java:
##########
@@ -1699,6 +1802,12 @@ public MethodNode visitMethodDeclaration(final 
MethodDeclarationContext ctx) {
         } else { // script method declaration
             methodNode = createScriptMethodNode(modifierManager, methodName, 
returnType, parameters, exceptions, code);
         }
+
+        // Inject @Async annotation for methods declared with the 'async' 
keyword modifier
+        if (isAsync) {
+            methodNode.addAnnotation(new 
AnnotationNode(ClassHelper.make("groovy.transform.Async")));
+        }

Review Comment:
   The injected annotation uses `ClassHelper.make("groovy.transform.Async")` 
while `AsyncASTTransformation` filters by 
`MY_TYPE.equals(anno.getClassNode())`. Since `ClassNode#equals` is 
identity-based, this can be brittle if the ClassNode instances don’t match 
(e.g., caching differences / classloader scenarios), potentially preventing the 
transformation from running for `async` methods. Prefer creating the annotation 
using the same class reference 
(`ClassHelper.make(groovy.transform.Async.class)` with an import), or relax the 
transformation’s check to compare by class name instead of `equals`.



##########
src/main/java/groovy/concurrent/AwaitableAdapterRegistry.java:
##########
@@ -0,0 +1,343 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+import org.apache.groovy.runtime.async.GroovyPromise;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+/**
+ * Central registry for {@link AwaitableAdapter} instances.
+ * <p>
+ * On class-load, adapters are discovered via {@link ServiceLoader} from
+ * {@code META-INF/services/groovy.concurrent.AwaitableAdapter}. A built-in
+ * adapter is always present as the lowest-priority fallback, handling:
+ * <ul>
+ *   <li>{@link CompletableFuture} and {@link CompletionStage}</li>
+ *   <li>{@link Future} (adapted via a blocking wrapper)</li>
+ * </ul>
+ * <p>
+ * JDK {@link java.util.concurrent.Flow.Publisher} support is provided by
+ * the separately registered
+ * {@link org.apache.groovy.runtime.async.FlowPublisherAdapter}, which is
+ * auto-discovered via ServiceLoader.  Third-party frameworks (Reactor,
+ * RxJava, etc.) may register their own higher-priority adapters.
+ * <p>
+ * Additional adapters can be registered at runtime via {@link #register}.
+ *
+ * @see AwaitableAdapter
+ * @since 6.0.0
+ */
+public class AwaitableAdapterRegistry {
+
+    private static final List<AwaitableAdapter> ADAPTERS = new 
CopyOnWriteArrayList<>();
+
+    /**
+     * Per-class adapter cache for {@link #toAwaitable(Object)}.
+     * Uses {@link ClassValue} for lock-free, GC-friendly per-class memoization
+     * that does not prevent class unloading — superior to
+     * {@code ConcurrentHashMap<Class<?>, V>} which holds strong references
+     * that can cause ClassLoader leaks in container environments.
+     * <p>
+     * Rebuilt via volatile reference swap when adapters are registered or
+     * removed (an extremely low-frequency operation).
+     */
+    private static volatile ClassValue<AwaitableAdapter> awaitableCache = 
buildAwaitableCache();
+
+    /**
+     * Per-class adapter cache for {@link #toAsyncStream(Object)}.
+     * Same caching strategy as {@link #awaitableCache}.
+     */
+    private static volatile ClassValue<AwaitableAdapter> streamCache = 
buildStreamCache();
+
+    /**
+     * Optional executor supplier for blocking Future adaptation, to avoid
+     * starving the common pool. Defaults to null; when set, the provided
+     * executor is used instead of {@code CompletableFuture.runAsync}'s 
default.
+     */
+    private static volatile Executor blockingExecutor;
+
+    static {
+        // SPI-discovered adapters
+        for (AwaitableAdapter adapter : 
ServiceLoader.load(AwaitableAdapter.class)) {
+            ADAPTERS.add(adapter);
+        }
+        // Built-in fallback (lowest priority)
+        ADAPTERS.add(new BuiltInAdapter());
+    }
+
+    private AwaitableAdapterRegistry() { }
+
+    /**
+     * Registers an adapter with higher priority than existing ones.
+     * <p>
+     * Invalidates the per-class adapter caches so that subsequent lookups
+     * re-evaluate adapter priority order.
+     *
+     * @param adapter the adapter to register; must not be {@code null}
+     * @return an {@link AutoCloseable} that, when closed, removes this adapter
+     *         from the registry. Useful for test isolation.
+     */
+    public static AutoCloseable register(AwaitableAdapter adapter) {
+        ADAPTERS.add(0, adapter);
+        invalidateCaches();
+        return () -> { ADAPTERS.remove(adapter); invalidateCaches(); };
+    }
+
+    /**
+     * Removes the given adapter from the registry.
+     * <p>
+     * Invalidates the per-class adapter caches so that subsequent lookups
+     * no longer consider the removed adapter.
+     *
+     * @param adapter the adapter to remove
+     * @return {@code true} if the adapter was found and removed
+     */
+    public static boolean unregister(AwaitableAdapter adapter) {
+        boolean removed = ADAPTERS.remove(adapter);
+        if (removed) {
+            invalidateCaches();
+        }
+        return removed;
+    }

Review Comment:
   `register`/`unregister` accept `null` despite the Javadoc stating “must not 
be null”. Registering `null` will later trigger NPEs during adapter 
scans/caching. Add an explicit null check (e.g., 
`Objects.requireNonNull(adapter, "...")`) to fail fast with a clear message.



##########
src/main/java/org/apache/groovy/runtime/async/FlowPublisherAdapter.java:
##########
@@ -0,0 +1,385 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.runtime.async;
+
+import groovy.concurrent.AsyncStream;
+import groovy.concurrent.Awaitable;
+import groovy.concurrent.AwaitableAdapter;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Flow;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Bridges JDK {@link Flow.Publisher} instances into Groovy's
+ * {@link Awaitable}/{@link AsyncStream} world.
+ *
+ * <h2>Registration</h2>
+ * This adapter is auto-discovered by the
+ * {@link groovy.concurrent.AwaitableAdapterRegistry} via the
+ * {@code META-INF/services/groovy.concurrent.AwaitableAdapter} file.
+ * It handles any object that implements {@link Flow.Publisher}.
+ *
+ * <h2>Adaptation modes</h2>
+ * <ul>
+ *   <li><b>Single-value</b> ({@code await publisher}):
+ *       subscribes, takes the first {@code onNext} item, cancels the
+ *       upstream subscription, and completes the returned {@link 
Awaitable}.</li>
+ *   <li><b>Multi-value</b> ({@code for await (item in publisher)}):
+ *       wraps the publisher into an {@link AsyncStream} backed by a
+ *       bounded {@link LinkedBlockingQueue}, providing natural
+ *       back-pressure by requesting one item at a time.</li>
+ * </ul>
+ *
+ * <h2>Thread safety</h2>
+ * <p>All subscriber callbacks ({@code onSubscribe}, {@code onNext},
+ * {@code onError}, {@code onComplete}) are safe for invocation from
+ * any thread.  Subscription references use {@link AtomicReference}
+ * for safe publication and race-free cancellation.  The close path
+ * uses a CAS on an {@link AtomicBoolean} to guarantee exactly-once
+ * cleanup semantics.</p>
+ *
+ * <h2>Reactive Streams compliance</h2>
+ * <p>This adapter follows the Reactive Streams specification
+ * (JDK {@link Flow} variant) rules:</p>
+ * <ul>
+ *   <li>§2.5 — duplicate {@code onSubscribe} cancels the second 
subscription</li>
+ *   <li>§2.13 — {@code null} items in {@code onNext} are rejected 
immediately</li>
+ *   <li>All signals ({@code onNext}, {@code onError}, {@code onComplete}) use
+ *       blocking {@code put()} with a non-blocking {@code offer()} fallback
+ *       when the publisher thread is interrupted — this prevents both silent
+ *       item loss and consumer deadlock even under unexpected interrupts</li>
+ *   <li>Terminal callbacks atomically close the stream via a single
+ *       {@link AtomicBoolean} flag and clear/cancel the stored subscription.
+ *       This makes post-terminal {@code onNext} calls from non-compliant
+ *       publishers harmless and releases resources promptly.</li>
+ *   <li>Back-pressure is enforced by requesting exactly one item after
+ *       each consumed element; demand is signalled <em>before</em>
+ *       {@code moveNext()} returns, preventing livelock when producer and
+ *       consumer share the same thread pool</li>
+ * </ul>
+ *
+ * @see groovy.concurrent.AwaitableAdapterRegistry
+ * @see AsyncStream
+ * @since 6.0.0
+ */
+public class FlowPublisherAdapter implements AwaitableAdapter {
+
+    /**
+     * Returns {@code true} if the given type is assignable to
+     * {@link Flow.Publisher}, enabling single-value {@code await}.
+     *
+     * @param type the source type to check
+     * @return {@code true} if this adapter can handle the type
+     */
+    @Override
+    public boolean supportsAwaitable(Class<?> type) {
+        return Flow.Publisher.class.isAssignableFrom(type);
+    }
+
+    /**
+     * Returns {@code true} if the given type is assignable to
+     * {@link Flow.Publisher}, enabling multi-value {@code for await}.
+     *
+     * @param type the source type to check
+     * @return {@code true} if this adapter can produce an async stream
+     */
+    @Override
+    public boolean supportsAsyncStream(Class<?> type) {
+        return Flow.Publisher.class.isAssignableFrom(type);
+    }
+
+    /**
+     * Converts a {@link Flow.Publisher} to a single-value {@link Awaitable}
+     * by subscribing and taking the first emitted item.
+     *
+     * @param source the publisher instance
+     * @param <T>    the element type
+     * @return an awaitable that resolves to the first emitted value
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> Awaitable<T> toAwaitable(Object source) {
+        return publisherToAwaitable((Flow.Publisher<T>) source);
+    }
+
+    /**
+     * Converts a {@link Flow.Publisher} to a multi-value {@link AsyncStream}
+     * for use with {@code for await} loops.
+     *
+     * @param source the publisher instance
+     * @param <T>    the element type
+     * @return an async stream that yields publisher items
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> AsyncStream<T> toAsyncStream(Object source) {
+        return publisherToAsyncStream((Flow.Publisher<T>) source);
+    }
+
+    // --

> Add native async/await support
> ------------------------------
>
>                 Key: GROOVY-9381
>                 URL: https://issues.apache.org/jira/browse/GROOVY-9381
>             Project: Groovy
>          Issue Type: New Feature
>            Reporter: Daniel Sun
>            Priority: Major
>
> h2. Summary
> Introduce first-class {{{}async{}}}/{{{}await{}}} language support to Groovy, 
> enabling developers to write asynchronous code in a sequential, readable 
> style — on par with the async/await facilities in JavaScript (ES2017), C# 
> (5.0), Kotlin (coroutines), and Swift (5.5).
> h2. Motivation
> Modern JVM applications are overwhelmingly concurrent. Web services, data 
> pipelines, and reactive systems spend most of their time waiting for network 
> I/O, database queries, or downstream services. The JVM offers powerful but 
> low-level concurrency primitives ({{{}CompletableFuture{}}}, 
> {{{}Flow.Publisher{}}}, {{{}ExecutorService{}}}), and while libraries like 
> RxJava and Project Reactor raise the abstraction level, they introduce their 
> own learning curve and cannot alter the language's control-flow syntax.
> Today, a typical three-step async workflow in Groovy looks like this:
> {code:groovy}
> CompletableFuture.supplyAsync { fetchUserId() }
>     .thenCompose { id -> CompletableFuture.supplyAsync { lookupName(id) } }
>     .thenCompose { name -> CompletableFuture.supplyAsync { loadProfile(name) 
> } }
>     .exceptionally { ex -> handleError(ex) }
> {code}
> The business logic is obscured by plumbing. Exception handling is decoupled 
> from the code that raises exceptions, and the control flow reads inside-out.
> With the proposed {{{}async{}}}/{{{}await{}}}, the same logic becomes:
> {code:groovy}
> async fetchProfile() {
>     def id   = await fetchUserIdAsync()
>     def name = await lookupNameAsync(id)
>     return await loadProfileAsync(name)
> }
> {code}
> This reads identically to synchronous code. Standard 
> {{{}try{}}}/{{{}catch{}}}, {{{}for{}}}, {{{}if{}}}, and variable assignment 
> all compose naturally — no callbacks, no chained lambdas.
> h2. Scope
> This proposal introduces the following language constructs and runtime APIs:
> h3. Language Constructs
> ||Construct||Syntax||Description||
> |Async method|async ReturnType methodName(params) \{ ... }|Declares a method 
> that executes asynchronously and returns {{Awaitable<T>}}|
> |Await expression|{{await expr}} / {{await(expr)}}|Suspends the enclosing 
> async context until the awaited computation completes; transparently unwraps 
> the result|
> |Async closure / lambda|async \{ params -> body } / async (params) -> \{ body 
> }|Creates a reusable asynchronous function (must be explicitly invoked to 
> start execution)|
> |For-await loop|for await (item in source) \{ ... }|Iterates over an 
> {{{}AsyncStream{}}}, {{{}Flow.Publisher{}}}, or {{{}Iterable{}}}, resolving 
> each element asynchronously|
> |Yield return|{{yield return expr}}|Emits a value from an async generator 
> method, producing an {{AsyncStream<T>}} with natural back-pressure|
> |Defer|defer \{ cleanup } / {{defer cleanup}}|Schedules a cleanup block to 
> execute on method exit (LIFO order), inspired by Go's {{defer}}|
> |@Async annotation|{{@Async}} on method declarations|Annotation equivalent of 
> the {{async}} keyword modifier, for interoperability with 
> annotation-processing tools|
> h3. Public API ({{{}groovy.concurrent{}}} package)
> ||Class/Interface||Role||
> |{{Awaitable<T>}}|Core promise abstraction (analogous to C#'s {{Task<T>}} / 
> JS's {{{}Promise<T>{}}}). Provides static combinators ({{{}all{}}}, 
> {{{}any{}}}, {{{}allSettled{}}}, {{{}delay{}}}, {{{}timeout{}}}, 
> {{{}timeoutOr{}}}), factories ({{{}of{}}}, {{{}failed{}}}), and instance 
> continuation methods ({{{}then{}}}, {{{}thenCompose{}}}, 
> {{{}exceptionally{}}}, {{{}orTimeout{}}}, {{{}completeOnTimeout{}}})|
> |{{AsyncStream<T>}}|Asynchronous iteration abstraction (analogous to C#'s 
> {{{}IAsyncEnumerable<T>{}}}). Supports 
> {{{}moveNext(){}}}/{{{}getCurrent(){}}} pull-based consumption with 
> {{AutoCloseable}} semantics|
> |{{AwaitResult<T>}}|Outcome wrapper returned by {{allSettled()}} — carries 
> either a success value or a failure throwable|
> |{{AwaitableAdapter}}|SPI interface for adapting third-party async types 
> (RxJava, Reactor, Spring) to {{{}Awaitable{}}}/{{{}AsyncStream{}}}|
> |{{AwaitableAdapterRegistry}}|Central adapter registry with {{ServiceLoader}} 
> auto-discovery and runtime registration|
> h3. Internal Runtime ({{{}org.apache.groovy.runtime.async{}}} package)
> ||Class||Role||
> |{{AsyncSupport}}|Central runtime entry point — all {{await}} overloads, 
> {{async}} execution, {{defer}} scope management, combinator implementation, 
> timeout scheduling, {{yield return}} dispatch|
> |{{GroovyPromise<T>}}|Default {{Awaitable}} implementation backed by 
> {{{}CompletableFuture{}}}. Sole bridge between the public API and JDK async 
> infrastructure|
> |{{AsyncStreamGenerator<T>}}|Producer/consumer {{AsyncStream}} implementation 
> using {{SynchronousQueue}} for zero-buffered back-pressure with cooperative 
> cancellation via thread tracking|
> h2. Design Principles
>  # *Readability first.* Async code should be visually indistinguishable from 
> synchronous code. All standard Groovy control-flow constructs 
> ({{{}try{}}}/{{{}catch{}}}, {{{}for{}}}, {{{}if{}}}/{{{}else{}}}, closures) 
> must work naturally inside async methods.
>  # *Exception transparency.* {{await}} automatically unwraps 
> {{{}CompletionException{}}}, {{{}ExecutionException{}}}, and other JVM 
> wrapper layers. The original exception type, message, and stack trace are 
> preserved — callers see the same exceptions they would in synchronous code.
>  # *API decoupling.* User code depends on {{{}Awaitable{}}}, not on 
> {{{}CompletableFuture{}}}. The public API ({{{}groovy.concurrent{}}}) is 
> separated from the internal implementation 
> ({{{}org.apache.groovy.runtime.async{}}}). If the JDK's async infrastructure 
> evolves (e.g., structured concurrency), only the internal layer changes.
>  # *Minimal grammar footprint.* {{{}async{}}}, {{{}await{}}}, {{{}defer{}}}, 
> and {{yield}} are contextual keywords — they remain valid identifiers in 
> non-async contexts, preserving backward compatibility. Grammar changes to 
> {{GroovyLexer.g4}} and {{GroovyParser.g4}} are minimal.
>  # *Thread safety is the framework's responsibility.* All concurrency control 
> (atomics, volatile, CAS) is encapsulated in the runtime. Application code 
> never needs explicit locks, synchronization, or volatile annotations.
>  # *JVM ecosystem integration.* Built-in adapters handle 
> {{{}CompletableFuture{}}}, {{{}CompletionStage{}}}, {{{}Future{}}}, and 
> {{Flow.Publisher}} out of the box. Third-party frameworks integrate via the 
> {{AwaitableAdapterRegistry}} SPI.
> h2. Execution Model
> On {*}JDK 21+{*}, each {{async}} method runs on a virtual thread. When the 
> thread blocks on {{{}await{}}}, the JVM parks the virtual thread and releases 
> the carrier (OS) thread. This achieves the practical scalability of 
> compiler-generated state machines (as in C# and Kotlin) without requiring 
> control-flow rewriting — stack traces remain complete, and standard debuggers 
> work unmodified.
> On {*}JDK 17–20{*}, a bounded cached thread pool (default 256, configurable 
> via {{{}groovy.async.parallelism{}}}) with a caller-runs back-pressure policy 
> provides stable performance.
> The executor is fully configurable at runtime via 
> {{{}Awaitable.setExecutor(executor){}}}.
> h2. Key Features in Detail
> h3. Combinators
> ||Method||Analogy||Behavior||
> |{{Awaitable.all(a, b, c)}}|{{Promise.all()}} / {{Task.WhenAll()}}|Waits for 
> all; fails fast on first error|
> |{{Awaitable.any(a, b)}}|{{Promise.race()}} / {{Task.WhenAny()}}|Returns the 
> first to complete|
> |{{Awaitable.allSettled(a, b)}}|{{Promise.allSettled()}}|Waits for all; 
> captures both successes and failures in {{AwaitResult}} list|
> |{{Awaitable.delay(ms)}}|{{Task.Delay()}} / {{setTimeout}}|Non-blocking timer|
> |{{Awaitable.timeout(source, ms)}}|{{withTimeout}}|Fails with 
> {{TimeoutException}} on expiry|
> |{{Awaitable.timeoutOr(source, fallback, ms)}}|—|Completes with fallback 
> value on expiry|
> h3. Async Generators and Back-Pressure
> Methods containing {{yield return}} produce an {{{}AsyncStream<T>{}}}, 
> consumable via {{{}for await{}}}. The producer and consumer coordinate 
> through a {{SynchronousQueue}} — the producer blocks on each {{yield return}} 
> until the consumer pulls the next element, providing natural back-pressure 
> without unbounded buffering. If the consumer exits early ({{{}break{}}}, 
> {{{}return{}}}, exception), the producer thread is interrupted via 
> cooperative cancellation, preventing resource leaks.
> h3. Defer (Go-Style Cleanup)
> The {{defer}} keyword schedules cleanup actions that execute in LIFO order 
> when the enclosing method returns, regardless of success or failure. If 
> multiple deferred blocks throw, the first exception is primary and subsequent 
> ones are attached via {{{}addSuppressed(){}}}. This provides deterministic 
> resource cleanup without deeply nested {{{}try{}}}/{{{}finally{}}} blocks.
> h3. Adapter Registry (Third-Party Integration)
> The {{AwaitableAdapterRegistry}} is an SPI-based extension point. Adapters 
> can be registered:
>  * At class-load time via {{ServiceLoader}} 
> ({{{}META-INF/services/groovy.concurrent.AwaitableAdapter{}}})
>  * At runtime via {{AwaitableAdapterRegistry.register(adapter)}}
> This enables {{await}} to work transparently with RxJava 
> {{{}Single{}}}/{{{}Observable{}}}, Reactor {{{}Mono{}}}/{{{}Flux{}}}, Spring 
> {{{}ListenableFuture{}}}, or any custom async type — a single {{await}} 
> keyword, regardless of the underlying library.
> h2. Thread Safety Mechanisms
> All concurrency control is internal and transparent to users:
>  * *Lock-free synchronization* — {{volatile}} fields, {{{}AtomicBoolean{}}}, 
> {{{}AtomicReference{}}}, {{CopyOnWriteArrayList}} used throughout; no 
> {{synchronized}} blocks
>  * *TOCTOU prevention* — {{AsyncStreamGenerator.moveNext()}} employs a 
> double-check pattern after registering the consumer thread, closing a race 
> window with concurrent {{close()}}
>  * *Cooperative cancellation* — {{close()}} atomically sets a closed flag and 
> interrupts both producer and consumer threads via tracked 
> {{AtomicReference<Thread>}}
>  * *Signal delivery under interrupt* — If {{{}complete(){}}}/{{{}error(){}}} 
> is interrupted and the non-blocking fallback fails, the generator 
> force-closes to prevent consumer thread leak
>  * *Idempotent close* — All close operations use {{compareAndSet}} and are 
> safe to call multiple times from any thread
> h2. Cross-Language Comparison
> ||Aspect||Groovy||JavaScript||C#||Kotlin||Swift||
> |Async declaration|async foo() \{ }|{{async function foo()}}|{{async Task<T> 
> Foo()}}|{{suspend fun foo(): T}}|{{func foo() async throws -> T}}|
> |Await|{{await expr}}|{{await expr}}|{{await 
> expr}}|{{deferred.await()}}|{{try await expr}}|
> |Async iteration|{{for await (x in src)}}|{{for await (x of src)}}|{{await 
> foreach (x in src)}}|manual ({{{}Flow.collect{}}})|{{for try await x in seq}}|
> |Async generator|{{yield return expr}}|{{yield}} in {{async 
> function*}}|{{yield return}} in {{IAsyncEnumerable}}|flow \{ emit( x ) 
> }|AsyncStream \{ yield( x ) }|
> |Defer|{{defer}}|_(none)_|{{await using}}|{{use}}|{{defer}}|
> |Implementation|Thread-per-task (VT on 21+)|Event loop|State 
> machine|Coroutine SM|Async SM|
> h2. Backward Compatibility
>  * {{{}async{}}}, {{{}await{}}}, {{{}defer{}}}, and {{yield}} are *contextual 
> keywords* — they act as keywords only in specific syntactic positions and 
> remain valid identifiers elsewhere. Existing code using these as variable 
> names, method names, or field names continues to compile and run without 
> modification.
>  * No existing public APIs are modified or removed.
>  * The feature is purely additive: code that does not use 
> {{{}async{}}}/{{{}await{}}} is entirely unaffected.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to