Skip to content

Commit 0e2cd54

Browse files
committed
Propagate cancellation tokens through failed message editing and notification persistence
This change continues the effort to consistently apply optional `CancellationToken` parameters to asynchronous methods within the `IEditFailedMessagesDataStore`, `INotificationsDataStore`, and related manager interfaces, along with their EFCore and RavenDB implementations. Cancellation tokens are now propagated to underlying asynchronous database calls, ensuring proper cancellation of long-running operations.
1 parent 3caeeeb commit 0e2cd54

20 files changed

Lines changed: 78 additions & 68 deletions

File tree

‎src/ServiceControl.AcceptanceTests/Monitoring/CustomChecks/When_email_notifications_are_enabled.cs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,18 @@ await Define<MyContext>(c =>
6262

6363
class SetupNotificationSettings(INotificationsDataStore notificationsDataStore) : IHostedService
6464
{
65-
public async Task StartAsync(CancellationToken cancellationToken)
65+
public async Task StartAsync(CancellationToken cancellationToken = default)
6666
{
67-
await using var notificationsManager = await notificationsDataStore.CreateNotificationsManager();
67+
await using var notificationsManager = await notificationsDataStore.CreateNotificationsManager(cancellationToken);
6868

69-
var settings = await notificationsManager.LoadSettings();
69+
var settings = await notificationsManager.LoadSettings(cancellationToken);
7070
settings.Email.Enabled = true;
7171
settings.Email.From = "YouServiceControl@particular.net";
7272
settings.Email.To = "WhoeverMightBeConcerned@particular.net";
73-
await notificationsManager.SaveChanges();
73+
await notificationsManager.SaveChanges(cancellationToken);
7474
}
7575

76-
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
76+
public Task StopAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
7777
}
7878

7979
public class MyContext : ScenarioContext

‎src/ServiceControl.Persistence.EFCore/Implementation/EditFailedMessagesDataStore.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace ServiceControl.Persistence.EFCore.Implementation;
55

66
public class EditFailedMessagesDataStore(IServiceScopeFactory scopeFactory, TimeProvider timeProvider) : IEditFailedMessagesDataStore
77
{
8-
public Task<IEditFailedMessagesManager> CreateEditFailedMessageManager()
8+
public Task<IEditFailedMessagesManager> CreateEditFailedMessageManager(CancellationToken cancellationToken = default)
99
{
1010
var scope = scopeFactory.CreateAsyncScope();
1111
return Task.FromResult<IEditFailedMessagesManager>(

‎src/ServiceControl.Persistence.EFCore/Implementation/EditFailedMessagesManager.cs‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class EditFailedMessagesManager(IAsyncDisposable scope, ServiceControlDbC
1010
FailedMessage? failedMessage; // cached after GetFailedMessage
1111
FailedMessageEditEntity? editEntity; // tracked after GetCurrentEditingRequestId / SetCurrentEditingRequestId
1212

13-
public async Task<FailedMessage?> GetFailedMessage(string failedMessageId)
13+
public async Task<FailedMessage?> GetFailedMessage(string failedMessageId, CancellationToken cancellationToken = default)
1414
{
1515
if (!Guid.TryParse(failedMessageId, out var uniqueMessageId))
1616
{
@@ -19,7 +19,7 @@ public class EditFailedMessagesManager(IAsyncDisposable scope, ServiceControlDbC
1919

2020
var entity = await dbContext.FailedMessages
2121
.AsNoTracking()
22-
.SingleOrDefaultAsync(message => message.UniqueMessageId == uniqueMessageId);
22+
.SingleOrDefaultAsync(message => message.UniqueMessageId == uniqueMessageId, cancellationToken);
2323

2424
if (entity == null)
2525
{
@@ -34,7 +34,7 @@ public class EditFailedMessagesManager(IAsyncDisposable scope, ServiceControlDbC
3434
return result;
3535
}
3636

37-
public async Task<string?> GetCurrentEditingRequestId(string failedMessageId)
37+
public async Task<string?> GetCurrentEditingRequestId(string failedMessageId, CancellationToken cancellationToken = default)
3838
{
3939
if (!Guid.TryParse(failedMessageId, out var uniqueMessageId))
4040
{
@@ -43,12 +43,12 @@ public class EditFailedMessagesManager(IAsyncDisposable scope, ServiceControlDbC
4343

4444
editEntity = await dbContext.FailedMessageEdits
4545
.AsNoTracking()
46-
.SingleOrDefaultAsync(e => e.UniqueMessageId == uniqueMessageId);
46+
.SingleOrDefaultAsync(e => e.UniqueMessageId == uniqueMessageId, cancellationToken);
4747

4848
return editEntity?.EditId;
4949
}
5050

51-
public Task SetCurrentEditingRequestId(string editingMessageId)
51+
public Task SetCurrentEditingRequestId(string editingMessageId, CancellationToken cancellationToken = default)
5252
{
5353
if (failedMessage == null)
5454
{
@@ -65,7 +65,7 @@ public Task SetCurrentEditingRequestId(string editingMessageId)
6565
return Task.CompletedTask;
6666
}
6767

68-
public async Task SetFailedMessageAsResolved()
68+
public async Task SetFailedMessageAsResolved(CancellationToken cancellationToken = default)
6969
{
7070
if (failedMessage == null)
7171
{
@@ -81,7 +81,7 @@ public async Task SetFailedMessageAsResolved()
8181
// swept immediately.
8282
var now = timeProvider.GetUtcNow().UtcDateTime;
8383
var entity = await dbContext.FailedMessages
84-
.SingleOrDefaultAsync(m => m.UniqueMessageId == Guid.Parse(message.UniqueMessageId))
84+
.SingleOrDefaultAsync(m => m.UniqueMessageId == Guid.Parse(message.UniqueMessageId), cancellationToken)
8585
?? throw new InvalidOperationException("Failed message entity not found");
8686

8787
entity.Status = FailedMessageStatus.Resolved;
@@ -90,7 +90,7 @@ public async Task SetFailedMessageAsResolved()
9090
message.Status = FailedMessageStatus.Resolved;
9191
}
9292

93-
public Task SaveChanges() => dbContext.SaveChangesAsync();
93+
public Task SaveChanges(CancellationToken cancellationToken = default) => dbContext.SaveChangesAsync(cancellationToken);
9494

9595
public ValueTask DisposeAsync() => scope.DisposeAsync();
9696
}

‎src/ServiceControl.Persistence.EFCore/Implementation/NotificationsDataStore.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace ServiceControl.Persistence.EFCore.Implementation;
55

66
public class NotificationsDataStore(IServiceProvider serviceProvider) : INotificationsDataStore
77
{
8-
public Task<INotificationsManager> CreateNotificationsManager()
8+
public Task<INotificationsManager> CreateNotificationsManager(CancellationToken cancellationToken = default)
99
{
1010
var scope = serviceProvider.CreateAsyncScope();
1111
ServiceControlDbContext serviceControlDbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContext>();

‎src/ServiceControl.Persistence.EFCore/Implementation/NotificationsManager.cs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@ public class NotificationsManager(IAsyncDisposable scope, ServiceControlDbContex
88
{
99
NotificationsSettings? _settings;
1010

11-
public Task SaveChanges()
11+
public Task SaveChanges(CancellationToken cancellationToken = default)
1212
{
1313
if (_settings == null)
1414
{
1515
return Task.CompletedTask;
1616
}
1717

18-
return dbContext.StoreSetting(SettingKeys.NotificationEmails, _settings.Email, CancellationToken.None);
18+
return dbContext.StoreSetting(SettingKeys.NotificationEmails, _settings.Email, cancellationToken);
1919
}
2020

21-
public async Task<NotificationsSettings> LoadSettings()
21+
public async Task<NotificationsSettings> LoadSettings(CancellationToken cancellationToken = default)
2222
{
23-
var settings = await dbContext.GetSetting<EmailNotifications>(SettingKeys.NotificationEmails, CancellationToken.None);
23+
var settings = await dbContext.GetSetting<EmailNotifications>(SettingKeys.NotificationEmails, cancellationToken);
2424
_settings = new NotificationsSettings() { Email = settings ?? new EmailNotifications() };
2525
return _settings;
2626
}

‎src/ServiceControl.Persistence.RavenDB/Editing/EditFailedMessageManager.cs‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace ServiceControl.Persistence.RavenDB
22
{
33
using System;
4+
using System.Threading;
45
using System.Threading.Tasks;
56
using Raven.Client.Documents.Session;
67
using ServiceControl.MessageFailures;
@@ -19,19 +20,19 @@ public EditFailedMessageManager(IAsyncDocumentSession session, ExpirationManager
1920
this.expirationManager = expirationManager;
2021
}
2122

22-
public async Task<FailedMessage> GetFailedMessage(string failedMessageId)
23+
public async Task<FailedMessage> GetFailedMessage(string failedMessageId, CancellationToken cancellationToken = default)
2324
{
24-
failedMessage = await session.LoadAsync<FailedMessage>(FailedMessageIdGenerator.MakeDocumentId(failedMessageId));
25+
failedMessage = await session.LoadAsync<FailedMessage>(FailedMessageIdGenerator.MakeDocumentId(failedMessageId), cancellationToken);
2526
return failedMessage;
2627
}
2728

28-
public async Task<string> GetCurrentEditingRequestId(string failedMessageId)
29+
public async Task<string> GetCurrentEditingRequestId(string failedMessageId, CancellationToken cancellationToken = default)
2930
{
30-
var edit = await session.LoadAsync<FailedMessageEdit>(FailedMessageEdit.MakeDocumentId(failedMessageId));
31+
var edit = await session.LoadAsync<FailedMessageEdit>(FailedMessageEdit.MakeDocumentId(failedMessageId), cancellationToken);
3132
return edit?.EditId;
3233
}
3334

34-
public Task SetCurrentEditingRequestId(string editingMessageId)
35+
public Task SetCurrentEditingRequestId(string editingMessageId, CancellationToken cancellationToken = default)
3536
{
3637
if (failedMessage == null)
3738
{
@@ -42,10 +43,10 @@ public Task SetCurrentEditingRequestId(string editingMessageId)
4243
Id = FailedMessageEdit.MakeDocumentId(failedMessage.UniqueMessageId),
4344
FailedMessageId = failedMessage.Id,
4445
EditId = editingMessageId
45-
});
46+
}, cancellationToken);
4647
}
4748

48-
public Task SetFailedMessageAsResolved()
49+
public Task SetFailedMessageAsResolved(CancellationToken cancellationToken = default)
4950
{
5051
// Instance is tracked by the document session
5152
failedMessage.Status = FailedMessageStatus.Resolved;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
namespace ServiceControl.Persistence.RavenDB.Editing
22
{
3+
using System.Threading;
34
using System.Threading.Tasks;
45

56
class EditFailedMessagesDataStore(IRavenSessionProvider sessionProvider, ExpirationManager expirationManager) : IEditFailedMessagesDataStore
67
{
7-
public async Task<IEditFailedMessagesManager> CreateEditFailedMessageManager() =>
8+
public async Task<IEditFailedMessagesManager> CreateEditFailedMessageManager(CancellationToken cancellationToken = default) =>
89
// the edit failed message manager manages the lifetime of the session
9-
new EditFailedMessageManager(await sessionProvider.OpenSession(), expirationManager);
10+
new EditFailedMessageManager(await sessionProvider.OpenSession(cancellationToken: cancellationToken), expirationManager);
1011
}
1112
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
namespace ServiceControl.Persistence.RavenDB.Editing
22
{
3+
using System.Threading;
34
using System.Threading.Tasks;
45

56
class NotificationsDataStore(IRavenSessionProvider sessionProvider) : INotificationsDataStore
67
{
7-
public async Task<INotificationsManager> CreateNotificationsManager() =>
8+
public async Task<INotificationsManager> CreateNotificationsManager(CancellationToken cancellationToken = default) =>
89
// the notifications manager manages the lifetime of the session
9-
new NotificationsManager(await sessionProvider.OpenSession());
10+
new NotificationsManager(await sessionProvider.OpenSession(cancellationToken: cancellationToken));
1011
}
1112
}

‎src/ServiceControl.Persistence.RavenDB/Editing/NotificationsManager.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace ServiceControl.Persistence.RavenDB.Editing
22
{
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using Notifications;
56
using Raven.Client.Documents.Session;
@@ -8,18 +9,18 @@ class NotificationsManager(IAsyncDocumentSession session) : AbstractSessionManag
89
{
910
const string SingleDocumentId = "NotificationsSettings/All";
1011

11-
public async Task<NotificationsSettings> LoadSettings()
12+
public async Task<NotificationsSettings> LoadSettings(CancellationToken cancellationToken = default)
1213
{
1314
// Deliberately not aggressively cached. These settings are read rarely and edited by hand,
1415
// and aggressive caching invalidates asynchronously via the Changes API, so a read straight
1516
// after a save can return the pre-save document.
1617
var settings = await Session
17-
.LoadAsync<NotificationsSettingsDocument>(SingleDocumentId);
18+
.LoadAsync<NotificationsSettingsDocument>(SingleDocumentId, cancellationToken);
1819

1920
if (settings == null)
2021
{
2122
settings = new NotificationsSettingsDocument { Id = SingleDocumentId };
22-
await Session.StoreAsync(settings);
23+
await Session.StoreAsync(settings, cancellationToken);
2324
}
2425

2526
return new NotificationsSettings()

‎src/ServiceControl.Persistence.RavenDB/Transactions/RavenTransactionalDataStore.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
namespace ServiceControl.Persistence.RavenDB
22
{
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using Raven.Client.Documents.Session;
56

67
abstract class AbstractSessionManager(IAsyncDocumentSession session) : IDataSessionManager
78
{
89
protected IAsyncDocumentSession Session { get; } = session;
910

10-
public Task SaveChanges() => Session.SaveChangesAsync();
11+
public Task SaveChanges(CancellationToken cancellationToken = default) => Session.SaveChangesAsync(cancellationToken);
1112
public ValueTask DisposeAsync()
1213
{
1314
Session.Dispose();

0 commit comments

Comments
 (0)