Skip to content

Commit a5dc667

Browse files
committed
Enhance logging in recurring tasks: add error handling and logger support to prevent crashes in scheduled timers.
1 parent ddffc29 commit a5dc667

5 files changed

Lines changed: 42 additions & 9 deletions

File tree

src/modules/Elsa.Common/Multitenancy/EventHandlers/TenantTaskManager.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,12 @@ private async Task StartRecurringTasksAsync(ITenantScope tenantScope, ITaskExecu
118118
{
119119
logger.LogInformation(e, "Recurring task {TaskType} was cancelled", task.GetType().Name);
120120
}
121-
});
121+
catch (Exception e)
122+
{
123+
// Log but don't rethrow - recurring tasks should not crash the host
124+
logger.LogError(e, "Recurring task {TaskType} failed with an error", task.GetType().Name);
125+
}
126+
}, logger);
122127

123128
_scheduledTimers.Add(timer);
124129
await task.StartAsync(cancellationToken);
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using Cronos;
2+
using Microsoft.Extensions.Logging;
23

34
namespace Elsa.Common.RecurringTasks;
45

56
public class CronSchedule(ISystemClock systemClock, CronExpression expression) : ISchedule
67
{
7-
public ScheduledTimer CreateTimer(Func<Task> action)
8+
public ScheduledTimer CreateTimer(Func<Task> action, ILogger? logger = null)
89
{
9-
return new ScheduledTimer(action, () => expression.GetNextOccurrence(systemClock.UtcNow.DateTime)!.Value - systemClock.UtcNow.DateTime);
10+
return new ScheduledTimer(action, () => expression.GetNextOccurrence(systemClock.UtcNow.DateTime)!.Value - systemClock.UtcNow.DateTime, logger);
1011
}
1112
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using Microsoft.Extensions.Logging;
2+
13
namespace Elsa.Common.RecurringTasks;
24

35
public interface ISchedule
46
{
5-
ScheduledTimer CreateTimer(Func<Task> action);
7+
ScheduledTimer CreateTimer(Func<Task> action, ILogger? logger = null);
68
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
using Microsoft.Extensions.Logging;
2+
13
namespace Elsa.Common.RecurringTasks;
24

35
public class IntervalSchedule(TimeSpan interval) : ISchedule
46
{
5-
public ScheduledTimer CreateTimer(Func<Task> action)
7+
public ScheduledTimer CreateTimer(Func<Task> action, ILogger? logger = null)
68
{
7-
return new ScheduledTimer(action, () => interval);
9+
return new ScheduledTimer(action, () => interval, logger);
810
}
911
}

src/modules/Elsa.Common/RecurringTasks/ScheduledTimer.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,45 @@
1+
using Microsoft.Extensions.Logging;
2+
13
namespace Elsa.Common.RecurringTasks;
24

35
public class ScheduledTimer : IDisposable, IAsyncDisposable
46
{
57
private readonly Func<Task> _action;
68
private readonly Func<TimeSpan> _interval;
79
private readonly Timer _timer;
10+
private readonly ILogger? _logger;
811

9-
public ScheduledTimer(Func<Task> action, Func<TimeSpan> interval)
12+
public ScheduledTimer(Func<Task> action, Func<TimeSpan> interval, ILogger? logger = null)
1013
{
1114
_action = action;
1215
_interval = interval;
16+
_logger = logger;
1317
_timer = new Timer(Callback, null, interval(), Timeout.InfiniteTimeSpan);
1418
}
1519

1620
private async void Callback(object? state)
1721
{
18-
await _action();
19-
_timer.Change(_interval(), Timeout.InfiniteTimeSpan);
22+
try
23+
{
24+
await _action();
25+
}
26+
catch (Exception e)
27+
{
28+
// Swallow exception to prevent async void from crashing the process.
29+
// Log unhandled exceptions here as a safeguard; calling code may have its own exception handling.
30+
_logger?.LogError(e, "Unhandled exception in scheduled timer action");
31+
}
32+
finally
33+
{
34+
try
35+
{
36+
_timer.Change(_interval(), Timeout.InfiniteTimeSpan);
37+
}
38+
catch (ObjectDisposedException)
39+
{
40+
// Timer was disposed, ignore.
41+
}
42+
}
2043
}
2144

2245
public void Dispose()

0 commit comments

Comments
 (0)