[
https://issues.apache.org/jira/browse/GROOVY-9381?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18065851#comment-18065851
]
ASF GitHub Bot commented on GROOVY-9381:
----------------------------------------
Copilot commented on code in PR #2387:
URL: https://github.com/apache/groovy/pull/2387#discussion_r2934859499
##########
src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java:
##########
@@ -865,6 +923,36 @@ public ReturnStatement visitYieldStmtAlt(final
YieldStmtAltContext ctx) {
return configureAST(this.visitYieldStatement(ctx.yieldStatement()),
ctx);
}
+ @Override
+ public ExpressionStatement visitYieldReturnStmtAlt(final
YieldReturnStmtAltContext ctx) {
+ Expression expr = (Expression) this.visit(ctx.expression());
+ Expression yieldCall = AsyncTransformHelper.buildYieldReturnCall(expr);
+ return configureAST(new ExpressionStatement(yieldCall), ctx);
+ }
Review Comment:
`yield return` is currently accepted regardless of whether parsing is inside
an async context, unlike `defer` which is explicitly guarded by
`asyncContextDepth`. If `yield return` appears in a non-async
method/closure/script, it will still compile to `AsyncSupport.yieldReturn(...)`
but won’t be wrapped as a generator, which is likely to fail at runtime (or
produce incorrect behavior). Add an `asyncContextDepth == 0` check here
(mirroring `visitDeferStmtAlt`) to make it a compile-time error outside async
bodies.
##########
src/main/java/org/apache/groovy/runtime/async/AsyncStreamGenerator.java:
##########
@@ -0,0 +1,348 @@
+/*
+ * 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 java.util.concurrent.CancellationException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A producer/consumer implementation of {@link AsyncStream} used by
+ * {@code async} methods that contain {@code yield return} statements.
+ * <p>
+ * The producer (method body) runs on a separate thread and calls
+ * {@link #yield(Object)} for each emitted element. The consumer
+ * calls {@link #moveNext()}/{@link #getCurrent()} — typically via
+ * a {@code for await} loop.
+ *
+ * <h2>Back-pressure</h2>
+ * Uses a {@link SynchronousQueue} to provide natural back-pressure:
+ * the producer thread blocks at each {@code yield return} until the
+ * consumer has consumed the previous element (mirroring C#'s async
+ * iterator suspension semantics).
+ *
+ * <h2>Cooperative cancellation via thread tracking</h2>
+ * The {@link #producerThread} and {@link #consumerThread} fields track the
+ * threads currently blocked inside {@link SynchronousQueue#put} (producer)
+ * and {@link SynchronousQueue#take} (consumer). This tracking is essential
+ * because {@code SynchronousQueue} operations block <em>indefinitely</em>
+ * when the counterpart is absent:
+ * <ul>
+ * <li>If the consumer exits the {@code for await} loop early (via
+ * {@code break}, {@code return}, or an exception), the producer may
+ * still be blocked in {@code queue.put()}. Without a reference to the
+ * producer thread, there is no way to unblock it, resulting in a
+ * <b>permanent thread leak</b>.</li>
+ * <li>Conversely, if the producer finishes but the consumer is still
+ * blocked in {@code queue.take()} (e.g. an external cancellation
+ * closed the stream), the consumer thread would also leak.</li>
+ * </ul>
+ * When {@link #close()} is called (typically from the compiler-generated
+ * {@code finally} block of a {@code for await} loop), it atomically sets the
+ * {@link #closed} flag and then interrupts both the producer and consumer
+ * threads (if any), causing their blocking {@code SynchronousQueue} operations
+ * to throw {@link InterruptedException}. The interrupted methods detect the
+ * closed state and exit gracefully — the producer's {@link #yield} throws
+ * {@link java.util.concurrent.CancellationException}, while the consumer's
+ * {@link #moveNext()} returns {@code Awaitable.of(false)}.
+ * <p>
+ * A <em>double-check</em> pattern in {@link #moveNext()} closes a TOCTOU race
+ * window: the {@link #closed} flag is re-checked <em>after</em> registering
the
+ * consumer thread, ensuring that an externally-triggered {@code close()}
cannot
+ * slip between the initial check and the registration, which would otherwise
+ * leave the consumer blocked forever in {@code queue.take()}.
+ * <p>
+ * The {@link #attachProducer}/{@link #detachProducer} lifecycle methods are
+ * called by the async runtime (in {@link AsyncSupport}) to register and
+ * unregister the producer thread. The consumer thread is tracked
+ * automatically inside {@link #moveNext()}.
+ * <p>
+ * This class is an internal implementation detail and should not be referenced
+ * directly by user code.
+ *
+ * @param <T> the element type
+ * @since 6.0.0
+ */
+public class AsyncStreamGenerator<T> implements AsyncStream<T> {
+
+ private static final Object DONE = new Object();
+
+ /**
+ * Cached awaitables for the two common {@code moveNext()} outcomes.
+ * Eliminates per-call object allocation on the hot path.
+ */
+ private static final Awaitable<Boolean> MOVE_NEXT_TRUE =
Awaitable.of(Boolean.TRUE);
+ private static final Awaitable<Boolean> MOVE_NEXT_FALSE =
Awaitable.of(Boolean.FALSE);
+
+ private final SynchronousQueue<Object> queue = new SynchronousQueue<>();
+ private final AtomicBoolean closed = new AtomicBoolean(false);
+ private final AtomicReference<Thread> producerThread = new
AtomicReference<>();
+ private final AtomicReference<Thread> consumerThread = new
AtomicReference<>();
+ private volatile T current;
+
+ /**
+ * Registers the given thread as the producer for this stream.
+ * Called by the async runtime immediately after the producer
+ * thread starts, <em>before</em> the generator body executes.
+ * <p>
+ * If the stream has already been {@linkplain #close() closed} by the
+ * time this method runs, the thread is immediately interrupted so
+ * that the generator body can exit promptly.
+ *
+ * @param thread the producer thread to register
+ */
+ void attachProducer(Thread thread) {
+ producerThread.set(thread);
+ if (closed.get()) {
+ thread.interrupt();
+ }
+ }
+
+ /**
+ * Unregisters the given thread as the producer for this stream.
+ * Called from a {@code finally} block after the generator body
+ * completes (normally or exceptionally).
+ *
+ * @param thread the producer thread to unregister
+ */
+ void detachProducer(Thread thread) {
+ producerThread.compareAndSet(thread, null);
+ }
Review Comment:
These methods have package-private visibility, but the new tests call them
from `org.codehaus.groovy.transform` (different package). This will not
compile. Either (a) move the concurrency tests into
`org.apache.groovy.runtime.async`, (b) use reflection in the tests, or (c) make
`attachProducer`/`detachProducer` `public` (or otherwise provide a
testable/public API that doesn’t expose internal details broadly).
##########
src/main/java/org/apache/groovy/runtime/async/FlowPublisherAdapter.java:
##########
@@ -0,0 +1,428 @@
+/*
+ * 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>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 {
+
+ /**
+ * Queue capacity for the push→pull bridge in
+ * {@link #publisherToAsyncStream}. 256 provides a generous buffer
+ * for bursty publishers while bounding memory.
+ */
+ private static final int QUEUE_CAPACITY = 256;
+
+ /**
+ * Cached awaitables for the two common {@code moveNext()} outcomes.
+ * Eliminates per-call {@link CompletableFuture} + {@link GroovyPromise}
+ * allocation on the hot path (every element and stream-end).
+ */
+ private static final Awaitable<Boolean> MOVE_NEXT_TRUE =
Awaitable.of(Boolean.TRUE);
+ private static final Awaitable<Boolean> MOVE_NEXT_FALSE =
Awaitable.of(Boolean.FALSE);
+
+ /**
+ * 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)