This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 80048b7aa1 fix: preserve Runnable hash codes in PekkoForkJoinTask
(#3668)
80048b7aa1 is described below
commit 80048b7aa134c8fa7f8b515edafa1bdb038de649
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Fri Oct 23 16:21:51 2026 +0800
fix: preserve Runnable hash codes in PekkoForkJoinTask (#3668)
Motivation:
Fresh fork-join task wrappers do not preserve their Runnable's hash code.
Modification:
Delegate hashCode to the Runnable with a final override and document the
stability requirement. Cover hash boundaries and independent wrapper
identity, completion, and cancellation in MailboxTaskSubmissionSpec.
Result:
Fresh wrappers retain the Runnable's hash code while keeping separate
fork-join task state.
Tests:
- New regressions: 2 failures before the fix; all 6 suite tests pass after
the fix on Scala 2.13.18 and 3.3.8, JDK 17.
- All 6 suite tests also passed on Scala 2.13.18, JDK 25.0.2.
- Related dispatcher/mailbox/fork-join suites: 73 passed, 2 JDK-dependent
pending tests.
- Native scalafmt, headerCreateAll, checkCodeStyle including
+headerCheckAll, and git diff --check passed.
- +mimaReportBinaryIssues passed on Scala 2.13.18 and 3.3.8.
- sortImports hit an existing SortImports/Scalafix NoSuchMethodError;
unrelated generated changes were discarded.
- validatePullRequest was stopped after a TcpConnectionSpec timeout.
Isolated rerun: 35 passed, 1 ignored, 1 pending, without code changes.
- Auditable Qoder stdout review and follow-up: no must-fix findings.
References:
Refs #3621, Refs #3628.
---
.../pekko/dispatch/MailboxTaskSubmissionSpec.scala | 47 +++++++++++++++++++++-
.../dispatch/ForkJoinExecutorConfigurator.scala | 7 ++++
2 files changed, 53 insertions(+), 1 deletion(-)
diff --git
a/actor-tests/src/test/scala/org/apache/pekko/dispatch/MailboxTaskSubmissionSpec.scala
b/actor-tests/src/test/scala/org/apache/pekko/dispatch/MailboxTaskSubmissionSpec.scala
index ad731104fc..597d9b8261 100644
---
a/actor-tests/src/test/scala/org/apache/pekko/dispatch/MailboxTaskSubmissionSpec.scala
+++
b/actor-tests/src/test/scala/org/apache/pekko/dispatch/MailboxTaskSubmissionSpec.scala
@@ -21,7 +21,7 @@ import java.util.concurrent.{ ForkJoinPool, ForkJoinTask,
ForkJoinWorkerThread }
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger }
import org.apache.pekko
-import pekko.dispatch.ForkJoinExecutorConfigurator.PekkoForkJoinPool
+import pekko.dispatch.ForkJoinExecutorConfigurator.{ PekkoForkJoinPool,
PekkoForkJoinTask }
import pekko.testkit.PekkoSpec
/**
@@ -75,6 +75,51 @@ class MailboxTaskSubmissionSpec extends PekkoSpec {
}
}
+ "PekkoForkJoinTask" must {
+
+ "preserve the Runnable hash code across fresh wrappers" in {
+ for (hash <- Seq(Int.MinValue, -1, 0, 1, Int.MaxValue)) {
+ val runnable = new Runnable {
+ final override def run(): Unit = ()
+ final override def hashCode(): Int = hash
+ }
+ val first = new PekkoForkJoinTask(runnable)
+ val second = new PekkoForkJoinTask(runnable)
+
+ withClue(s"Runnable hash code $hash: ") {
+ first.hashCode() shouldBe hash
+ second.hashCode() shouldBe hash
+ (first should not).equal(second)
+ }
+ }
+ }
+
+ "retain independent completion and cancellation for wrappers with the same
hash code" in {
+ val runs = new AtomicInteger(0)
+ val runnable = new Runnable {
+ final override def run(): Unit = { runs.incrementAndGet() }
+ final override def hashCode(): Int = 42
+ }
+ val cancelled = new PekkoForkJoinTask(runnable)
+ val completed = new PekkoForkJoinTask(runnable)
+ val pending = new PekkoForkJoinTask(runnable)
+
+ cancelled.cancel(false) shouldBe true
+ completed.invoke()
+
+ cancelled.isCancelled shouldBe true
+ completed.isCompletedNormally shouldBe true
+ pending.isDone shouldBe false
+ runs.get() shouldBe 1
+ Set(cancelled, completed, pending).size shouldBe 3
+ Seq(cancelled, completed, pending).foreach(_.hashCode() shouldBe 42)
+
+ pending.invoke()
+ pending.isCompletedNormally shouldBe true
+ runs.get() shouldBe 2
+ }
+ }
+
"PekkoForkJoinPool" must {
"not run a cancelled ForkJoinTask submitted as a Runnable" in {
diff --git
a/actor/src/main/scala/org/apache/pekko/dispatch/ForkJoinExecutorConfigurator.scala
b/actor/src/main/scala/org/apache/pekko/dispatch/ForkJoinExecutorConfigurator.scala
index 5b056398fb..387b78a554 100644
---
a/actor/src/main/scala/org/apache/pekko/dispatch/ForkJoinExecutorConfigurator.scala
+++
b/actor/src/main/scala/org/apache/pekko/dispatch/ForkJoinExecutorConfigurator.scala
@@ -77,6 +77,13 @@ object ForkJoinExecutorConfigurator {
*/
@SerialVersionUID(1L)
final class PekkoForkJoinTask(runnable: Runnable) extends ForkJoinTask[Unit]
{
+
+ /**
+ * Preserve the wrapped Runnable's hash code across fresh task wrappers,
provided that the
+ * Runnable's hash code is stable. Each wrapper retains its own identity
and completion state.
+ */
+ final override def hashCode(): Int = runnable.hashCode()
+
override def getRawResult(): Unit = ()
override def setRawResult(unit: Unit): Unit = ()
override def exec(): Boolean =
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]