diff --git a/src/Netclaw.Actors/Reminders/ReminderExecutionActor.cs b/src/Netclaw.Actors/Reminders/ReminderExecutionActor.cs
index 3caadb5e9..38897dbcf 100644
--- a/src/Netclaw.Actors/Reminders/ReminderExecutionActor.cs
+++ b/src/Netclaw.Actors/Reminders/ReminderExecutionActor.cs
@@ -23,8 +23,14 @@ namespace Netclaw.Actors.Reminders;
/// Creates a session pipeline, sends reminder instructions, collects output,
/// and reports success/failure to .
///
-internal sealed class ReminderExecutionActor : ReceiveActor
+internal sealed class ReminderExecutionActor : ReceiveActor, IWithTimers
{
+ ///
+ /// Timer keys for the two IWithTimers backstops. Distinct keys so arming
+ /// one never cancels the other.
+ ///
+ private static readonly object ExecutionAttemptTimerKey = new();
+ private static readonly object DeliveryBackstopTimerKey = new();
///
/// Backstop timeout for CurrentSession reminders with
/// DeliveryRequired=true waiting for a
@@ -69,8 +75,13 @@ internal sealed class ReminderExecutionActor : ReceiveActor
private string? _sessionIdValue;
private bool _awaitingDeliveryResult;
private ReminderId? _expectedReminderDeliveryKey;
- private ICancelable? _deliveryTimeoutCancelable;
- private ICancelable? _executionTimeoutCancelable;
+
+ ///
+ /// Timer scheduler injected by Akka for . Timers
+ /// are canceled automatically when the actor stops, so the one-shot
+ /// backstops can never fire after settlement stops the actor.
+ ///
+ public ITimerScheduler Timers { get; set; } = null!;
private bool _settlementStarted;
private bool RoutesBackToOriginSession => _definition.Delivery.Kind == DeliveryKind.CurrentSession;
@@ -126,11 +137,10 @@ protected override void PreStart()
_log.Info(
$"ReminderExecution Dispatched: execution_id={_executionId} reminder_id={_definition.Id} title={_definition.Title} schedule_type={_definition.Schedule.Type} dispatched_at={_dispatchedAt} delivery_kind={_definition.Delivery.Kind}");
- _executionTimeoutCancelable = Context.System.Scheduler.ScheduleTellOnceCancelable(
- ExecutionAttemptTimeout,
- Self,
+ Timers.StartSingleTimer(
+ ExecutionAttemptTimerKey,
ExecutionAttemptTimeoutReached.Instance,
- Self);
+ ExecutionAttemptTimeout);
Self.Tell(new ExecutionStarted());
RunTask(RoutesBackToOriginSession ? InitializeCurrentSessionAsync : InitializeAsync);
@@ -334,11 +344,10 @@ private void BeginAwaitingDeliveryResult(ReminderId reminderDeliveryKey)
{
_awaitingDeliveryResult = true;
_expectedReminderDeliveryKey = reminderDeliveryKey;
- _deliveryTimeoutCancelable = Context.System.Scheduler.ScheduleTellOnceCancelable(
- DeliveryObservedTimeout,
- Self,
+ Timers.StartSingleTimer(
+ DeliveryBackstopTimerKey,
new DeliveryBackstopTimeout(reminderDeliveryKey),
- Self);
+ DeliveryObservedTimeout);
}
private void HandleDeliveryResult(ReminderDeliveryResult result)
@@ -356,8 +365,7 @@ private void HandleDeliveryResult(ReminderDeliveryResult result)
return;
_awaitingDeliveryResult = false;
- _deliveryTimeoutCancelable?.Cancel();
- _deliveryTimeoutCancelable = null;
+ Timers.Cancel(DeliveryBackstopTimerKey);
if (result.Delivered)
{
@@ -384,7 +392,6 @@ private void HandleDeliveryBackstopTimeout(DeliveryBackstopTimeout msg)
return;
_awaitingDeliveryResult = false;
- _deliveryTimeoutCancelable = null;
_log.Warning(
"reminder_delivery_observation_timeout execution_id={ExecutionId} reminder_id={ReminderId} key={ReminderDeliveryKey} timeout={Timeout}",
_executionId, _definition.Id, msg.ReminderDeliveryKey, DeliveryObservedTimeout);
@@ -569,8 +576,7 @@ private void ReportOutcome(bool success, string? errorMessage = null)
_settlementStarted = true;
Context.SetReceiveTimeout(null);
- _executionTimeoutCancelable?.Cancel();
- _executionTimeoutCancelable = null;
+ Timers.Cancel(ExecutionAttemptTimerKey);
_completed = true;
@@ -610,11 +616,6 @@ private void HandleExecutionAccepted(ReminderExecutionAccepted accepted)
protected override void PostStop()
{
- _deliveryTimeoutCancelable?.Cancel();
- _deliveryTimeoutCancelable = null;
- _executionTimeoutCancelable?.Cancel();
- _executionTimeoutCancelable = null;
-
try
{
_handle.Dispose();