Skip to content

Commit 5d3e5b6

Browse files
committed
Prevent in-process background services from crashing at setup #381
1 parent f981fae commit 5d3e5b6

File tree

7 files changed

+167
-34
lines changed

7 files changed

+167
-34
lines changed

src/PopForums.Mvc/Areas/Forums/BackgroundJobs/AwardCalculatorJob.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,43 @@
33

44
namespace PopForums.Mvc.Areas.Forums.BackgroundJobs;
55

6-
public class AwardCalculatorJob(ISettingsManager settingsManager, IServiceHeartbeatService serviceHeartbeatService, IAwardCalculatorWorker awardCalculatorWorker) : BackgroundService
6+
public class AwardCalculatorJob(ISettingsManager settingsManager, IServiceHeartbeatService serviceHeartbeatService, IAwardCalculatorWorker awardCalculatorWorker, IServiceProvider serviceProvider) : BackgroundService
77
{
8-
private readonly PeriodicTimer _timer = new(TimeSpan.FromMilliseconds(settingsManager.Current.ScoringGameCalculatorInterval));
9-
108
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
119
{
10+
PeriodicTimer timer;
11+
try
12+
{
13+
timer = new(TimeSpan.FromMilliseconds(settingsManager.Current.ScoringGameCalculatorInterval));
14+
}
15+
catch(Exception ex)
16+
{
17+
var logger = await GetLogger();
18+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job. This job will not restart without restarting the app.");
19+
return;
20+
}
21+
1222
while (!stoppingToken.IsCancellationRequested)
1323
{
14-
awardCalculatorWorker.Execute();
15-
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
16-
_timer.Period = TimeSpan.FromMilliseconds(settingsManager.Current.ScoringGameCalculatorInterval);
17-
await _timer.WaitForNextTickAsync(stoppingToken);
24+
try
25+
{
26+
awardCalculatorWorker.Execute();
27+
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
28+
timer.Period = TimeSpan.FromMilliseconds(settingsManager.Current.ScoringGameCalculatorInterval);
29+
}
30+
catch (Exception ex)
31+
{
32+
var logger = await GetLogger();
33+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job.");
34+
}
35+
await timer.WaitForNextTickAsync(stoppingToken);
1836
}
1937
}
38+
39+
private async Task<ILogger<AwardCalculatorJob>> GetLogger()
40+
{
41+
await using var scope = serviceProvider.CreateAsyncScope();
42+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<AwardCalculatorJob>>();
43+
return logger;
44+
}
2045
}

src/PopForums.Mvc/Areas/Forums/BackgroundJobs/CloseAgedTopicsJob.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace PopForums.Mvc.Areas.Forums.BackgroundJobs;
55

6-
public class CloseAgedTopicsJob(IServiceHeartbeatService serviceHeartbeatService, ICloseAgedTopicsWorker closeAgedTopicsWorker) : BackgroundService
6+
public class CloseAgedTopicsJob(IServiceHeartbeatService serviceHeartbeatService, ICloseAgedTopicsWorker closeAgedTopicsWorker, IServiceProvider serviceProvider) : BackgroundService
77
{
88
private const int IntervalValue = 12;
99
private readonly PeriodicTimer _timer = new(TimeSpan.FromHours(IntervalValue));
@@ -12,9 +12,24 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
1212
{
1313
while (!stoppingToken.IsCancellationRequested)
1414
{
15-
closeAgedTopicsWorker.Execute();
16-
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
15+
try
16+
{
17+
closeAgedTopicsWorker.Execute();
18+
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
19+
}
20+
catch (Exception ex)
21+
{
22+
var logger = await GetLogger();
23+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job.");
24+
}
1725
await _timer.WaitForNextTickAsync(stoppingToken);
1826
}
1927
}
28+
29+
private async Task<ILogger<CloseAgedTopicsJob>> GetLogger()
30+
{
31+
await using var scope = serviceProvider.CreateAsyncScope();
32+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<CloseAgedTopicsJob>>();
33+
return logger;
34+
}
2035
}

src/PopForums.Mvc/Areas/Forums/BackgroundJobs/EmailJob.cs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,45 @@
33

44
namespace PopForums.Mvc.Areas.Forums.BackgroundJobs;
55

6-
public class EmailJob(ISettingsManager settingsManager, IServiceHeartbeatService serviceHeartbeatService, IEmailWorker emailWorker) : BackgroundService
6+
public class EmailJob(ISettingsManager settingsManager, IServiceHeartbeatService serviceHeartbeatService, IEmailWorker emailWorker, IServiceProvider serviceProvider) : BackgroundService
77
{
8-
private readonly PeriodicTimer _timer = new(TimeSpan.FromMilliseconds(settingsManager.Current.MailSendingInverval));
9-
108
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
119
{
10+
PeriodicTimer timer;
11+
try
12+
{
13+
timer = new(TimeSpan.FromMilliseconds(settingsManager.Current.MailSendingInverval));
14+
}
15+
catch(Exception ex)
16+
{
17+
var logger = await GetLogger();
18+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job. This job will not restart without restarting the app.");
19+
return;
20+
}
1221
while (!stoppingToken.IsCancellationRequested)
1322
{
23+
try
24+
{
1425
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
15-
emailWorker.Execute();
26+
emailWorker.Execute();
1627
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
17-
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
18-
var newTimeSpan = TimeSpan.FromMilliseconds(settingsManager.Current.MailSendingInverval);
19-
_timer.Period = newTimeSpan;
20-
await _timer.WaitForNextTickAsync(stoppingToken);
28+
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
29+
var newTimeSpan = TimeSpan.FromMilliseconds(settingsManager.Current.MailSendingInverval);
30+
timer.Period = newTimeSpan;
31+
}
32+
catch (Exception ex)
33+
{
34+
var logger = await GetLogger();
35+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job.");
36+
}
37+
await timer.WaitForNextTickAsync(stoppingToken);
2138
}
2239
}
40+
41+
private async Task<ILogger<EmailJob>> GetLogger()
42+
{
43+
await using var scope = serviceProvider.CreateAsyncScope();
44+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<EmailJob>>();
45+
return logger;
46+
}
2347
}

src/PopForums.Mvc/Areas/Forums/BackgroundJobs/PostImageCleanupJob.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace PopForums.Mvc.Areas.Forums.BackgroundJobs;
55

6-
public class PostImageCleanupJob(IServiceHeartbeatService serviceHeartbeatService, IPostImageCleanupWorker postImageCleanupWorker) : BackgroundService
6+
public class PostImageCleanupJob(IServiceHeartbeatService serviceHeartbeatService, IPostImageCleanupWorker postImageCleanupWorker, IServiceProvider serviceProvider) : BackgroundService
77
{
88
private const double IntervalValue = 12;
99
private readonly PeriodicTimer _timer = new(TimeSpan.FromHours(IntervalValue));
@@ -12,9 +12,24 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
1212
{
1313
while (!stoppingToken.IsCancellationRequested)
1414
{
15-
postImageCleanupWorker.Execute();
16-
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
15+
try
16+
{
17+
postImageCleanupWorker.Execute();
18+
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
19+
}
20+
catch (Exception ex)
21+
{
22+
var logger = await GetLogger();
23+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job.");
24+
}
1725
await _timer.WaitForNextTickAsync(stoppingToken);
1826
}
1927
}
28+
29+
private async Task<ILogger<PostImageCleanupJob>> GetLogger()
30+
{
31+
await using var scope = serviceProvider.CreateAsyncScope();
32+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<PostImageCleanupJob>>();
33+
return logger;
34+
}
2035
}

src/PopForums.Mvc/Areas/Forums/BackgroundJobs/SearchIndexJob.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,42 @@
33

44
namespace PopForums.Mvc.Areas.Forums.BackgroundJobs;
55

6-
public class SearchIndexJob(ISettingsManager settingsManager, IServiceHeartbeatService serviceHeartbeatService, ISearchIndexWorker searchIndexWorker) : BackgroundService
6+
public class SearchIndexJob(ISettingsManager settingsManager, IServiceHeartbeatService serviceHeartbeatService, ISearchIndexWorker searchIndexWorker, IServiceProvider serviceProvider) : BackgroundService
77
{
8-
private readonly PeriodicTimer _timer = new(TimeSpan.FromMilliseconds(settingsManager.Current.SearchIndexingInterval));
9-
108
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
119
{
10+
PeriodicTimer timer;
11+
try
12+
{
13+
timer = new(TimeSpan.FromMilliseconds(settingsManager.Current.SearchIndexingInterval));
14+
}
15+
catch(Exception ex)
16+
{
17+
var logger = await GetLogger();
18+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job. This job will not restart without restarting the app.");
19+
return;
20+
}
1221
while (!stoppingToken.IsCancellationRequested)
1322
{
14-
searchIndexWorker.Execute();
15-
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
16-
_timer.Period = TimeSpan.FromMilliseconds(settingsManager.Current.SearchIndexingInterval);
17-
await _timer.WaitForNextTickAsync(stoppingToken);
23+
try
24+
{
25+
searchIndexWorker.Execute();
26+
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
27+
timer.Period = TimeSpan.FromMilliseconds(settingsManager.Current.SearchIndexingInterval);
28+
}
29+
catch (Exception ex)
30+
{
31+
var logger = await GetLogger();
32+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job.");
33+
}
34+
await timer.WaitForNextTickAsync(stoppingToken);
1835
}
1936
}
37+
38+
private async Task<ILogger<SearchIndexJob>> GetLogger()
39+
{
40+
await using var scope = serviceProvider.CreateAsyncScope();
41+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<SearchIndexJob>>();
42+
return logger;
43+
}
2044
}

src/PopForums.Mvc/Areas/Forums/BackgroundJobs/SubscribeNotificationJob.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace PopForums.Mvc.Areas.Forums.BackgroundJobs;
55

6-
public class SubscribeNotificationJob(IServiceHeartbeatService serviceHeartbeatService, ISubscribeNotificationWorker subscribeNotificationWorker) : BackgroundService
6+
public class SubscribeNotificationJob(IServiceHeartbeatService serviceHeartbeatService, ISubscribeNotificationWorker subscribeNotificationWorker, IServiceProvider serviceProvider) : BackgroundService
77
{
88
private const double IntervalValue = 15;
99
private readonly PeriodicTimer _timer = new(TimeSpan.FromSeconds(IntervalValue));
@@ -12,9 +12,24 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
1212
{
1313
while (!stoppingToken.IsCancellationRequested)
1414
{
15-
subscribeNotificationWorker.Execute();
16-
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
15+
try
16+
{
17+
subscribeNotificationWorker.Execute();
18+
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
19+
}
20+
catch (Exception ex)
21+
{
22+
var logger = await GetLogger();
23+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job.");
24+
}
1725
await _timer.WaitForNextTickAsync(stoppingToken);
1826
}
1927
}
28+
29+
private async Task<ILogger<PostImageCleanupJob>> GetLogger()
30+
{
31+
await using var scope = serviceProvider.CreateAsyncScope();
32+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<PostImageCleanupJob>>();
33+
return logger;
34+
}
2035
}

src/PopForums.Mvc/Areas/Forums/BackgroundJobs/UserSessionJob.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace PopForums.Mvc.Areas.Forums.BackgroundJobs;
55

6-
public class UserSessionJob(IServiceHeartbeatService serviceHeartbeatService, IUserSessionWorker userSessionWorker) : BackgroundService
6+
public class UserSessionJob(IServiceHeartbeatService serviceHeartbeatService, IUserSessionWorker userSessionWorker, IServiceProvider serviceProvider) : BackgroundService
77
{
88
private const double IntervalValue = 1;
99
private readonly PeriodicTimer _timer = new(TimeSpan.FromMinutes(IntervalValue));
@@ -12,9 +12,24 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
1212
{
1313
while (!stoppingToken.IsCancellationRequested)
1414
{
15-
userSessionWorker.Execute();
16-
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
15+
try
16+
{
17+
userSessionWorker.Execute();
18+
await serviceHeartbeatService.RecordHeartbeat(GetType().FullName, Environment.MachineName);
19+
}
20+
catch (Exception ex)
21+
{
22+
var logger = await GetLogger();
23+
logger.LogError(ex, $"Error while executing {GetType().FullName} background job.");
24+
}
1725
await _timer.WaitForNextTickAsync(stoppingToken);
1826
}
1927
}
28+
29+
private async Task<ILogger<UserSessionJob>> GetLogger()
30+
{
31+
await using var scope = serviceProvider.CreateAsyncScope();
32+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<UserSessionJob>>();
33+
return logger;
34+
}
2035
}

0 commit comments

Comments
 (0)