Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@
<PackageVersion Include="Microsoft.VisualStudio.Threading.Analyzers" Version="18.7.23" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0" />
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.15.0" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.29.0.143774" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.30.0.144632" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_pairedLoop, _triggeredLoop, _statusLoop, _reachabilityLoop, _observedLoop, _wipedLoop
}.Where(t => t is not null))
}.OfType<Task>())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
await loop!.ConfigureAwait(false);
await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ public async Task EnsureConnectionAsync(ulong guildId, Guid serverId, Cancellati
/// <inheritdoc />
public async Task StopAsync(ulong guildId, Guid serverId)
{
await _gate.WaitAsync().ConfigureAwait(false);
// CancellationToken.None, not _shutdown.Token: teardown must still acquire the gate after
// StopAllAsync has already cancelled _shutdown, otherwise the connection is never stopped.
await _gate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
try
{
await StopConnectionAsync((guildId, serverId)).ConfigureAwait(false);
Expand All @@ -188,7 +190,8 @@ public async Task StopAsync(ulong guildId, Guid serverId)
public async Task StopAllAsync()
{
await _shutdown.CancelAsync().ConfigureAwait(false);
await _gate.WaitAsync().ConfigureAwait(false);
// CancellationToken.None: _shutdown was just cancelled, so waiting on it would abandon shutdown.
await _gate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
try
{
foreach (var key in _connections.Keys.ToList())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_relayLoop, _rigLoop, _tickLoop, _disconnectLoop
}.Where(t => t is not null))
}.OfType<Task>())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
await loop!.ConfigureAwait(false);
await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
Expand Down
4 changes: 2 additions & 2 deletions src/RustPlusBot.Features.Map/Hosting/InfoMapHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_statusLoop, _tickLoop
}.Where(t => t is not null))
}.OfType<Task>())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
await loop!.ConfigureAwait(false);
await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
Expand Down
4 changes: 2 additions & 2 deletions src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_markerLoop, _settingsLoop, _statusLoop, _tickLoop
}.Where(t => t is not null))
}.OfType<Task>())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
await loop!.ConfigureAwait(false);
await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,11 @@ private async Task MarkExpiredAsync((ulong Guild, ulong Owner) key, Guid registr
await using (expireScope.ConfigureAwait(false))
{
var store = expireScope.ServiceProvider.GetRequiredService<IFcmRegistrationStore>();
await store.SetStatusAsync(registrationId, FcmRegistrationStatus.Expired).ConfigureAwait(false);
await store.SetStatusAsync(registrationId, FcmRegistrationStatus.Expired, _shutdown.Token)
.ConfigureAwait(false);
}

await notifier.NotifyCredentialsExpiredAsync(key.Guild, key.Owner).ConfigureAwait(false);
await notifier.NotifyCredentialsExpiredAsync(key.Guild, key.Owner, _shutdown.Token).ConfigureAwait(false);
}

private sealed class Handle : IAsyncDisposable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_pairedLoop, _triggeredLoop, _statusLoop, _reachabilityLoop, _wipedLoop
}.Where(t => t is not null))
}.OfType<Task>())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
await loop!.ConfigureAwait(false);
await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_pairedLoop, _stateLoop, _statusLoop, _deviceLoop, _reachabilityLoop, _wipedLoop
}.Where(t => t is not null))
}.OfType<Task>())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
await loop!.ConfigureAwait(false);
await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
Expand Down
4 changes: 2 additions & 2 deletions src/RustPlusBot.Features.Wipes/Hosting/WipesHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_statusLoop, _wipedLoop
}.Where(t => t is not null))
}.OfType<Task>())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
await loop!.ConfigureAwait(false);
await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private Task OnReadyAsync()
// Healing sweeps every provisioned guild's channels over REST; doing it inline blocks the
// gateway task and stalls event dispatch, so offload it. Failures must be caught here —
// nothing awaits this.
_ = Task.Run(HealProvisionedGuildsAsync);
_ = Task.Run(HealProvisionedGuildsAsync, _cts.Token);
return Task.CompletedTask;
}

Expand All @@ -110,12 +110,16 @@ private async Task HealProvisionedGuildsAsync()
{
var store = scope.ServiceProvider.GetRequiredService<IWorkspaceStore>();
var reconciler = scope.ServiceProvider.GetRequiredService<IWorkspaceReconciler>();
foreach (var guildId in await store.GetProvisionedGuildIdsAsync().ConfigureAwait(false))
foreach (var guildId in await store.GetProvisionedGuildIdsAsync(_cts.Token).ConfigureAwait(false))
{
await reconciler.HealGuildAsync(guildId).ConfigureAwait(false);
await reconciler.HealGuildAsync(guildId, _cts.Token).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
// Shutting down.
}
catch (Exception ex) // Broad catch is intentional: a faulting startup heal must not crash the host.
{
logger.LogError(ex, "Startup self-heal failed.");
Expand All @@ -135,9 +139,13 @@ private async Task OnChannelDestroyedAsync(SocketChannel channel)
await using (scope.ConfigureAwait(false))
{
var reconciler = scope.ServiceProvider.GetRequiredService<IWorkspaceReconciler>();
await reconciler.HealGuildAsync(guildChannel.Guild.Id).ConfigureAwait(false);
await reconciler.HealGuildAsync(guildChannel.Guild.Id, _cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// Shutting down.
}
catch (Exception ex) // Broad catch is intentional: a faulting self-heal must not crash the host.
{
logger.LogError(ex, "Self-heal failed for guild {GuildId}.", guildChannel.Guild.Id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ public async Task Publishes_a_clan_state_event_on_connect()

Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
Assert.Equal(ClanProbeStatus.HasClan, evt!.Status);
Assert.Equal(ClanProbeStatus.HasClan, evt.Status);
Assert.NotNull(evt.Snapshot);
Assert.Equal(1L, evt.Snapshot!.ClanId);
Assert.Equal(1L, evt.Snapshot.ClanId);

await h.Supervisor.StopAllAsync();
await cts.CancelAsync();
Expand Down Expand Up @@ -220,7 +220,7 @@ public async Task Publishes_NoClan_on_connect_when_the_player_has_no_clan()

Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
Assert.Equal(ClanProbeStatus.NoClan, evt!.Status);
Assert.Equal(ClanProbeStatus.NoClan, evt.Status);
Assert.Null(evt.Snapshot);

await h.Supervisor.StopAllAsync();
Expand Down Expand Up @@ -264,7 +264,7 @@ public async Task Publishes_a_clan_state_event_when_the_socket_reports_a_change(
// connect-time probe call), so RaiseClanChanged below is guaranteed to reach the handler.
await WaitUntilAsync(() => !captured.IsEmpty, cts.Token);
Assert.NotNull(source.LastConnection);
source.LastConnection!.RaiseClanChanged(ClanProbeResult.NoClan);
source.LastConnection.RaiseClanChanged(ClanProbeResult.NoClan);

await WaitUntilAsync(() => captured.Count >= 2, cts.Token);

Expand Down Expand Up @@ -321,7 +321,7 @@ public async Task Publishes_clan_messages_and_flags_the_active_player()
await WaitUntilAsync(() => !stateCaptured.IsEmpty, cts.Token);

Assert.NotNull(source.LastConnection);
source.LastConnection!.RaiseClanMessage(new ClanChatLine(100UL, "Active", "hi", DateTimeOffset.UnixEpoch));
source.LastConnection.RaiseClanMessage(new ClanChatLine(100UL, "Active", "hi", DateTimeOffset.UnixEpoch));
source.LastConnection.RaiseClanMessage(new ClanChatLine(999UL, "Other", "yo", DateTimeOffset.UnixEpoch));

await WaitUntilAsync(() => captured.Count >= 2, cts.Token);
Expand Down Expand Up @@ -394,7 +394,7 @@ public async Task Stops_publishing_clan_events_after_the_socket_closes()
await WaitUntilAsync(() => !stateCaptured.IsEmpty, cts.Token);

Assert.NotNull(source.LastConnection);
var closedConnection = source.LastConnection!;
var closedConnection = source.LastConnection;

// Stop the connection loop — this joins RunConnectedAsync's finally block, which unsubscribes
// the clan handlers, before StopAsync returns.
Expand Down Expand Up @@ -453,7 +453,7 @@ public async Task Routes_a_team_send_to_team_chat_and_a_clan_send_to_clan_chat()
Assert.Equal(ChatSendResult.Sent, clanResult);

Assert.NotNull(source.LastConnection);
var connection = source.LastConnection!;
var connection = source.LastConnection;

// Assert both destinations independently, so swapping the routing fails on both sides.
Assert.Equal("[Alice] team line", Assert.Single(connection.SentMessages));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public async Task Connect_Healthy_BecomesConnectedWithPlayerCount()

var state = await WaitForStateAsync(h.Provider, serverId, s => s.Status == ConnectionStatus.Connected);
Assert.NotNull(state);
Assert.Equal(7, state!.PlayerCount);
Assert.Equal(7, state.PlayerCount);
}

[Fact]
Expand Down Expand Up @@ -432,7 +432,7 @@ public async Task First_marker_poll_is_a_silent_baseline()

Assert.Single(captured);
Assert.True(captured.TryPeek(out var evt));
Assert.Single(evt!.Added);
Assert.Single(evt.Added);
Assert.Equal(MarkerKind.PatrolHelicopter, evt.Added[0].Kind);
Assert.Empty(evt.Removed);

Expand Down Expand Up @@ -489,7 +489,7 @@ public async Task Marker_added_on_a_later_poll_publishes_changed_event()
Assert.Single(captured);
Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
Assert.Single(evt!.Added);
Assert.Single(evt.Added);
Assert.Equal(MarkerKind.CargoShip, evt.Added[0].Kind);
Assert.Empty(evt.Removed);
Assert.Equal(expectedDims, evt.Dimensions);
Expand Down Expand Up @@ -540,7 +540,7 @@ public async Task Marker_position_change_publishes_moved_bucket()
Assert.Single(captured);
Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
Assert.Empty(evt!.Added);
Assert.Empty(evt.Added);
Assert.Empty(evt.Removed);
var moved = Assert.Single(evt.Moved);
Assert.Equal(7UL, moved.Id);
Expand Down Expand Up @@ -602,7 +602,7 @@ public async Task Failed_marker_poll_retains_previous_snapshot()
await Task.Delay(TimeSpan.FromMilliseconds(60), cts.Token);

// Poll 4: recover — hold-last still returns [cargo], so snapshot is unchanged, no new event.
source.LastConnection!.MarkersThrow = false;
source.LastConnection.MarkersThrow = false;
await Task.Delay(TimeSpan.FromMilliseconds(100), cts.Token);

// Still only one event total — the failed poll did not corrupt the snapshot.
Expand Down Expand Up @@ -659,7 +659,7 @@ public async Task Ch47_entering_rig_radius_publishes_activated_once_per_visit()
Assert.Single(rigEvents);
Assert.True(rigEvents.TryPeek(out var evt));
Assert.NotNull(evt);
Assert.Equal(RigKind.Small, evt!.Rig);
Assert.Equal(RigKind.Small, evt.Rig);
Assert.Equal(RigEventKind.Activated, evt.Kind);
Assert.Equal(1000f, evt.X);
Assert.Equal(1000f, evt.Y);
Expand Down Expand Up @@ -784,7 +784,7 @@ public async Task TeamChanged_push_publishes_player_state_transition()
await WaitUntilAsync(() => !captured.IsEmpty, cts.Token);

Assert.True(captured.TryDequeue(out var evt));
var transition = Assert.Single(evt!.Transitions);
var transition = Assert.Single(evt.Transitions);
Assert.Equal(PlayerTransitionKind.Disconnect, transition.Kind);
Assert.Equal(100UL, transition.SteamId);

Expand Down Expand Up @@ -838,7 +838,7 @@ online with

await WaitUntilAsync(() => !captured.IsEmpty, cts.Token);
Assert.True(captured.TryDequeue(out var evt));
Assert.Contains(evt!.Transitions, t => t.Kind == PlayerTransitionKind.Disconnect && t.SteamId == 100UL);
Assert.Contains(evt.Transitions, t => t.Kind == PlayerTransitionKind.Disconnect && t.SteamId == 100UL);

await h.Supervisor.StopAllAsync();
await cts.CancelAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task Renders_all_five_rows()
var payload = await Build(events, rigs).RenderAsync(new MessageRenderContext(1, ServerId, "en"), default);

Assert.NotNull(payload.Embed);
Assert.Equal(5, payload.Embed!.Fields.Length);
Assert.Equal(5, payload.Embed.Fields.Length);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public void GetById_ReturnsKnownItem()
{
var ak = _db.GetById(1545779598);
Assert.NotNull(ak);
Assert.Equal("Assault Rifle", ak!.Name);
Assert.Equal("Assault Rifle", ak.Name);
}

[Fact]
Expand Down Expand Up @@ -61,8 +61,8 @@ public void GetById_StoneBarricade_HasDecay()
{
var rec = _db.GetById(15388698);
Assert.NotNull(rec);
Assert.NotNull(rec!.Decay);
Assert.Equal(900, rec.Decay!.Seconds);
Assert.NotNull(rec.Decay);
Assert.Equal(900, rec.Decay.Seconds);
Assert.Equal(100, rec.Decay.Hp);
}

Expand All @@ -71,8 +71,8 @@ public void GetById_WoodenDoor_HasUpkeep()
{
var rec = _db.GetById(1729120840);
Assert.NotNull(rec);
Assert.NotNull(rec!.Upkeep);
var entry = Assert.Single(rec.Upkeep!.Entries);
Assert.NotNull(rec.Upkeep);
var entry = Assert.Single(rec.Upkeep.Entries);
Assert.Equal(-151838493, entry.ItemId);
Assert.Equal(30, entry.QuantityMin);
Assert.Equal(100, entry.QuantityMax);
Expand Down
Loading
Loading