Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal partial class StatelessWorkerGrainContext : IGrainContext, IAsyncDispos
private double _integralTerm = 0d;
private int _detectedIdleCyclesCount = 0;
private readonly int _minIdleCyclesBeforeRemoval;
private readonly bool _isIdleWorkerRemovalStrategy;

public StatelessWorkerGrainContext(
GrainAddress address,
Expand All @@ -52,7 +53,8 @@ public StatelessWorkerGrainContext(
var strategy = (StatelessWorkerPlacement)_shared.PlacementStrategy;
var options = _shared.StatelessWorkerOptions;

if (strategy.RemoveIdleWorkers && options.RemoveIdleWorkers)
_isIdleWorkerRemovalStrategy = strategy.RemoveIdleWorkers && options.RemoveIdleWorkers;
if (_isIdleWorkerRemovalStrategy)
{
_minIdleCyclesBeforeRemoval = options.MinIdleCyclesBeforeRemoval > 0 ? options.MinIdleCyclesBeforeRemoval : 1;
_inspectionTimer = new Timer(
Expand Down Expand Up @@ -172,9 +174,23 @@ private async Task RunMessageLoop()

if (_workers.Count == 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can there be a scenario where _workers.Count reached 0, but in the queue there is an item to process WorkItemType.Message which will then create a new worker? In that case a new worker will be created right after.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very unlikely since the destruction of the workers comes after the fact of a grain disposing (which means it wont receive more messages), but possible! If that happens, the dispose command will be processed after that message.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative would be to stop the disposal if a worker exists within the SW context, we should not do that, we should respect the call for disposal from outside!

{
// When the last worker is destroyed, we can consider the stateless worker grain
// activation to be destroyed as well
// When the last worker is destroyed, we can consider the stateless worker grain activation to be destroyed as well
_shared.InternalRuntime.Catalog.UnregisterMessageTarget(this);

if (_isIdleWorkerRemovalStrategy)
{
var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
EnqueueWorkItem(WorkItemType.DisposeAsync, new DisposeAsyncWorkItemState(completion));

// DO NOT await this as it would deadlock the work loop!
_ = completion.Task.ContinueWith(t =>
{
if (t.Exception is { } ex)
{
LogErrorInMessageLoop(_shared.Logger, ex);
}
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Current);
}
}
break;
}
Expand Down Expand Up @@ -343,16 +359,12 @@ private async Task DeactivatedTaskInternal(TaskCompletionSource completion)

private async Task DisposeAsyncInternal(TaskCompletionSource completion)
{
try
{
if (_inspectionTimer != null)
{
await _inspectionTimer.DisposeAsync();
_inspectionTimer = null;
}
}
catch
if (_inspectionTimer != null)
{
await _inspectionTimer.DisposeAsync().AsTask()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the change here needed? what is the benefit of AsTask().ConfigureAwait(...) against former DisposeAsync() -> only to suppress exceptions?

@ledjon-behluli ledjon-behluli Aug 5, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only to suppress exceptions

Yes, this is an "just in case" thing so the rest of the method continues

.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext);

_inspectionTimer = null;
}

try
Expand Down
Loading