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

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


The following commit(s) were added to refs/heads/asf-site by this push:
     new 2315c10  draft GPars integration proposal
2315c10 is described below

commit 2315c10defb4c780d7bf24fb221c787a9254414e
Author: Paul King <[email protected]>
AuthorDate: Wed Apr 15 19:49:02 2026 +1000

    draft GPars integration proposal
---
 site/src/site/wiki/GEP-18.adoc | 415 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 415 insertions(+)

diff --git a/site/src/site/wiki/GEP-18.adoc b/site/src/site/wiki/GEP-18.adoc
new file mode 100644
index 0000000..db13942
--- /dev/null
+++ b/site/src/site/wiki/GEP-18.adoc
@@ -0,0 +1,415 @@
+= GEP-18: Integrated Concurrency and Parallel Processing
+
+:icons: font
+
+.Metadata
+****
+[horizontal,options="compact"]
+*Number*:: GEP-18
+*Title*:: Integrated Concurrency and Parallel Processing
+*Version*:: 1
+*Type*:: Feature
+*Status*:: Draft
+*Leader*:: Paul King
+*Created*:: 2026-04-15
+*Last modification*&#160;:: 2026-04-15
+****
+
+== Abstract
+
+This GEP describes the integration of concurrency, parallelism, dataflow,
+and actor-based programming into Groovy core, drawing from GPars' proven
+patterns and modernising them for virtual threads, structured concurrency,
+and the `async`/`await` language features introduced in Groovy 6.
+
+The work delivers a comprehensive concurrent programming toolkit directly
+in Groovy core, with a pure-Java subset published as a standalone module
+(`groovy-concurrent-java`) for use by Java, Kotlin, and other JVM language
+users without requiring the Groovy runtime.
+
+== Motivation
+
+GPars has served the Groovy community as the primary concurrency library
+for over a decade, providing parallel collections, dataflow variables,
+actors, agents, and more. However, GPars is a separate library with its
+own release cycle, and modern JDK developments (virtual threads, structured
+task scope, parallel streams) have shifted the concurrency landscape.
+
+Groovy 6 introduced native `async`/`await` support (GEP-16), providing
+language-level concurrency primitives. This GEP extends that foundation
+with the higher-level patterns that GPars users rely on, integrated
+directly into Groovy core so they are available out of the box.
+
+=== Design principles
+
+* *Pure-Java API surface* — all types in `groovy.concurrent` use
+  `java.util.function` types (`Supplier`, `Function`, `Predicate`, etc.)
+  rather than `groovy.lang.Closure`. Groovy closures work seamlessly
+  via SAM coercion. This enables a Java-only extraction.
+* *No extension classes needed* — SAM coercion eliminates the need for
+  Closure-bridging extension methods for the public API.
+* *Virtual-thread-first* — on JDK 21+, the default executor uses virtual
+  threads. All abstractions (actors, agents, pools) scale to millions of
+  concurrent entities without pool tuning.
+* *Structured concurrency* — scopes ensure child tasks complete before
+  the scope exits. Nesting, cancellation propagation, and timeouts are
+  built in.
+* *GPars migration path* — familiar patterns (`withPool`, `Agent`,
+  `DataflowVariable`, `Dataflows`, `@ActiveObject`) are preserved with
+  minimal API changes.
+
+== Features
+
+=== Pool and ParallelScope
+
+`Pool` is a managed thread pool extending `Executor` and `AutoCloseable`:
+
+[source,groovy]
+----
+def pool = Pool.cpu()       // ForkJoinPool sized to available processors
+def pool = Pool.fixed(8)    // Fixed-size ForkJoinPool
+def pool = Pool.io()        // Virtual threads on JDK 21+
+def pool = Pool.virtual()   // Virtual-thread-per-task
+----
+
+`Pool.cpu()` and `Pool.fixed()` create `ForkJoinPool`-backed pools,
+enabling parallel stream isolation for CPU-bound work.
+`Pool.io()` and `Pool.virtual()` use virtual threads for I/O-bound work.
+
+`ParallelScope` binds a pool for scoped execution:
+
+[source,groovy]
+----
+ParallelScope.withPool(Pool.cpu()) { scope ->
+    def a = scope.async { cpuWork1() }
+    def b = scope.async { cpuWork2() }
+    [await(a), await(b)]
+}
+----
+
+`Pool.current()` tracks the active pool via `ScopedValue` on JDK 25+
+(with `ThreadLocal` fallback), so parallel collection methods
+automatically use the correct pool.
+
+`ConcurrentConfig` provides global defaults via system properties
+(`groovy.concurrent.poolsize`, `groovy.concurrent.virtual`) or
+programmatic override.
+
+=== Scope nesting and timeout
+
+`AsyncScope` supports parent-child relationships. Cancelling a parent
+scope propagates to all child scopes:
+
+[source,groovy]
+----
+AsyncScope.withScope { outer ->
+    outer.async {
+        AsyncScope.withScope { inner ->
+            assert inner.parent == outer
+            inner.async { work() }
+        }
+    }
+}
+----
+
+Scopes support timeouts:
+
+[source,groovy]
+----
+AsyncScope.withScope(Duration.ofSeconds(5)) { scope ->
+    scope.async { longRunningWork() }
+    // Cancelled automatically if not complete within 5 seconds
+}
+----
+
+=== Parallel collections
+
+Seventeen parallel methods are added to `Collection`, backed by Java
+parallel streams with pool isolation:
+
+[cols="1,3"]
+|===
+|Category |Methods
+
+|Transformation
+|`collectParallel`, `collectManyParallel`
+
+|Filtering
+|`findAllParallel`, `findParallel`, `findAnyParallel`, `grepParallel`, 
`splitParallel`
+
+|Iteration
+|`eachParallel`, `eachWithIndexParallel`
+
+|Predicates
+|`anyParallel`, `everyParallel`, `countParallel`
+
+|Aggregation
+|`sumParallel`, `injectParallel`, `minParallel`, `maxParallel`, 
`groupByParallel`
+|===
+
+All methods use `java.util.function` types. Within a `ParallelScope.withPool`
+block, operations automatically use the bound pool:
+
+[source,groovy]
+----
+ParallelScope.withPool(Pool.cpu()) { scope ->
+    def results = bigList.collectParallel { transform(it) }
+    def filtered = results.findAllParallel { it > threshold }
+    filtered.groupByParallel { it.category }
+}
+----
+
+=== Agent
+
+`Agent` provides thread-safe mutable state through serialised update
+functions, inspired by GPars' `Agent` and Clojure agents:
+
+[source,groovy]
+----
+def counter = Agent.create(0)
+counter.send { it + 1 }
+counter.send { it + 1 }
+assert await(counter.getAsync()) == 2
+----
+
+Updates are queued and applied one at a time on a dedicated thread.
+`get()` returns a non-blocking snapshot; `getAsync()` returns an
+`Awaitable` that completes after pending updates.
+
+=== Actor
+
+`Actor` provides message-passing concurrency with two factory methods:
+
+[source,groovy]
+----
+// Reactor — stateless, each message produces a reply
+def doubler = Actor.reactor { it * 2 }
+assert await(doubler.sendAndGet(21)) == 42
+
+// Stateful — maintains state across messages
+def counter = Actor.stateful(0) { state, msg ->
+    switch (msg) {
+        case 'increment': return state + 1
+        case 'decrement': return state - 1
+        default: return state
+    }
+}
+counter.send('increment')
+assert await(counter.sendAndGet('increment')) == 2
+----
+
+Each actor has a dedicated thread processing messages sequentially.
+On JDK 21+, actors use virtual threads — millions of actors are
+feasible without pool tuning.
+
+=== @ActiveObject / @ActiveMethod
+
+For annotation-driven thread safety, `@ActiveObject` routes annotated
+method calls through an internal actor:
+
+[source,groovy]
+----
+@ActiveObject
+class Account {
+    private double balance = 0
+
+    @ActiveMethod
+    void deposit(double amount) { balance += amount }
+
+    @ActiveMethod
+    void withdraw(double amount) { balance -= amount }
+
+    @ActiveMethod
+    double getBalance() { balance }
+}
+
+def account = new Account()
+account.deposit(100)
+account.withdraw(30)
+assert account.getBalance() == 70.0
+----
+
+All `@ActiveMethod` calls are serialised — no locks needed. Methods
+without the annotation run on the caller's thread as normal.
+`@ActiveMethod(blocking = false)` returns an `Awaitable` immediately.
+
+=== DataflowVariable and Dataflows
+
+`DataflowVariable` is a single-assignment variable that blocks readers
+until a value is bound:
+
+[source,groovy]
+----
+def x = new DataflowVariable()
+def y = new DataflowVariable()
+
+async { z << await(x) + await(y) }
+async { x << 10 }
+async { y << 5 }
+
+assert await(z) == 15
+----
+
+`Dataflows` provides dynamic property-based access:
+
+[source,groovy]
+----
+def df = new Dataflows()
+
+async { df.z = df.x + df.y }
+async { df.x = 10 }
+async { df.y = 5 }
+
+assert df.z == 15
+----
+
+Both integrate natively with `Awaitable` and the `async`/`await` keywords.
+
+=== Channel composition
+
+`AsyncChannel` supports composable pipeline operations:
+
+[source,groovy]
+----
+def pipeline = source
+    .filter { it > 50 }
+    .map { it * 2 }
+
+for (val in pipeline) { process(val) }
+----
+
+Additional operations: `merge` (interleave two channels), `split`
+(partition by predicate), `tap` (fork a monitoring copy).
+
+`ChannelSelect` waits for the first available value from multiple
+channels:
+
+[source,groovy]
+----
+def sel = ChannelSelect.from(prices, alerts)
+def result = await sel.select()
+println "Channel ${result.index}: ${result.value}"
+----
+
+`BroadcastChannel` delivers each value to all subscribers (one-to-many):
+
+[source,groovy]
+----
+def broadcast = BroadcastChannel.create()
+def sub1 = broadcast.subscribe()
+def sub2 = broadcast.subscribe()
+
+async { broadcast.send('hello'); broadcast.close() }
+
+for (msg in sub1) { println "Sub1: $msg" }
+for (msg in sub2) { println "Sub2: $msg" }
+----
+
+== Java-only module
+
+The `groovy-concurrent-java` module publishes the pure-Java subset of
+the concurrent API as a standalone dependency:
+
+[source,xml]
+----
+<dependency>
+    <groupId>org.apache.groovy</groupId>
+    <artifactId>groovy-concurrent-java</artifactId>
+    <version>6.0.0</version>
+</dependency>
+----
+
+This module contains 27 classes with zero Groovy runtime dependency.
+Java users get access to `AsyncScope`, `Awaitable`, `Pool`,
+`ParallelScope`, `Actor`, `Agent`, `DataflowVariable`, `AsyncChannel`,
+`BroadcastChannel`, `ChannelSelect`, and `ConcurrentConfig`.
+
+Mutual exclusion with the full Groovy runtime is enforced via a shared
+Gradle capability (`org.apache.groovy:groovy-concurrent-api`). A runtime
+warning is logged if both jars are detected on the classpath (for Maven
+users who lack Gradle's capability mechanism). JPMS split-package
+detection provides additional protection on the module path.
+
+Features not available in the Java module include `Dataflows` (requires
+Groovy's `propertyMissing`), `@ActiveObject`/`@ActiveMethod` (Groovy AST
+transform), parallel collection methods (registered as Groovy extension
+methods), and the `async`/`await`/`defer`/`yield return` keywords
+(Groovy compiler syntax).
+
+== Excluded and deferred features
+
+The following GPars features are intentionally excluded or deferred:
+
+[cols="2,1,3"]
+|===
+|Feature |Status |Rationale
+
+|`callAsync` / `asyncFun`
+|Not planned
+|Superseded by `async`/`await` keywords
+
+|`getParallel` (PAWrapper)
+|Not planned
+|Legacy JSR-166 ParallelArray API; obsolete since Java 8 streams
+
+|`makeConcurrent` / `makeSequential` / `asConcurrent`
+|Not planned
+|Only if sufficient subsequent user demand
+
+|`DataflowOperator` / `Pipeline`
+|Deferred
+|Explicit `async` + channel `receive` covers the multi-input join use case; 
add if demand warrants declarative operator graphs
+
+|`KanbanFlow`
+|Deferred
+|Only if sufficient subsequent user demand
+
+|`SyncDataflowVariable` / `SyncDataflowQueue`
+|Deferred
+|Rendezvous semantics primarily for testing; low demand expected
+
+|Remote actors
+|Deferred
+|Requires networking dependencies (serialization, transport, discovery); 
belongs in a separate optional module
+
+|`BlockingActor`
+|Not planned
+|Legacy blocking pattern; virtual threads and `async`/`await` provide better 
alternatives
+
+|`GPars fork/join` (`AbstractForkJoinWorker`)
+|Not planned
+|JDK's `ForkJoinPool` and `RecursiveTask`/`RecursiveAction` are the standard 
API
+
+|`Dataflow.task { }`
+|Not planned
+|Superseded by `async { }` keyword
+
+|`Promise` interface
+|Not planned
+|Superseded by `Awaitable` interface
+|===
+
+== Compatibility
+
+=== GPars migration
+
+[cols="1,1"]
+|===
+|GPars |Groovy 6
+
+|`GParsPool.withPool(n) { }` |`ParallelScope.withPool(n) { }`
+|`eachParallel`, `collectParallel`, etc. |Same method names on `Collection`
+|`new Agent(initialValue)` |`Agent.create(initialValue)`
+|`new DataflowVariable()` |`new DataflowVariable()`
+|`new Dataflows()` |`new Dataflows()`
+|`Dataflow.task { }` |`async { }`
+|`reactor { }` |`Actor.reactor { }`
+|`@ActiveObject` / `@ActiveMethod` |Same annotations (in `groovy.transform`)
+|`promise.get()` |`await(awaitable)`
+|`whenAllBound(a, b, c, handler)` |`await(a, b, c)`
+|===
+
+== References
+
+* GEP-16: Async/Await (Groovy 6)
+* https://github.com/GPars/GPars[GPars on GitHub]

Reply via email to