Motivation
When a classic PersistentActor's RecoveryCompleted handler throws an exception, the actor enters an infinite restart loop instead of stopping. This is inconsistent with every other recovery failure path, which all explicitly call context.stop(self).
Current Behavior
In Eventsourced.scala:786-814, when RecoveryCompleted handler throws:
finally transitToProcessingState() runs — changes state
- Exception propagates to outer
catch which calls returnRecoveryPermit() and rethrows
- No
context.stop(self) is called
- No
onRecoveryFailure() callback is invoked
- Default supervision (
Restart with unlimited retries) causes infinite restart loop
- Same journal events replayed → same exception → forever
Comparison with Other Recovery Failure Paths
| Failure Scenario |
onRecoveryFailure() |
context.stop(self) |
Result |
ReplayedMessage throws |
Yes |
Yes |
Actor stops |
ReplayMessagesFailure |
Yes |
Yes |
Actor stops |
RecoveryTick timeout |
Yes |
Yes |
Actor stops |
RecoveryCompleted throws |
No |
No |
Restart loop |
Expected Behavior
The RecoveryCompleted failure path should be consistent with other recovery failure paths — call onRecoveryFailure() and context.stop(self) to prevent the infinite restart loop.
Reproduction
Confirmed by existing test RecoveryPermitterSpec.scala:174-197 which observes 5 consecutive restarts and must manually call system.stop() to break the loop.
Environment
- Pekko Persistence (any version)
Eventsourced.scala:786-814
References
None
Motivation
When a classic
PersistentActor'sRecoveryCompletedhandler throws an exception, the actor enters an infinite restart loop instead of stopping. This is inconsistent with every other recovery failure path, which all explicitly callcontext.stop(self).Current Behavior
In
Eventsourced.scala:786-814, whenRecoveryCompletedhandler throws:finally transitToProcessingState()runs — changes statecatchwhich callsreturnRecoveryPermit()and rethrowscontext.stop(self)is calledonRecoveryFailure()callback is invokedRestartwith unlimited retries) causes infinite restart loopComparison with Other Recovery Failure Paths
onRecoveryFailure()context.stop(self)ReplayedMessagethrowsReplayMessagesFailureRecoveryTicktimeoutRecoveryCompletedthrowsExpected Behavior
The
RecoveryCompletedfailure path should be consistent with other recovery failure paths — callonRecoveryFailure()andcontext.stop(self)to prevent the infinite restart loop.Reproduction
Confirmed by existing test
RecoveryPermitterSpec.scala:174-197which observes 5 consecutive restarts and must manually callsystem.stop()to break the loop.Environment
Eventsourced.scala:786-814References
None