-
Notifications
You must be signed in to change notification settings - Fork 1.9k
GROOVY-9381: Support async/await like ES7#2387
Conversation
Codecov Report Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2387 +/- ## ================================================== + Coverage 66.7571% 67.2694% +0.5123% - Complexity 29856 31196 +1340 ================================================== Files 1382 1392 +10 Lines 116130 118238 +2108 Branches 20471 21355 +884 ================================================== + Hits 77525 79538 +2013 - Misses 32275 32337 +62 - Partials 6330 6363 +33
New features to boost your workflow:
|
5b87db0 to
ec35b7d
Compare
ec35b7d to
5f8acbb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements GROOVY-9381 by adding native async/await syntax to the Groovy language (including for await, yield return async generators, and defer), plus runtime support and extensive documentation/tests.
Changes:
- Extend the Groovy grammar + AST builder to parse/compile
async,await,for await,yield return, anddefer. - Add/extend runtime primitives (
Awaitable,AsyncStream, adapters/registry, promise + generator implementation) and@AsyncAST transformation support. - Add comprehensive tests and new spec documentation; add Reactor/RxJava test dependencies for adapter interop coverage.
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| versions.properties | Adds version pins for Reactor and RxJava3 used by new integration tests. |
| build.gradle | Adds Reactor/RxJava3 as testImplementation dependencies. |
| gradle/verification-metadata.xml | Adds verification metadata entries for new test dependencies. |
| src/antlr/GroovyLexer.g4 | Introduces lexer tokens for async, await, defer. |
| src/antlr/GroovyParser.g4 | Adds grammar support for async closure/lambda, await expressions, for await, yield return, and defer. |
| src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java | Lowers new syntax into runtime calls (e.g., AsyncSupport.await, stream conversion/cleanup, defer, yieldReturn). |
| src/main/java/org/codehaus/groovy/ast/ModifierNode.java | Treats ASYNC as a non-bytecode modifier (opcode 0). |
| src/main/java/org/codehaus/groovy/transform/AsyncTransformHelper.java | Centralizes AST construction + scanning/rewriting for async constructs. |
| src/main/java/org/codehaus/groovy/transform/AsyncASTTransformation.java | Implements @Async transformation using shared helper logic (await rewrite, generator/defer handling). |
| src/main/java/groovy/transform/Async.java | Adds @Async annotation and documentation. |
| src/main/java/groovy/concurrent/* | Adds new async public API (Awaitable, AsyncStream, adapters/registry, AwaitResult). |
| src/main/java/org/apache/groovy/runtime/async/* | Adds runtime implementations (GroovyPromise, AsyncStreamGenerator) used by compiler output. |
| src/spec/doc/core-async-await.adoc | Adds user-facing language documentation/spec for async/await. |
| src/test/groovy/org/codehaus/groovy/transform/* | Adds extensive test coverage for async/await, generators, defer, adapters, and virtual threads. |
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| | VOLATILE | ||
| | DEF | ||
| | VAR | ||
| | ASYNC |
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding ASYNC to the general modifier rule means async can now appear anywhere modifiers are allowed (e.g., on fields, variables, classes) and will likely be silently ignored (since it maps to opcode 0). If async is intended to be a method-only modifier, consider restricting it in the grammar or adding a parse/semantic error when async is used in unsupported contexts, to avoid misleading code compiling without effect.
| | ASYNC |
| def customPool = Executors.newFixedThreadPool(2, { r -> | ||
| def t = new Thread(r) | ||
| t.setName("custom-async-" + t.getId()) | ||
| t | ||
| }) | ||
| Awaitable.setExecutor(customPool) |
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom fixed thread pool created here is never shut down. Since the thread factory creates non-daemon threads, this can leak threads and potentially hang the test JVM. Please ensure the executor is shut down (preferably in the existing finally block) after restoring the previous executor.
| Awaitable.setExecutor(Executors.newSingleThreadExecutor()) | ||
| assert Awaitable.getExecutor() != originalExecutor | ||
| // Reset to null -- should restore default | ||
| Awaitable.setExecutor(null) | ||
| def restored = Awaitable.getExecutor() | ||
| assert restored != null | ||
| // Verify it works | ||
| def task = async { 42 }; def awaitable = task() | ||
| assert await(awaitable) == 42 | ||
| // Restore original | ||
| Awaitable.setExecutor(originalExecutor) |
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test sets the global executor to a newSingleThreadExecutor but never shuts that executor down. Even though the executor is reset to null later, the underlying thread can remain alive and leak across the suite. Please shut down the created executor (e.g., keep a reference and shutdown in a finally block).
| Awaitable.setExecutor(Executors.newSingleThreadExecutor()) | |
| assert Awaitable.getExecutor() != originalExecutor | |
| // Reset to null -- should restore default | |
| Awaitable.setExecutor(null) | |
| def restored = Awaitable.getExecutor() | |
| assert restored != null | |
| // Verify it works | |
| def task = async { 42 }; def awaitable = task() | |
| assert await(awaitable) == 42 | |
| // Restore original | |
| Awaitable.setExecutor(originalExecutor) | |
| def customExecutor = Executors.newSingleThreadExecutor() | |
| try { | |
| Awaitable.setExecutor(customExecutor) | |
| assert Awaitable.getExecutor() != originalExecutor | |
| // Reset to null -- should restore default | |
| Awaitable.setExecutor(null) | |
| def restored = Awaitable.getExecutor() | |
| assert restored != null | |
| // Verify it works | |
| def task = async { 42 }; def awaitable = task() | |
| assert await(awaitable) == 42 | |
| } finally { | |
| // Restore original and shut down custom executor to avoid thread leaks | |
| Awaitable.setExecutor(originalExecutor) | |
| customExecutor.shutdown() | |
| } |
| static Executor myPool = Executors.newFixedThreadPool(1, { r -> | ||
| def t = new Thread(r) | ||
| t.setName("my-pool-thread") | ||
| t | ||
| }) | ||
|
|
Copilot
AI
Mar 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The static Executor created via Executors.newFixedThreadPool uses non-daemon threads and is never shut down. This can leak threads for the remainder of the test run and may prevent the JVM from exiting. Consider using daemon threads (as other tests do) and/or explicitly shutting down the pool at the end of the script.