Skip to content

Commit 021dbf5

Browse files
HandyS11claude
andcommitted
fix: satisfy new SonarAnalyzer 10.30 rules S8969 and S8949
SonarAnalyzer 10.30 adds S8969 (redundant null-forgiving operator) and S8949 (missing cancellation token). With TreatWarningsAsErrors both break the build. - Drop null-forgiving operators the compiler already knows are redundant, mostly after Assert.NotNull. - Swap `.Where(t => t is not null)` for `.OfType<Task>()` in the hosted services' StopAsync so the loop task is non-null by type instead of by assertion. - Thread the service token through the workspace heal paths and the pairing expiry write, and swallow the resulting cancellation on shutdown. - Pass CancellationToken.None explicitly to ConnectionSupervisor's gate waits: those run during teardown, after _shutdown is already cancelled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 94043a9 commit 021dbf5

28 files changed

Lines changed: 91 additions & 74 deletions

File tree

src/RustPlusBot.Features.Alarms/Hosting/AlarmsHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
4949
foreach (var loop in new[]
5050
{
5151
_pairedLoop, _triggeredLoop, _statusLoop, _reachabilityLoop, _observedLoop, _wipedLoop
52-
}.Where(t => t is not null))
52+
}.OfType<Task>())
5353
{
5454
try
5555
{
5656
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
57-
await loop!.ConfigureAwait(false);
57+
await loop.ConfigureAwait(false);
5858
#pragma warning restore VSTHRD003
5959
}
6060
catch (OperationCanceledException)

src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ public async Task EnsureConnectionAsync(ulong guildId, Guid serverId, Cancellati
173173
/// <inheritdoc />
174174
public async Task StopAsync(ulong guildId, Guid serverId)
175175
{
176-
await _gate.WaitAsync().ConfigureAwait(false);
176+
// CancellationToken.None, not _shutdown.Token: teardown must still acquire the gate after
177+
// StopAllAsync has already cancelled _shutdown, otherwise the connection is never stopped.
178+
await _gate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
177179
try
178180
{
179181
await StopConnectionAsync((guildId, serverId)).ConfigureAwait(false);
@@ -188,7 +190,8 @@ public async Task StopAsync(ulong guildId, Guid serverId)
188190
public async Task StopAllAsync()
189191
{
190192
await _shutdown.CancelAsync().ConfigureAwait(false);
191-
await _gate.WaitAsync().ConfigureAwait(false);
193+
// CancellationToken.None: _shutdown was just cancelled, so waiting on it would abandon shutdown.
194+
await _gate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
192195
try
193196
{
194197
foreach (var key in _connections.Keys.ToList())

src/RustPlusBot.Features.Events/Hosting/EventsHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
6060
foreach (var loop in new[]
6161
{
6262
_relayLoop, _rigLoop, _tickLoop, _disconnectLoop
63-
}.Where(t => t is not null))
63+
}.OfType<Task>())
6464
{
6565
try
6666
{
6767
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
68-
await loop!.ConfigureAwait(false);
68+
await loop.ConfigureAwait(false);
6969
#pragma warning restore VSTHRD003
7070
}
7171
catch (OperationCanceledException)

src/RustPlusBot.Features.Map/Hosting/InfoMapHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
5858
foreach (var loop in new[]
5959
{
6060
_statusLoop, _tickLoop
61-
}.Where(t => t is not null))
61+
}.OfType<Task>())
6262
{
6363
try
6464
{
6565
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
66-
await loop!.ConfigureAwait(false);
66+
await loop.ConfigureAwait(false);
6767
#pragma warning restore VSTHRD003
6868
}
6969
catch (OperationCanceledException)

src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
7979
foreach (var loop in new[]
8080
{
8181
_markerLoop, _settingsLoop, _statusLoop, _tickLoop
82-
}.Where(t => t is not null))
82+
}.OfType<Task>())
8383
{
8484
try
8585
{
8686
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
87-
await loop!.ConfigureAwait(false);
87+
await loop.ConfigureAwait(false);
8888
#pragma warning restore VSTHRD003
8989
}
9090
catch (OperationCanceledException)

src/RustPlusBot.Features.Pairing/Supervisor/PairingSupervisor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,11 @@ private async Task MarkExpiredAsync((ulong Guild, ulong Owner) key, Guid registr
249249
await using (expireScope.ConfigureAwait(false))
250250
{
251251
var store = expireScope.ServiceProvider.GetRequiredService<IFcmRegistrationStore>();
252-
await store.SetStatusAsync(registrationId, FcmRegistrationStatus.Expired).ConfigureAwait(false);
252+
await store.SetStatusAsync(registrationId, FcmRegistrationStatus.Expired, _shutdown.Token)
253+
.ConfigureAwait(false);
253254
}
254255

255-
await notifier.NotifyCredentialsExpiredAsync(key.Guild, key.Owner).ConfigureAwait(false);
256+
await notifier.NotifyCredentialsExpiredAsync(key.Guild, key.Owner, _shutdown.Token).ConfigureAwait(false);
256257
}
257258

258259
private sealed class Handle : IAsyncDisposable

src/RustPlusBot.Features.StorageMonitors/Hosting/StorageMonitorsHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
4747
foreach (var loop in new[]
4848
{
4949
_pairedLoop, _triggeredLoop, _statusLoop, _reachabilityLoop, _wipedLoop
50-
}.Where(t => t is not null))
50+
}.OfType<Task>())
5151
{
5252
try
5353
{
5454
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
55-
await loop!.ConfigureAwait(false);
55+
await loop.ConfigureAwait(false);
5656
#pragma warning restore VSTHRD003
5757
}
5858
catch (OperationCanceledException)

src/RustPlusBot.Features.Switches/Hosting/SwitchesHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
4949
foreach (var loop in new[]
5050
{
5151
_pairedLoop, _stateLoop, _statusLoop, _deviceLoop, _reachabilityLoop, _wipedLoop
52-
}.Where(t => t is not null))
52+
}.OfType<Task>())
5353
{
5454
try
5555
{
5656
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
57-
await loop!.ConfigureAwait(false);
57+
await loop.ConfigureAwait(false);
5858
#pragma warning restore VSTHRD003
5959
}
6060
catch (OperationCanceledException)

src/RustPlusBot.Features.Wipes/Hosting/WipesHostedService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
3939
foreach (var loop in new[]
4040
{
4141
_statusLoop, _wipedLoop
42-
}.Where(t => t is not null))
42+
}.OfType<Task>())
4343
{
4444
try
4545
{
4646
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
47-
await loop!.ConfigureAwait(false);
47+
await loop.ConfigureAwait(false);
4848
#pragma warning restore VSTHRD003
4949
}
5050
catch (OperationCanceledException)

src/RustPlusBot.Features.Workspace/Hosting/WorkspaceHostedService.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private Task OnReadyAsync()
9797
// Healing sweeps every provisioned guild's channels over REST; doing it inline blocks the
9898
// gateway task and stalls event dispatch, so offload it. Failures must be caught here —
9999
// nothing awaits this.
100-
_ = Task.Run(HealProvisionedGuildsAsync);
100+
_ = Task.Run(HealProvisionedGuildsAsync, _cts.Token);
101101
return Task.CompletedTask;
102102
}
103103

@@ -110,12 +110,16 @@ private async Task HealProvisionedGuildsAsync()
110110
{
111111
var store = scope.ServiceProvider.GetRequiredService<IWorkspaceStore>();
112112
var reconciler = scope.ServiceProvider.GetRequiredService<IWorkspaceReconciler>();
113-
foreach (var guildId in await store.GetProvisionedGuildIdsAsync().ConfigureAwait(false))
113+
foreach (var guildId in await store.GetProvisionedGuildIdsAsync(_cts.Token).ConfigureAwait(false))
114114
{
115-
await reconciler.HealGuildAsync(guildId).ConfigureAwait(false);
115+
await reconciler.HealGuildAsync(guildId, _cts.Token).ConfigureAwait(false);
116116
}
117117
}
118118
}
119+
catch (OperationCanceledException)
120+
{
121+
// Shutting down.
122+
}
119123
catch (Exception ex) // Broad catch is intentional: a faulting startup heal must not crash the host.
120124
{
121125
logger.LogError(ex, "Startup self-heal failed.");
@@ -135,9 +139,13 @@ private async Task OnChannelDestroyedAsync(SocketChannel channel)
135139
await using (scope.ConfigureAwait(false))
136140
{
137141
var reconciler = scope.ServiceProvider.GetRequiredService<IWorkspaceReconciler>();
138-
await reconciler.HealGuildAsync(guildChannel.Guild.Id).ConfigureAwait(false);
142+
await reconciler.HealGuildAsync(guildChannel.Guild.Id, _cts.Token).ConfigureAwait(false);
139143
}
140144
}
145+
catch (OperationCanceledException)
146+
{
147+
// Shutting down.
148+
}
141149
catch (Exception ex) // Broad catch is intentional: a faulting self-heal must not crash the host.
142150
{
143151
logger.LogError(ex, "Self-heal failed for guild {GuildId}.", guildChannel.Guild.Id);

0 commit comments

Comments
 (0)