Skip to content

PersistentActor enters infinite restart loop when RecoveryCompleted handler throws #3249

Description

@He-Pin

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:

  1. finally transitToProcessingState() runs — changes state
  2. Exception propagates to outer catch which calls returnRecoveryPermit() and rethrows
  3. No context.stop(self) is called
  4. No onRecoveryFailure() callback is invoked
  5. Default supervision (Restart with unlimited retries) causes infinite restart loop
  6. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions