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) {