You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pekko.apache.org by he...@apache.org on 2026/07/26 09:08:45 UTC
(pekko) 01/01: feat: add logging variants for Java DSL stash and timer base classes
This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch feat/java-dsl-logging-variants
in repository https://gitbox.apache.org/repos/asf/pekko.git
commit c5f8de7d5a1d1250d446ba8309e83d01a63db831
Author: 虎鸣 <he...@alibaba-inc.com>
AuthorDate: Sun Jul 26 17:08:25 2026 +0800
feat: add logging variants for Java DSL stash and timer base classes
Motivation:
Java does not support multiple inheritance or trait mixin, so users
cannot combine AbstractActorWithStash/AbstractActorWithTimers with
ActorLogging. This forces Java users to manually implement logging
boilerplate when they need both stash/timer support and logging.
Modification:
Add 8 new abstract base classes that combine ActorLogging with each
stash and timer variant:
- AbstractLoggingActorWithStash / UntypedAbstractLoggingActorWithStash
- AbstractLoggingActorWithUnboundedStash / Untyped variant
- AbstractLoggingActorWithUnrestrictedStash / Untyped variant
- AbstractLoggingActorWithTimers / UntypedAbstractLoggingActorWithTimers
Result:
Java users can now extend a single base class to get both stash/timer
support and logging, matching the convenience Scala users get via
trait mixin.
Tests:
- sbt "actor-tests / Test / testOnly org.apache.pekko.actor.LoggingVariantActorsTest"
Passed: Total 9, Failed 0, Errors 0, Passed 9
- sbt "actor / mimaReportBinaryIssues" - success (pure additions)
References:
Fixes #2520
---
.../pekko/actor/LoggingVariantActorsTest.java | 298 +++++++++++++++++++++
.../org/apache/pekko/actor/AbstractActor.scala | 74 +++++
.../main/scala/org/apache/pekko/actor/Timers.scala | 32 +++
3 files changed, 404 insertions(+)
diff --git a/actor-tests/src/test/java/org/apache/pekko/actor/LoggingVariantActorsTest.java b/actor-tests/src/test/java/org/apache/pekko/actor/LoggingVariantActorsTest.java
new file mode 100644
index 0000000000..b6e6a96e01
--- /dev/null
+++ b/actor-tests/src/test/java/org/apache/pekko/actor/LoggingVariantActorsTest.java
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+/*
+ * Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
+ */
+
+package org.apache.pekko.actor;
+
+import java.time.Duration;
+
+import scala.concurrent.duration.FiniteDuration;
+
+import com.typesafe.config.ConfigFactory;
+
+import org.apache.pekko.event.Logging;
+import org.apache.pekko.testkit.PekkoJUnitJupiterActorSystemResource;
+import org.apache.pekko.testkit.TestProbe;
+import org.apache.pekko.testkit.javadsl.EventFilter;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class LoggingVariantActorsTest {
+
+ @RegisterExtension
+ static PekkoJUnitJupiterActorSystemResource actorSystemResource =
+ new PekkoJUnitJupiterActorSystemResource(
+ "LoggingVariantActorsTest",
+ ConfigFactory.parseString(
+ "pekko.loggers = [org.apache.pekko.testkit.TestEventListener]")
+ .withFallback(ActorWithBoundedStashSpec.testConf()));
+
+ private final ActorSystem system = actorSystemResource.getSystem();
+
+ public static class WithStashAndLogging extends AbstractLoggingActorWithStash {
+ int count = 0;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder()
+ .match(
+ String.class,
+ s -> {
+ if (count < 0) {
+ log().info("Replying with length of: {}", s);
+ getSender().tell(s.length(), getSelf());
+ } else if (count == 2) {
+ unstashAll();
+ count = -1;
+ } else {
+ stash();
+ count += 1;
+ }
+ })
+ .build();
+ }
+ }
+
+ public static class UntypedWithStashAndLogging extends UntypedAbstractLoggingActorWithStash {
+ int count = 0;
+
+ @Override
+ public void onReceive(Object msg) throws Exception {
+ if (msg instanceof String s) {
+ if (count < 0) {
+ log().info("Replying with length of: {}", s);
+ getSender().tell(s.length(), getSelf());
+ } else if (count == 2) {
+ unstashAll();
+ count = -1;
+ } else {
+ stash();
+ count += 1;
+ }
+ } else {
+ unhandled(msg);
+ }
+ }
+ }
+
+ public static class WithUnboundedStashAndLogging extends AbstractLoggingActorWithUnboundedStash {
+ int count = 0;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder()
+ .match(
+ String.class,
+ s -> {
+ if (count < 0) {
+ log().info("Replying with length of: {}", s);
+ getSender().tell(s.length(), getSelf());
+ } else if (count == 2) {
+ unstashAll();
+ count = -1;
+ } else {
+ stash();
+ count += 1;
+ }
+ })
+ .build();
+ }
+ }
+
+ public static class UntypedWithUnboundedStashAndLogging
+ extends UntypedAbstractLoggingActorWithUnboundedStash {
+ int count = 0;
+
+ @Override
+ public void onReceive(Object msg) throws Exception {
+ if (msg instanceof String s) {
+ if (count < 0) {
+ log().info("Replying with length of: {}", s);
+ getSender().tell(s.length(), getSelf());
+ } else if (count == 2) {
+ unstashAll();
+ count = -1;
+ } else {
+ stash();
+ count += 1;
+ }
+ } else {
+ unhandled(msg);
+ }
+ }
+ }
+
+ public static class WithUnrestrictedStashAndLogging
+ extends AbstractLoggingActorWithUnrestrictedStash {
+ int count = 0;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder()
+ .match(
+ String.class,
+ s -> {
+ if (count < 0) {
+ log().info("Replying with length of: {}", s);
+ getSender().tell(s.length(), getSelf());
+ } else if (count == 2) {
+ unstashAll();
+ count = -1;
+ } else {
+ stash();
+ count += 1;
+ }
+ })
+ .build();
+ }
+ }
+
+ public static class UntypedWithUnrestrictedStashAndLogging
+ extends UntypedAbstractLoggingActorWithUnrestrictedStash {
+ int count = 0;
+
+ @Override
+ public void onReceive(Object msg) throws Exception {
+ if (msg instanceof String s) {
+ if (count < 0) {
+ log().info("Replying with length of: {}", s);
+ getSender().tell(s.length(), getSelf());
+ } else if (count == 2) {
+ unstashAll();
+ count = -1;
+ } else {
+ stash();
+ count += 1;
+ }
+ } else {
+ unhandled(msg);
+ }
+ }
+ }
+
+ public static class WithTimersAndLogging extends AbstractLoggingActorWithTimers {
+ private ActorRef replyTo;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder()
+ .matchEquals(
+ "tick",
+ s -> {
+ log().info("Timer fired");
+ replyTo.tell("done", getSelf());
+ })
+ .match(
+ String.class,
+ s -> {
+ log().info("Received: {}", s);
+ replyTo = getSender();
+ getTimers().startSingleTimer("key", "tick", Duration.ofMillis(100));
+ })
+ .build();
+ }
+ }
+
+ public static class UntypedWithTimersAndLogging extends UntypedAbstractLoggingActorWithTimers {
+ private ActorRef replyTo;
+
+ @Override
+ public void onReceive(Object msg) throws Exception {
+ if ("tick".equals(msg)) {
+ log().info("Timer fired");
+ replyTo.tell("done", getSelf());
+ } else if (msg instanceof String s) {
+ log().info("Received: {}", s);
+ replyTo = getSender();
+ getTimers().startSingleTimer("key", "tick", Duration.ofMillis(100));
+ } else {
+ unhandled(msg);
+ }
+ }
+ }
+
+ private void testStashWithLogging(Props props) {
+ ActorRef ref = system.actorOf(props);
+ final TestProbe probe = new TestProbe(system);
+ probe.send(ref, "Hello");
+ probe.send(ref, "Hello2");
+ probe.send(ref, "Hello12");
+ probe.expectMsg(5);
+ }
+
+ @Test
+ public void mustSupportStashWithLogging() {
+ testStashWithLogging(Props.create(WithStashAndLogging.class));
+ }
+
+ @Test
+ public void mustSupportUntypedStashWithLogging() {
+ testStashWithLogging(Props.create(UntypedWithStashAndLogging.class));
+ }
+
+ @Test
+ public void mustSupportUnboundedStashWithLogging() {
+ testStashWithLogging(Props.create(WithUnboundedStashAndLogging.class));
+ }
+
+ @Test
+ public void mustSupportUntypedUnboundedStashWithLogging() {
+ testStashWithLogging(Props.create(UntypedWithUnboundedStashAndLogging.class));
+ }
+
+ @Test
+ public void mustSupportUnrestrictedStashWithLogging() {
+ testStashWithLogging(
+ Props.create(WithUnrestrictedStashAndLogging.class)
+ .withMailbox("pekko.actor.mailbox.unbounded-deque-based"));
+ }
+
+ @Test
+ public void mustSupportUntypedUnrestrictedStashWithLogging() {
+ testStashWithLogging(
+ Props.create(UntypedWithUnrestrictedStashAndLogging.class)
+ .withMailbox("pekko.actor.mailbox.unbounded-deque-based"));
+ }
+
+ @Test
+ public void mustSupportTimersWithLogging() {
+ ActorRef ref = system.actorOf(Props.create(WithTimersAndLogging.class));
+ final TestProbe probe = new TestProbe(system);
+ probe.send(ref, "start");
+ probe.expectMsg(FiniteDuration.create(3, "seconds"), "done");
+ }
+
+ @Test
+ public void mustSupportUntypedTimersWithLogging() {
+ ActorRef ref = system.actorOf(Props.create(UntypedWithTimersAndLogging.class));
+ final TestProbe probe = new TestProbe(system);
+ probe.send(ref, "start");
+ probe.expectMsg(FiniteDuration.create(3, "seconds"), "done");
+ }
+
+ @Test
+ public void mustEmitLogEvents() {
+ new EventFilter(Logging.Info.class, system)
+ .startsWith("Replying with length of")
+ .occurrences(2)
+ .intercept(
+ () -> {
+ ActorRef ref = system.actorOf(Props.create(WithStashAndLogging.class));
+ final TestProbe probe = new TestProbe(system);
+ probe.send(ref, "Hello");
+ probe.send(ref, "Hello2");
+ probe.send(ref, "Hello12");
+ probe.expectMsg(5);
+ return null;
+ });
+ }
+}
diff --git a/actor/src/main/scala/org/apache/pekko/actor/AbstractActor.scala b/actor/src/main/scala/org/apache/pekko/actor/AbstractActor.scala
index 7bb3ce71a0..d6728b1d75 100644
--- a/actor/src/main/scala/org/apache/pekko/actor/AbstractActor.scala
+++ b/actor/src/main/scala/org/apache/pekko/actor/AbstractActor.scala
@@ -492,3 +492,77 @@ abstract class AbstractActorWithUnrestrictedStash extends AbstractActor with Abs
* @since 2.0.0
*/
abstract class UntypedAbstractActorWithUnrestrictedStash extends UntypedAbstractActor with AbstractActorStashSupport
+
+/**
+ * Java API: compatible with lambda expressions
+ *
+ * Actor base class that mixes in logging and a stash.
+ * By default requests a `DequeBasedMessageQueueSemantics` mailbox.
+ *
+ * @see [[pekko.actor.AbstractActorWithStash]] for details on how `Stash` works.
+ *
+ * @since 2.0.0
+ */
+abstract class AbstractLoggingActorWithStash extends AbstractActor with AbstractActorStashSupport
+ with RequiresMessageQueue[DequeBasedMessageQueueSemantics] with ActorLogging
+
+/**
+ * Java API: compatible with lambda expressions
+ *
+ * Actor base class that mixes in logging and a stash.
+ * By default requests a `DequeBasedMessageQueueSemantics` mailbox.
+ *
+ * @see [[pekko.actor.AbstractActorWithStash]] for details on how `Stash` works.
+ *
+ * @since 2.0.0
+ */
+abstract class UntypedAbstractLoggingActorWithStash extends UntypedAbstractActor with AbstractActorStashSupport
+ with RequiresMessageQueue[DequeBasedMessageQueueSemantics] with ActorLogging
+
+/**
+ * Java API: compatible with lambda expressions
+ *
+ * Actor base class that mixes in logging and an unbounded stash.
+ *
+ * @see [[pekko.actor.AbstractActorWithUnboundedStash]] for details on how `Stash` works.
+ *
+ * @since 2.0.0
+ */
+abstract class AbstractLoggingActorWithUnboundedStash extends AbstractActor with AbstractActorStashSupport
+ with RequiresMessageQueue[UnboundedDequeBasedMessageQueueSemantics] with ActorLogging
+
+/**
+ * Java API: compatible with lambda expressions
+ *
+ * Actor base class that mixes in logging and an unbounded stash.
+ *
+ * @see [[pekko.actor.AbstractActorWithUnboundedStash]] for details on how `Stash` works.
+ *
+ * @since 2.0.0
+ */
+abstract class UntypedAbstractLoggingActorWithUnboundedStash extends UntypedAbstractActor with AbstractActorStashSupport
+ with RequiresMessageQueue[UnboundedDequeBasedMessageQueueSemantics] with ActorLogging
+
+/**
+ * Java API: compatible with lambda expressions
+ *
+ * Actor base class that mixes in logging and an unrestricted stash.
+ *
+ * @see [[pekko.actor.AbstractActorWithUnrestrictedStash]] for details on how `Stash` works.
+ *
+ * @since 2.0.0
+ */
+abstract class AbstractLoggingActorWithUnrestrictedStash extends AbstractActor with AbstractActorStashSupport
+ with ActorLogging
+
+/**
+ * Java API: compatible with lambda expressions
+ *
+ * Actor base class that mixes in logging and an unrestricted stash.
+ *
+ * @see [[pekko.actor.AbstractActorWithUnrestrictedStash]] for details on how `Stash` works.
+ *
+ * @since 2.0.0
+ */
+abstract class UntypedAbstractLoggingActorWithUnrestrictedStash
+ extends UntypedAbstractActor with AbstractActorStashSupport with ActorLogging
diff --git a/actor/src/main/scala/org/apache/pekko/actor/Timers.scala b/actor/src/main/scala/org/apache/pekko/actor/Timers.scala
index c75574be4d..7cbc1bcebc 100644
--- a/actor/src/main/scala/org/apache/pekko/actor/Timers.scala
+++ b/actor/src/main/scala/org/apache/pekko/actor/Timers.scala
@@ -98,6 +98,38 @@ abstract class UntypedAbstractActorWithTimers extends UntypedAbstractActor with
final def getTimers: TimerScheduler = timers
}
+/**
+ * Java API: Support for scheduled `self` messages via [[TimerScheduler]], with logging.
+ *
+ * Timers are bound to the lifecycle of the actor that owns it,
+ * and thus are cancelled automatically when it is restarted or stopped.
+ *
+ * @since 2.0.0
+ */
+abstract class AbstractLoggingActorWithTimers extends AbstractActor with Timers with ActorLogging {
+
+ /**
+ * Start and cancel timers via the enclosed `TimerScheduler`.
+ */
+ final def getTimers: TimerScheduler = timers
+}
+
+/**
+ * Java API: Support for scheduled `self` messages via [[TimerScheduler]], with logging.
+ *
+ * Timers are bound to the lifecycle of the actor that owns it,
+ * and thus are cancelled automatically when it is restarted or stopped.
+ *
+ * @since 2.0.0
+ */
+abstract class UntypedAbstractLoggingActorWithTimers extends UntypedAbstractActor with Timers with ActorLogging {
+
+ /**
+ * Start and cancel timers via the enclosed `TimerScheduler`.
+ */
+ final def getTimers: TimerScheduler = timers
+}
+
/**
* Support for scheduled `self` messages in an actor.
* It is used by mixing in trait `Timers` in Scala or extending `AbstractActorWithTimers`
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pekko.apache.org
For additional commands, e-mail: commits-help@pekko.apache.org