diff --git a/src/Orleans.Core.Abstractions/Timers/TimerCreationOptions.cs b/src/Orleans.Core.Abstractions/Timers/TimerCreationOptions.cs
index 99981851d0..21c8a6793d 100644
--- a/src/Orleans.Core.Abstractions/Timers/TimerCreationOptions.cs
+++ b/src/Orleans.Core.Abstractions/Timers/TimerCreationOptions.cs
@@ -36,8 +36,14 @@ namespace Orleans.Runtime;
public bool Interleave { get; init; }
///
- /// Gets a value indicating whether callbacks scheduled by this timer should keep the grain activation active. Defaults to .
+ /// Gets a value indicating whether callbacks scheduled by this timer should extend the lifetime of the grain activation. Defaults to .
///
+ ///
+ /// If this value is , timer callbacks will not extend a grain activation's lifetime.
+ /// If a grain is only processing timer callbacks and no other messages, the grain will be collected after its idle collection period expires.
+ /// If this value is , timer callback will extend a grain activation's lifetime.
+ /// If the timer period is shorter than the grain's idle collection period, the grain will not be collected due to idleness.
+ ///
public bool KeepAlive { get; init; }
}
\ No newline at end of file
diff --git a/src/Orleans.Runtime/Timers/GrainTimer.cs b/src/Orleans.Runtime/Timers/GrainTimer.cs
index 2335125acd..b8b9645dcb 100644
--- a/src/Orleans.Runtime/Timers/GrainTimer.cs
+++ b/src/Orleans.Runtime/Timers/GrainTimer.cs
@@ -95,6 +95,8 @@ protected void ScheduleTickOnActivation()
_grainContext.ReceiveMessage(msg);
}
+ protected abstract Task InvokeCallback();
+
private ValueTask InvokeTickAsync(CancellationToken cancellationToken)
{
try
@@ -110,16 +112,20 @@ private ValueTask InvokeTickAsync(CancellationToken cancellationToken)
// If the task is not completed, we need to await the tick asynchronously.
if (task is { IsCompletedSuccessfully: false })
{
+ // Complete asynchronously.
return AwaitCallbackTask(task, cancellationToken);
}
-
- if (Logger.IsEnabled(LogLevel.Trace))
+ else
{
- Logger.LogTrace((int)ErrorCode.TimerAfterCallback, "Completed timer callback for timer {TimerName}", this);
+ // Complete synchronously.
+ if (Logger.IsEnabled(LogLevel.Trace))
+ {
+ Logger.LogTrace((int)ErrorCode.TimerAfterCallback, "Completed timer callback for timer {TimerName}", this);
+ }
+
+ OnTickCompleted();
+ return new(Response.Completed);
}
-
- OnTickCompleted();
- return new(Response.Completed);
}
catch (Exception exc)
{