elsa-core/src/modules/Elsa.Scheduling/Services/LocalScheduler.cs
Max Brooks 50a3dc7921
Enhance thread safety with ConcurrentDictionary usage (#6760)
* Enhance thread safety with ConcurrentDictionary usage

Replaced `IDictionary` with `ConcurrentDictionary` for
both `_scheduledTasks` and `_scheduledTaskKeys` to
improve thread safety in a multi-threaded environment.

Updated methods `RegisterScheduledTask`,
`RemoveScheduledTask`, and `RemoveScheduledTasks` to
utilize the `Remove` method of `ConcurrentDictionary`,
ensuring safe and efficient removal of scheduled tasks.

* Refactor task registration and removal logic

Updated `RegisterScheduledTask` to use `AddOrUpdate` for streamlined task management. This change simplifies the addition and updating of scheduled tasks by consolidating logic into a single operation. Introduced `RemoveScheduledTask` method to handle task removal by name, improving code organization and clarity.

* Improve task removal handling in LocalScheduler

Modified the `LocalScheduler` class to enhance the removal process of scheduled tasks from the `_scheduledTaskKeys` collection. The removal operation now captures the result in a variable and includes a conditional check to log a warning if the task was not found, improving error handling and debugging capabilities.

* Refactor task removal in LocalScheduler

Updated the removal process for scheduled tasks in `_scheduledTasks`.
The new implementation collects all corresponding keys and attempts to remove them individually, logging warnings for any failures. This enhances error handling and provides better debugging information.

---------

Co-authored-by: Max Brooks <Max@compyl.com>
2025-07-15 14:01:38 +02:00

107 lines
4.1 KiB
C#

using System.Collections.Concurrent;
using System.Collections.Generic;
using Elsa.Extensions;
namespace Elsa.Scheduling.Services;
/// <summary>
/// Represents a local, in-memory scheduler that schedules tasks in-process.
/// </summary>
public class LocalScheduler : IScheduler
{
private readonly IServiceProvider _serviceProvider;
private readonly ConcurrentDictionary<string, IScheduledTask> _scheduledTasks = new ConcurrentDictionary<string, IScheduledTask>();
private readonly ConcurrentDictionary<IScheduledTask, ICollection<string>> _scheduledTaskKeys = new ConcurrentDictionary<IScheduledTask, ICollection<string>>();
/// <summary>
/// Initializes a new instance of the <see cref="LocalScheduler"/> class.
/// </summary>
public LocalScheduler(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
/// <inheritdoc />
public ValueTask ScheduleAsync(string name, ITask task, ISchedule schedule, CancellationToken cancellationToken = default)
{
return ScheduleAsync(name, task, schedule, null, cancellationToken);
}
/// <inheritdoc />
public ValueTask ScheduleAsync(string name, ITask task, ISchedule schedule, IEnumerable<string>? keys = null, CancellationToken cancellationToken = default)
{
var scheduleContext = new ScheduleContext(_serviceProvider, task);
var scheduledTask = schedule.Schedule(scheduleContext);
RegisterScheduledTask(name, scheduledTask, keys);
return ValueTask.CompletedTask;
}
/// <inheritdoc />
public ValueTask ClearScheduleAsync(string name, CancellationToken cancellationToken = default)
{
RemoveScheduledTask(name);
return ValueTask.CompletedTask;
}
/// <inheritdoc />
public ValueTask ClearScheduleAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default)
{
RemoveScheduledTasks(keys);
return ValueTask.CompletedTask;
}
private void RegisterScheduledTask(string name, IScheduledTask scheduledTask, IEnumerable<string>? keys = null)
{
_scheduledTasks.AddOrUpdate(
name,
addValueFactory: _ => scheduledTask,
updateValueFactory: (_, existingScheduledTask) =>
{
existingScheduledTask.Cancel();
var removed = _scheduledTaskKeys.TryRemove(existingScheduledTask, out ICollection<string>? _);
if (!removed)
System.Diagnostics.Debug.WriteLine($"[LocalScheduler] Warning: Tried to remove scheduled task keys for an existing scheduled task, but it was not present in _scheduledTaskKeys.");
return scheduledTask;
});
if (keys != null)
_scheduledTaskKeys[scheduledTask] = keys.ToList();
}
private void RemoveScheduledTask(string name)
{
if (_scheduledTasks.TryGetValue(name, out var existingScheduledTask))
{
_scheduledTaskKeys.Remove(existingScheduledTask, out _);
_scheduledTasks.Remove(name, out _);
existingScheduledTask.Cancel();
}
}
private void RemoveScheduledTasks(IEnumerable<string> keys)
{
foreach (var key in keys)
{
var scheduledTasks = _scheduledTaskKeys.Where(x => x.Value.Contains(key)).Select(x => x.Key).Distinct().ToList();
foreach (var scheduledTask in scheduledTasks)
{
// Collect all keys in _scheduledTasks that map to this scheduledTask
var matchingTaskKeys = _scheduledTasks.Where(x => x.Value == scheduledTask).Select(x => x.Key).ToList();
foreach (var taskKey in matchingTaskKeys)
{
var removed = _scheduledTasks.TryRemove(taskKey, out _);
if (!removed)
System.Diagnostics.Debug.WriteLine($"[LocalScheduler] Warning: Failed to remove scheduled task with key '{taskKey}' for '{key}' from _scheduledTasks.");
}
_scheduledTaskKeys.Remove(scheduledTask, out _);
scheduledTask.Cancel();
}
}
}
}