This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new e5dc35d750 improve @Parallel to use virtual threads if available
e5dc35d750 is described below

commit e5dc35d7503c83b25d770b71086bb121b4d8d874
Author: Paul King <[email protected]>
AuthorDate: Sat Mar 28 15:01:30 2026 +1000

    improve @Parallel to use virtual threads if available
---
 .../groovy/util/concurrent/ThreadHelper.java       | 78 ++++++++++++++++++++++
 .../transform/ParallelASTTransformation.java       |  7 +-
 2 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/apache/groovy/util/concurrent/ThreadHelper.java 
b/src/main/java/org/apache/groovy/util/concurrent/ThreadHelper.java
new file mode 100644
index 0000000000..5276fa2c2e
--- /dev/null
+++ b/src/main/java/org/apache/groovy/util/concurrent/ThreadHelper.java
@@ -0,0 +1,78 @@
+/*
+ *  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.util.concurrent;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+/**
+ * Helper for starting threads that prefers virtual threads when running
+ * on Java 21+, falling back to platform threads on older runtimes.
+ *
+ * @since 6.0.0
+ */
+public final class ThreadHelper {
+
+    private static final MethodHandle VIRTUAL_START;
+
+    static {
+        MethodHandle h = null;
+        try {
+            MethodHandles.Lookup lookup = MethodHandles.publicLookup();
+            // Thread.ofVirtual() returns Thread.Builder.OfVirtual (Java 21+)
+            Class<?> ofVirtualReturnType = 
Class.forName("java.lang.Thread$Builder$OfVirtual");
+            MethodHandle ofVirtual = lookup.findStatic(Thread.class, 
"ofVirtual",
+                    MethodType.methodType(ofVirtualReturnType));
+            // start(Runnable) is declared on Thread.Builder
+            Class<?> builderClass = Class.forName("java.lang.Thread$Builder");
+            MethodHandle start = lookup.findVirtual(builderClass, "start",
+                    MethodType.methodType(Thread.class, Runnable.class));
+            // Adapt ofVirtual return type so collectArguments can combine them
+            ofVirtual = ofVirtual.asType(MethodType.methodType(builderClass));
+            // Combine: start(ofVirtual(), runnable)
+            h = MethodHandles.collectArguments(start, 0, ofVirtual);
+        } catch (ClassNotFoundException | NoSuchMethodException | 
IllegalAccessException ignore) {
+            // Java < 21 — virtual threads not available
+        }
+        VIRTUAL_START = h;
+    }
+
+    private ThreadHelper() { }
+
+    /**
+     * Starts a new thread to execute the given task.
+     * Uses a virtual thread on Java 21+, otherwise a platform thread.
+     *
+     * @param task the runnable to execute
+     * @return the started thread
+     */
+    public static Thread startThread(Runnable task) {
+        if (VIRTUAL_START != null) {
+            try {
+                return (Thread) VIRTUAL_START.invoke(task);
+            } catch (Throwable ignore) {
+                // unexpected failure — fall back to platform thread
+            }
+        }
+        Thread t = new Thread(task);
+        t.start();
+        return t;
+    }
+}
diff --git 
a/src/main/java/org/codehaus/groovy/transform/ParallelASTTransformation.java 
b/src/main/java/org/codehaus/groovy/transform/ParallelASTTransformation.java
index 66abdb4835..376ddc065f 100644
--- a/src/main/java/org/codehaus/groovy/transform/ParallelASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/ParallelASTTransformation.java
@@ -20,6 +20,7 @@ package org.codehaus.groovy.transform;
 
 import groovy.transform.Parallel;
 import org.apache.groovy.lang.annotation.Incubating;
+import org.apache.groovy.util.concurrent.ThreadHelper;
 import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassHelper;
@@ -40,7 +41,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
 
 /**
@@ -98,8 +98,9 @@ public class ParallelASTTransformation implements 
ASTTransformation {
         Expression runnable = castX(ClassHelper.make(Runnable.class), 
boundWorker);
 
         Statement startThread = stmt(callX(
-                ctorX(ClassHelper.make(Thread.class), args(runnable)),
-                "start"));
+                ClassHelper.make(ThreadHelper.class),
+                "startThread",
+                args(runnable)));
         startThread.setSourcePosition(annotation);
 
         BlockStatement loopBody = block(startThread);

Reply via email to