Skip to content

GROOVY-12033: groovy.concurrent.Actor: pre-GA hardening — Stop sentin…#2555

Open
paulk-asert wants to merge 1 commit into
apache:masterfrom
paulk-asert:groovy12033
Open

GROOVY-12033: groovy.concurrent.Actor: pre-GA hardening — Stop sentin…#2555
paulk-asert wants to merge 1 commit into
apache:masterfrom
paulk-asert:groovy12033

Conversation

@paulk-asert
Copy link
Copy Markdown
Contributor

…el, error callback, bounded mailbox, per-actor pool

This comment was marked as outdated.

@paulk-asert paulk-asert marked this pull request as draft May 24, 2026 07:42
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 24, 2026

Codecov Report

❌ Patch coverage is 88.74172% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.2021%. Comparing base (6466667) to head (296731d).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
.../org/apache/groovy/runtime/async/DefaultActor.java 89.6825% 16 Missing and 10 partials ⚠️
src/main/java/groovy/concurrent/Actor.java 73.3333% 4 Missing ⚠️
src/main/java/groovy/concurrent/ActorContext.java 33.3333% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #2555        +/-   ##
==================================================
+ Coverage     68.1611%   68.2021%   +0.0410%     
- Complexity      33112      33186        +74     
==================================================
  Files            1508       1510         +2     
  Lines          126154     126433       +279     
  Branches        22890      22924        +34     
==================================================
+ Hits            85988      86230       +242     
- Misses          32540      32567        +27     
- Partials         7626       7636        +10     
Files with missing lines Coverage Δ
src/main/java/groovy/concurrent/ActorOptions.java 100.0000% <100.0000%> (ø)
src/main/java/groovy/concurrent/Actor.java 76.4706% <73.3333%> (-23.5294%) ⬇️
src/main/java/groovy/concurrent/ActorContext.java 33.3333% <33.3333%> (ø)
.../org/apache/groovy/runtime/async/DefaultActor.java 88.9680% <89.6825%> (+2.9680%) ⬆️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This comment was marked as outdated.

@paulk-asert paulk-asert force-pushed the groovy12033 branch 2 times, most recently from 39f80c1 to eab66d0 Compare May 27, 2026 09:16
…el, error callback, bounded mailbox, per-actor pool
@paulk-asert paulk-asert marked this pull request as ready for review May 27, 2026 10:52
@testlens-app
Copy link
Copy Markdown

testlens-app Bot commented May 27, 2026

✅ All tests passed ✅

⚠️ TestLens detected flakiness ⚠️

Test Summary

Check Project/Task Test Runs
Build and test / lts (17, macos-latest) :test ActorTest > testScheduleAtFixedRateCancelStopsFurtherFires() ❌ ✅

🏷️ Commit: 296731d
▶️ Tests: 20041 executed
⚪️ Checks: 30/30 completed


Learn more about TestLens at testlens.app.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

throw new RuntimeException("Interrupted while sending to actor", ie);
}
} catch (Throwable t) {
}
Comment on lines +503 to +534
// Chicken-and-egg: the task wants to deregister itself from
// pendingTimers after firing, but the ScheduledFuture doesn't
// exist until schedule(...) returns. Stash it via AtomicReference
// and read it inside the task.
AtomicReference<ScheduledFuture<?>> ref = new AtomicReference<>();
Runnable task = () -> {
// Hand the send off to the async executor so the scheduler
// thread never blocks on a full BLOCK mailbox. The scheduler
// is a shared resource (one per JVM); blocking it on one
// actor's bound would starve every other actor's timers.
AsyncSupport.getExecutor().execute(() -> {
try {
send(message);
} catch (IllegalStateException ignored) {
// Actor was stopped between schedule and fire — drop.
}
});
ScheduledFuture<?> f = ref.get();
if (f != null) pendingTimers.remove(f);
};
ScheduledFuture<?> future = AsyncSupport.getScheduler()
.schedule(task, delay.toNanos(), TimeUnit.NANOSECONDS);
// Race window: with a near-zero delay, the task can fire and
// reach `ref.get()` before this set() runs, observing null and
// skipping its self-deregistration. The future is then added to
// pendingTimers below and stays there until stop() cancels it
// (a no-op on an already-fired future). Functionally benign —
// just a dangling entry until shutdown — so we don't pay for a
// compareAndSet here. Documented for future readers.
ref.set(future);
pendingTimers.add(future);
return new TimerCancellable(future, pendingTimers);
Comment on lines +537 to +559
private Cancellable scheduleAtFixedRateInternal(T message, Duration initialDelay, Duration interval) {
checkOnWorkerThread("scheduleAtFixedRate()");
Objects.requireNonNull(message, "message must not be null");
Objects.requireNonNull(initialDelay, "initialDelay must not be null");
Objects.requireNonNull(interval, "interval must not be null");
// Periodic timers never self-deregister — they fire repeatedly
// until cancelled explicitly or via stop(). Each fire offloads
// the send to the async executor so a BLOCK-bounded actor can
// never starve the shared scheduler thread.
Runnable task = () -> AsyncSupport.getExecutor().execute(() -> {
try {
send(message);
} catch (IllegalStateException ignored) {
// Actor stopped — drop.
}
});
ScheduledFuture<?> future = AsyncSupport.getScheduler()
.scheduleAtFixedRate(task,
initialDelay.toNanos(),
interval.toNanos(),
TimeUnit.NANOSECONDS);
pendingTimers.add(future);
return new TimerCancellable(future, pendingTimers);
Comment on lines +189 to +191
* a full mailbox will block the shared scheduler thread; prefer
* {@link ActorOptions.Overflow#FAIL FAIL} or
* {@link ActorOptions.Overflow#DROP_NEWEST DROP_NEWEST} for
Comment on lines +307 to +312
actor.stop() // flips isActive() to false immediately
assert !actor.isActive() // refuses new sends from this point on

actor.stop() // graceful: processes remaining messages then exits
Thread.sleep(50)
assert !actor.isActive()
// The worker may still be draining queued messages. Poll for terminated
// when you actually need to be sure the actor has finished shutting down.
while (!actor.isTerminated()) Thread.sleep(10)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants