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