[ 
https://issues.apache.org/jira/browse/GROOVY-9381?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King resolved GROOVY-9381.
-------------------------------
    Fix Version/s: 6.0.0-alpha-1
                       (was: 6.x)
       Resolution: Fixed

> 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
>            Assignee: Daniel Sun
>            Priority: Major
>             Fix For: 6.0.0-alpha-1
>
>
>  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 {{async}}/{{await}}, the same logic becomes:                           
>                                                                               
>                                                                               
>                       
>   {code:groovy}                                                               
>                                                                               
>                                                                               
>                       
>   def profile = async {                                                       
>                                                                               
>                                                                               
>                       
>       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 closure|async \{ ... \}|Starts a closure on a background thread, 
> returning an {{Awaitable}} (or {{Iterable}} when the body contains {{yield 
> return}})|
>   |Await expression|await expr / await(expr)|Blocks until the awaited 
> computation completes; transparently unwraps the result|                      
>                                                                               
>                       
>   |Multi-arg await|await(a, b, c) \\ await a, b, c|Syntactic sugar for 
> {{Awaitable.all(a, b, c)}}|
>   |For-await loop|for await (item in source) \{ ... \}|Iterates over an async 
> source (generator, channel, reactive type), with automatic resource cleanup 
> via try-finally|                                                              
>                       
>   |Yield return|yield return expr|Emits a value from an async generator 
> closure, producing an {{Iterable}} with natural back-pressure|                
>                                                                               
>                         
>   |Defer|defer \{ cleanup \} \\ defer cleanup|Schedules a cleanup block to 
> execute on closure exit (LIFO order), inspired by Go's {{defer}}|             
>                                                                               
>                     
>                                                                               
>                                                                               
>                                                                               
>                       
>   h3. Public API ({{groovy.concurrent}} package)                              
>                                                                               
>                                                                               
>                       
>   ||Class/Interface||Role||                                                   
>                                                                               
>                                                                               
>                       
>   |{{Awaitable}}|Core promise abstraction (analogous to C#'s {{Task}} / JS's 
> {{Promise}}). Provides static combinators ({{all}}, {{any}}, {{first}}, 
> {{allSettled}}, {{delay}}, {{orTimeout}}, {{completeOnTimeout}}), factories 
> ({{of}}, {{failed}}, {{from}}, 
>   {{go}}), and instance continuation methods ({{then}}, {{thenCompose}}, 
> {{thenAccept}}, {{exceptionally}}, {{whenComplete}}, {{handle}}, 
> {{orTimeout}}, {{completeOnTimeout}})|                                        
>                                         
>   |{{AsyncChannel}}|Go-style inter-task communication channel with optional 
> buffering. Supports unbuffered (rendezvous) and buffered modes. Implements 
> {{Iterable}}, so works with {{for await}} and regular {{for}} loops|
>   |{{AsyncScope}}|Structured concurrency scope — binds child task lifetimes 
> to a scope with fail-fast cancellation. All children are guaranteed complete 
> (or cancelled) before the scope exits|                                        
>                          
>   |{{AwaitResult}}|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, etc.) to {{Awaitable}}|                                     
>                                                                               
>                          
>   |{{AwaitableAdapterRegistry}}|Central adapter registry with 
> {{ServiceLoader}} auto-discovery and runtime registration|                    
>                                                                               
>                                       
>   |{{ChannelClosedException}}|Thrown when sending to or receiving from a 
> closed channel|                                                               
>                                                                               
>                            
>                                                                               
>                                                                               
>                                                                               
>                       
>   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, channel and scope support|
>   |{{GroovyPromise}}|Default {{Awaitable}} implementation backed by 
> {{CompletableFuture}}. Sole bridge between the public API and JDK async 
> infrastructure|                                                               
>                                       
>   |{{GeneratorBridge}}|Producer/consumer bridge for async generators ({{yield 
> return}}). Uses {{SynchronousQueue}} for zero-buffered back-pressure with 
> cooperative cancellation via thread tracking. Implements {{Iterator}} and 
> {{Closeable}}|                
>   |{{DefaultAsyncChannel}}|Default {{AsyncChannel}} implementation with 
> buffered and unbuffered modes|                                                
>                                                                               
>                             
>   |{{DefaultAsyncScope}}|Default {{AsyncScope}} implementation with fail-fast 
> child cancellation|                                                           
>                                                                               
>                       
>                                                                               
>                                                                               
>                                                                               
>                       
>   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 closures.
>                                                                               
>                                                                               
>                                                                               
>                       
>   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}}, and {{Future}} out of the box. Third-party frameworks 
> integrate via the {{AwaitableAdapterRegistry}} SPI.                           
>                           
>                                                             
>   h2. Execution Model                                                         
>                                                                               
>                                                                               
>                       
>                                                             
>   On {*}JDK 21+{*}, each {{async}} closure 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 to succeed; fails immediately on first error (fail-fast)|
>   |{{Awaitable.any(a, b)}}|{{Promise.any()}}|Returns the first to complete 
> (success or failure)|                                                         
>                                                                               
>    
>   |{{Awaitable.first(a, b, c)}}|{{Promise.race()}} \\ 
> {{Task.WhenAny()}}|Returns the first to succeed; fails only when all sources 
> fail (aggregate error)|                                                       
>                                                                       
>   |{{Awaitable.allSettled(a, b)}}|{{Promise.allSettled()}}|Waits for all to 
> complete (success or fail); captures outcomes in {{AwaitResult}} list without 
> throwing|                                                                     
>                         
>   |{{Awaitable.delay(ms)}}|{{Task.Delay()}} \\ {{setTimeout}}|Non-blocking 
> timer|                                                                        
>                                                                               
>                           
>   |{{Awaitable.orTimeoutMillis(source, ms)}}|{{withTimeout}}|Fails with 
> {{TimeoutException}} on expiry|                                               
>                                                                               
>                             
>   |{{Awaitable.completeOnTimeoutMillis(source, fallback, ms)}}|—|Completes 
> with fallback value on expiry|                                                
>                                                                               
>                          
>                                                                               
>                                                                               
>                                                                               
>                       
>   h3. Async Generators and Back-Pressure                                      
>                                                                               
>                                                                               
>                       
>                                                                               
>                                                                               
>                                                                               
>                       
>   Closures containing {{yield return}} produce an {{Iterable}}, consumable 
> via {{for await}} or a regular {{for}} loop. The producer and consumer 
> coordinate through the {{GeneratorBridge}}, which uses 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. {{for await}} wraps the loop in a try-finally to 
> ensure generator cleanup.                                                     
>                                                                               
>                       
>                                                             
>   h3. Channels (Go-Style Inter-Task Communication)
>   {{AsyncChannel}} provides CSP-style communication between tasks. A producer 
> sends values into a channel; a consumer receives them. The channel handles 
> synchronization and optional buffering — no shared mutable state needed. 
> Channels support unbuffered   
>   (rendezvous, {{create()}}) and buffered ({{create\(n)}}) modes. Sending 
> blocks when the buffer is full; receiving blocks when empty. Since channels 
> implement {{Iterable}}, they work with {{for await}}, regular {{for}} loops, 
> and Groovy collection methods.
>                                                                               
>                                                                               
>                                                                               
>                       
>   h3. Structured Concurrency                                                  
>                                                                               
>                                                                               
>                       
>    
>   {{AsyncScope}} binds the lifetime of child tasks to a scope. When the scope 
> exits, all children are guaranteed complete (or cancelled). This prevents 
> orphaned tasks and silent failures. By default, the scope uses fail-fast 
> semantics — if any child fails,
>    all siblings are cancelled immediately. On JDK 25+, scope tracking uses 
> {{ScopedValue}} for optimal virtual thread performance; on JDK 17–24, a 
> {{ThreadLocal}} fallback is used transparently.
>                                                                               
>                                                                               
>                                                                               
>                       
>   h3. Defer (Go-Style Cleanup)                              
>   The {{defer}} keyword schedules cleanup actions that execute in LIFO order 
> when the enclosing async closure returns, regardless of success or failure. 
> If multiple deferred blocks throw, the first exception is primary and 
> subsequent ones are attached via 
>   {{addSuppressed()}}. If a deferred action returns an {{Awaitable}} or 
> {{Future}}, the result is awaited before the next deferred action runs, 
> ensuring orderly cleanup of asynchronous resources. 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}}, or any custom async 
> type — a single {{await}} keyword, regardless of the underlying library. 
> Drop-in adapter modules are provided for:         
>   - groovy-reactor — {{await}} on {{Mono}}, {{for await}} over {{Flux}}       
>                                                                               
>                                                                               
>                       
>   - groovy-rxjava — {{await}} on {{Single}}/{{Maybe}}/{{Completable}}, {{for 
> await}} over {{Observable}}/{{Flowable}}                                      
>                                                                               
>                        
>                                                                               
>                                                                               
>                                                                               
>                       
>   h2. Thread Safety Mechanisms                                                
>                                                                               
>                                                                               
>                       
>                                                             
>   All concurrency control is internal and transparent to users:
>   - Lock-free synchronization — {{volatile}} fields, {{AtomicInteger}}, 
> {{AtomicReference}}, {{CopyOnWriteArrayList}} used throughout; no 
> {{synchronized}} blocks in the async runtime
>   - TOCTOU prevention — {{GeneratorBridge.yield()}} sets {{producerThread}} 
> before checking the {{closed}} flag, then re-checks after blocking 
> operations, closing a race window with concurrent {{close()}}                 
>                                    
>   - Cooperative cancellation — {{GeneratorBridge.close()}} atomically sets a 
> closed flag, drains any pending handoff, and interrupts the producer thread   
>                                                  
>   - Idempotent close — All close operations are safe to call multiple times 
> from any thread                                                               
>                                                                               
>                         
>                                                                               
>                                                                               
>                                                                               
>                       
>   h2. Cross-Language Comparison                                               
>                                                                               
>                                                                               
>                       
>   ||Aspect||Groovy||JavaScript||C#||Kotlin||Swift||                           
>                                                                               
>                                                                               
>                       
>   |Async declaration|async \{ ... \}|{{async function foo()}}|{{async Task 
> 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}}|                  
>                                                                               
>                                                                               
>                       
>   |Channels|{{AsyncChannel}}|(none)|{{Channel}}|{{Channel}}|{{AsyncStream}} 
> (limited)|                                                                    
>                                                                               
>                         
>   |Structured 
> concurrency|{{AsyncScope}}|(none)|(none)|{{coroutineScope}}|{{TaskGroup}}|    
>                                                                               
>                                                                               
>         
>   |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