Stop PersistentActor by default when RecoveryCompleted handler fails#3359
Merged
Merged
Conversation
Motivation: A classic PersistentActor can restart indefinitely under the default supervision strategy when its RecoveryCompleted handler throws. Modification: Wrap exceptions from the RecoveryCompleted handler in ActorInitializationException before passing them to the parent supervisor. Keep recovery permit cleanup in the existing outer catch, add directional tests for the default Stop decision and a custom parent Restart decision, and document the supervision contract. Result: The default strategy stops the actor instead of looping, while a custom parent supervisor can still resume or restart it. The original failure remains available as the exception cause. Tests: - sbt "persistence / Test / testOnly org.apache.pekko.persistence.RecoveryPermitterSpec" (7 passed; restart test failed before the fix) - sbt "persistence / Test / test" (62 passed) - sbt docs/paradox - sbt headerCreateAll - sbt +headerCheckAll - sbt checkCodeStyle - sbt validatePullRequest (24m44s; failed only on unchanged baseline/environment issues: x86 LevelDB JNI on macOS ARM64 and IntegrationDocTest using pekko.dispatch.UnboundedMailbox) - sbt sortImports (not completed: repository Scalafix rule failed with scala.meta NoSuchMethodError) - scalafmt --list --mode diff-ref=origin/main - git diff --check - Qoder stdout review: No must-fix findings References: Fixes apache#3234, Fixes apache#3249
He-Pin
force-pushed
the
fix-3234-recovery-completed-failure
branch
from
July 17, 2026 06:25
4818e0e to
074671f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
A classic
PersistentActorcan enter an unbounded restart loop when itsRecoveryCompletedhandler throws because the Classic default decider restarts ordinary exceptions. Issues #3234 and #3249 describe the same trigger and root cause.RecoveryCompletedruns after event replay has succeeded, so this failure should remain visible to the parent supervisor instead of being translated into anonRecoveryFailurereplay failure.Modification
NonFatalexceptions from theRecoveryCompletedhandler inActorInitializationExceptionand pass them to Classic supervision.Result
The default supervision strategy stops the actor instead of repeatedly restarting it. A custom parent strategy can still Resume or Restart the same
ActorRef, and the original exception is retained as the cause.Tests
sbt "persistence / Test / testOnly org.apache.pekko.persistence.RecoveryPermitterSpec"— restart-direction test failed before the production change; 7 passed after itsbt "persistence / Test / test"— 62 passedsbt docs/paradox— passed with existing duplicate-anchor warningssbt headerCreateAll— passedsbt +headerCheckAll— passed for Scala 2.13.18 and 3.3.8sbt checkCodeStyle— passedsbt validatePullRequest— ran for 24m44s; affected persistence, typed, query, cluster-sharding, multi-JVM, and docs tests completed. Overall exit 1 is limited to unchanged baseline/environment failures:LeveldbJournalNativeSpeccannot load the x86leveldbjni-all1.8 binary on macOS ARM64, andIntegrationDocTestreferences the existing invalid classpekko.dispatch.UnboundedMailboxsbt sortImports— not completed because the repository Scalafix rule fails against the current scala.meta withNoSuchMethodError; no unrelated changes were retainedscalafmt --list --mode diff-ref=origin/main— passedgit diff --check— passedReferences
Fixes #3234
Fixes #3249