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: 虎鸣 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
