Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/antlr/GroovyLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,9 @@ BuiltInPrimitiveType
;

ABSTRACT : 'abstract';
ASYNC : 'async';
ASSERT : 'assert';
AWAIT : 'await';

fragment
BOOLEAN : 'boolean';
Expand Down
11 changes: 11 additions & 0 deletions src/antlr/GroovyParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ modifier
: classOrInterfaceModifier
| m=( NATIVE
| SYNCHRONIZED
| ASYNC
| TRANSIENT
| VOLATILE
| DEF
Expand Down Expand Up @@ -776,6 +777,12 @@ expression
// must come before postfixExpression to resolve the ambiguities between casting and call on parentheses expression, e.g. (int)(1 / 2)
: castParExpression castOperandExpression #castExprAlt

// async expression
| ASYNC nls closureOrLambdaExpression #asyncExprAlt

// await expression
| AWAIT nls expression #awaitExprAlt

// qualified names, array expressions, method invocation, post inc/dec
| postfixExpression #postfixExprAlt

Expand Down Expand Up @@ -1226,6 +1233,8 @@ identifier
: Identifier
| CapitalizedIdentifier
| AS
| ASYNC
| AWAIT
| IN
| PERMITS
| RECORD
Expand All @@ -1243,7 +1252,9 @@ builtInType
keywords
: ABSTRACT
| AS
| ASYNC
| ASSERT
| AWAIT
| BREAK
| CASE
| CATCH
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/groovy/transform/Async.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.transform;

import org.codehaus.groovy.transform.GroovyASTTransformationClass;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation used to mark async methods, closures and lambda expressions.
*
* @since 6.0.0
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.LOCAL_VARIABLE, ElementType.FIELD})
@GroovyASTTransformationClass({"org.codehaus.groovy.transform.AsyncASTTransformation"})
public @interface Async {
}
116 changes: 116 additions & 0 deletions src/main/java/groovy/util/concurrent/async/AsyncHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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.util.concurrent.async;

import org.apache.groovy.util.SystemUtil;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;

/**
* Helper class for async/await operations
*
* @since 6.0.0
*/
public class AsyncHelper {
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The system property 'groovy.async.parallelism' is not documented. Consider adding a comment explaining its purpose and valid values.

Suggested change
public class AsyncHelper {
public class AsyncHelper {
/**
* The system property "groovy.async.parallelism" controls the parallelism level
* (number of threads) used for the async thread pool. Valid values are positive integers.
* If not set, the default is (number of available processors + 1).
* Setting this value too high may lead to resource exhaustion; too low may reduce concurrency.
*/

Copilot uses AI. Check for mistakes.
private static final int PARALLELISM = SystemUtil.getIntegerSafe("groovy.async.parallelism", Runtime.getRuntime().availableProcessors() + 1);
private static final Executor DEFAULT_EXECUTOR;
private static int seq;
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static field seq is accessed from multiple threads without synchronization in the thread factory. Use AtomicInteger instead to prevent race conditions.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static field seq is accessed without synchronization in the thread factory. This can lead to race conditions when multiple threads are created concurrently, resulting in duplicate or inconsistent thread names. Use AtomicInteger instead: private static final AtomicInteger seq = new AtomicInteger(0); and update line 54 to t.setName(\"async-thread-\" + seq.getAndIncrement());

Copilot uses AI. Check for mistakes.

static {
Executor tmpExecutor;
try {
MethodHandle mh = MethodHandles.lookup().findStatic(
Executors.class,
"newVirtualThreadPerTaskExecutor",
MethodType.methodType(ExecutorService.class)
);
tmpExecutor = (Executor) mh.invoke();
} catch (Throwable throwable) {
// Fallback to default thread pool if virtual threads are not available
tmpExecutor = Executors.newFixedThreadPool(PARALLELISM, r -> {
Thread t = new Thread(r);
t.setName("async-thread-" + seq++);
return t;
});
}
DEFAULT_EXECUTOR = tmpExecutor;
}

/**
* Submits a supplier for asynchronous execution using the default executor
*
* @param supplier the supplier
* @param <T> the result type
* @return the promise
*/
public static <T> Promise<T> async(Supplier<T> supplier) {
return SimplePromise.of(supplier, DEFAULT_EXECUTOR);
}

/**
* Submits a supplier for asynchronous execution using the provided executor
*
* @param supplier the supplier
* @param executor the executor
* @param <T> the result type
* @return the promise
*/
public static <T> Promise<T> async(Supplier<T> supplier, Executor executor) {
return SimplePromise.of(supplier, executor);
}

/**
* Awaits the result of an awaitable
*
* @param awaitable the awaitable
* @param <T> the result type
* @return the result
*/
public static <T> T await(Awaitable<T> awaitable) {
if (null == awaitable) return null;

return awaitable.await();
}

/**
* Awaits the result of an object implementing Awaitable
*
* @param obj the object
* @param <T> the result type
* @return the result
* @throws IllegalArgumentException if the object does not implement Awaitable
*/
@SuppressWarnings("unchecked")
public static <T> T await(Object obj) {
if (null == obj) return null;

if (!(obj instanceof Awaitable)) {
throw new IllegalArgumentException("type " + obj.getClass().getName() + " does not implement " + Awaitable.class.getName());
}
return await((Awaitable<T>) obj);
}

private AsyncHelper() {}
}
44 changes: 44 additions & 0 deletions src/main/java/groovy/util/concurrent/async/AwaitException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.util.concurrent.async;

/**
* Exception thrown when attempting to wait for the result of a promise
* that aborted by throwing an exception. This exception can be
* inspected using the {@link #getCause()} method.
*
* @see Promise
* @since 6.0.0
*/
public class AwaitException extends RuntimeException {
public AwaitException() {
}

public AwaitException(String message) {
super(message);
}

public AwaitException(Throwable cause) {
super(cause);
}

public AwaitException(String message, Throwable cause) {
super(message, cause);
}
}
35 changes: 35 additions & 0 deletions src/main/java/groovy/util/concurrent/async/Awaitable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.util.concurrent.async;

/**
* Represents a result of an asynchronous computation
*
* @since 6.0.0
*/
public interface Awaitable<T> {
/**
* Waits if necessary for the computation to complete, and then retrieves its
* result.
*
* @return the computed result
* @throws AwaitException if the computation was cancelled or completed exceptionally
*/
T await();
}
Loading