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 5732f223f0 Cover ordered unstashing in durable state behavior (#3659)
5732f223f0 is described below

commit 5732f223f0fd4933466eede39b15341ec5d83c15
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Fri Oct 23 21:21:20 2026 +0800

    Cover ordered unstashing in durable state behavior (#3659)
    
    * Cover ordered unstashing in durable state behavior
    
    Motivation:
    
    The durable state suite does not cover a large user stash followed by a new 
command after unstashing completes.
    
    Modification:
    
    Stash a deterministic command sequence, observe a processing barrier, 
unstash and assert every reply in order, then verify the next command succeeds.
    
    Result:
    
    Coverage protects ordered draining and continued processing without 
sleep-based coordination.
    
    Tests:
    
    - Passed: `persistence-typed-tests / Test / testOnly 
org.apache.pekko.persistence.typed.state.scaladsl.DurableStateRevisionSpec`.
    - Passed: `checkCodeStyle`.
    - Passed: `+headerCheckAll`.
    - Full `validatePullRequest` is left to remote CI.
    
    References:
    
    - Refs [akka/akka-core#32137](https://github.com/akka/akka-core/pull/32137).
    - Upstream commit: 
[a95c177fe9d3dce2e9f83eb3c4cd02e963101658](https://github.com/akka/akka-core/commit/a95c177fe9d3dce2e9f83eb3c4cd02e963101658).
    - The user-stash regression in `DurableStateRevisionSpec.scala` adapts the 
5000-event recovery/stash/unstash scenario from Akka's 
`DurableStateStashOverflowSpec.scala` in akka/akka-core#32137, retaining that 
source's exact 2020-2023 copyright notice alongside the existing Pekko file 
notice. CountStashed synchronization, exact sequence ordering and the 
subsequent-command assertions are Pekko additions.
    - Source snapshot: Akka `v2.9.0` 
(`bf20e967571574994ccb3cae411401f5a9021ab9`). Its 
[license](https://github.com/akka/akka-core/blob/v2.9.0/LICENSE) names 
2026-10-23 as the Change Date and Apache License 2.0 as the Change License. 
`LICENSE` and `NOTICE` record the source, and original copyright notices are 
retained. Headers were generated/checked with the project sbt tasks.
    
    * Keep licensing metadata outside the migration change
    
    Motivation:
    Keep PR #3659 scoped to its code or documentation change.
    
    Modification:
    Restore the root LICENSE and NOTICE files to main.
    
    Result:
    The migration diff retains its source headers without repository-level 
licensing edits.
    
    Tests:
    - git diff --check passed.
    - Verified every other file is unchanged; prior focused validation still 
applies.
    
    References:
    Refs #3659.
    
    * Name durable unstashing coverage for its contract #3659
    
    Motivation:
    The test demonstrates ordered unstashing and later-command progress rather 
than a proven stack-overflow regression.
    
    Modification:
    Name the test for those assertions and consolidate the Lightbend notices to 
cover 2009-2023.
    
    Result:
    The test states the behavior it verifies and retains full copyright-year 
coverage.
    
    Tests:
    Validation used a local integration checkout with current main and the 
other review fixes.
    
    - persistence-typed-tests / Test / testOnly 
org.apache.pekko.persistence.typed.state.scaladsl.DurableStateRevisionSpec: 5 
tests passed.
    - headerCreateAll, +headerCheckAll, native scalafmt, and git diff --check: 
passed.
    - Full validation is delegated to remote CI.
    
    References:
    Refs #3659.
---
 .../state/scaladsl/DurableStateRevisionSpec.scala  | 54 +++++++++++++++++++++-
 1 file changed, 53 insertions(+), 1 deletion(-)

diff --git 
a/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/state/scaladsl/DurableStateRevisionSpec.scala
 
b/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/state/scaladsl/DurableStateRevisionSpec.scala
index f02d907114..99fce196bb 100644
--- 
a/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/state/scaladsl/DurableStateRevisionSpec.scala
+++ 
b/persistence-typed-tests/src/test/scala/org/apache/pekko/persistence/typed/state/scaladsl/DurableStateRevisionSpec.scala
@@ -8,12 +8,15 @@
  */
 
 /*
- * Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
+ * Copyright (C) 2009-2023 Lightbend Inc. <https://www.lightbend.com>
  */
 
 package org.apache.pekko.persistence.typed.state.scaladsl
 
+import scala.concurrent.duration._
+
 import org.apache.pekko
+import pekko.Done
 import pekko.actor.Dropped
 import pekko.actor.testkit.typed.scaladsl.LogCapturing
 import pekko.actor.testkit.typed.scaladsl.LoggingTestKit
@@ -34,6 +37,12 @@ import com.typesafe.config.ConfigFactory
 
 object DurableStateRevisionSpec {
 
+  sealed trait UserStashCommand
+  case object Ready extends UserStashCommand
+  final case class Read(index: Int) extends UserStashCommand
+  case object CountStashed extends UserStashCommand
+  case object Unstash extends UserStashCommand
+
   def conf: Config = 
PersistenceTestKitDurableStateStorePlugin.config.withFallback(ConfigFactory.parseString(s"""
     pekko.loglevel = INFO
     """))
@@ -180,4 +189,47 @@ class DurableStateRevisionSpec
 
     }
   }
+
+  "The durable state user stash" must {
+    "unstash a large read-only batch in order and process subsequent commands" 
in {
+      import DurableStateRevisionSpec._
+
+      val size = 5000
+      val ready = TestProbe[Done]()
+      val counted = TestProbe[Int]()
+      val replies = TestProbe[Int]()
+      
system.settings.config.getBoolean("pekko.persistence.typed.recurse-when-unstashing-read-only-commands")
 shouldBe
+      false
+
+      val ref = spawn(Behaviors.setup[UserStashCommand] { _ =>
+        var stashed = 0
+        DurableStateBehavior[UserStashCommand, Boolean](
+          PersistenceId.ofUniqueId("read-only-user-stash"),
+          false,
+          (readEnabled, command) =>
+            command match {
+              case Ready                   => Effect.reply(ready.ref)(Done)
+              case Read(_) if !readEnabled =>
+                stashed += 1
+                Effect.stash()
+              case Read(index)  => Effect.reply(replies.ref)(index)
+              case CountStashed => Effect.reply(counted.ref)(stashed)
+              case Unstash      => Effect.persist(true).thenUnstashAll()
+            }).withStashCapacity(size)
+      })
+
+      // This reply proves recovery has completed before commands enter the 
user stash.
+      ref ! Ready
+      ready.expectMessage(Done)
+      (1 to size).foreach(index => ref ! Read(index))
+      ref ! CountStashed
+      counted.expectMessage(size)
+
+      ref ! Unstash
+      replies.receiveMessages(size) shouldBe (1 to size)
+      ref ! Read(size + 1)
+      replies.expectMessage(size + 1)
+      replies.expectNoMessage(100.millis)
+    }
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to