Skip to content

Commit 1da31ae

Browse files
HandyS11claude
andcommitted
feat(connections): failover on ServerCredentialsChangedEvent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3973353 commit 1da31ae

2 files changed

Lines changed: 50 additions & 8 deletions

File tree

src/RustPlusBot.Features.Connections/Hosting/ConnectionHostedService.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,17 @@ public async Task StopAsync(CancellationToken cancellationToken)
5050

5151
private async Task RunAsync(CancellationToken cancellationToken)
5252
{
53-
// Subscription is registered when this loop first awaits the bus, after StartAllAsync completes.
53+
// Subscriptions register when each loop first awaits the bus, after StartAllAsync completes.
5454
// The in-process bus does not replay, so events published before this point are not delivered. Safe:
55-
// the server-registered event is only raised by the FCM pairing flow at runtime, long after startup.
55+
// both events are only raised at runtime, long after startup.
5656
try
5757
{
5858
await supervisor.StartAllAsync(cancellationToken).ConfigureAwait(false);
5959

60-
await foreach (var registered in eventBus.SubscribeAsync<ServerRegisteredEvent>(cancellationToken)
61-
.ConfigureAwait(false))
62-
{
63-
await supervisor.EnsureConnectionAsync(registered.GuildId, registered.ServerId, cancellationToken)
64-
.ConfigureAwait(false);
65-
}
60+
await Task.WhenAll(
61+
ConsumeRegisteredAsync(cancellationToken),
62+
ConsumeCredentialsChangedAsync(cancellationToken))
63+
.ConfigureAwait(false);
6664
}
6765
catch (OperationCanceledException)
6866
{
@@ -76,6 +74,26 @@ await supervisor.EnsureConnectionAsync(registered.GuildId, registered.ServerId,
7674
#pragma warning restore CA1031
7775
}
7876

77+
private async Task ConsumeRegisteredAsync(CancellationToken cancellationToken)
78+
{
79+
await foreach (var registered in eventBus.SubscribeAsync<ServerRegisteredEvent>(cancellationToken)
80+
.ConfigureAwait(false))
81+
{
82+
await supervisor.EnsureConnectionAsync(registered.GuildId, registered.ServerId, cancellationToken)
83+
.ConfigureAwait(false);
84+
}
85+
}
86+
87+
private async Task ConsumeCredentialsChangedAsync(CancellationToken cancellationToken)
88+
{
89+
await foreach (var changed in eventBus.SubscribeAsync<ServerCredentialsChangedEvent>(cancellationToken)
90+
.ConfigureAwait(false))
91+
{
92+
await supervisor.EnsureConnectionAsync(changed.GuildId, changed.ServerId, cancellationToken)
93+
.ConfigureAwait(false);
94+
}
95+
}
96+
7997
[LoggerMessage(Level = LogLevel.Error, Message = "Connection hosted-service loop faulted.")]
8098
private static partial void LogLoopFaulted(ILogger logger, Exception exception);
8199
}

tests/RustPlusBot.Features.Connections.Tests/ConnectionHostedServiceTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,28 @@ public async Task Startup_CallsStartAll_And_ServerRegistered_EnsuresAConnection(
3535
await service.StopAsync(default);
3636
await supervisor.Received(1).StopAllAsync();
3737
}
38+
39+
[Fact]
40+
public async Task ServerCredentialsChanged_EnsuresAConnection()
41+
{
42+
var supervisor = Substitute.For<IConnectionSupervisor>();
43+
var bus = new InMemoryEventBus();
44+
var service = new ConnectionHostedService(supervisor, bus, NullLogger<ConnectionHostedService>.Instance);
45+
46+
await service.StartAsync(default);
47+
48+
var serverId = Guid.NewGuid();
49+
var deadline = DateTimeOffset.UtcNow.AddSeconds(20);
50+
while (DateTimeOffset.UtcNow < deadline
51+
&& !supervisor.ReceivedCalls().Any(c =>
52+
c.GetMethodInfo().Name == nameof(IConnectionSupervisor.EnsureConnectionAsync)))
53+
{
54+
await bus.PublishAsync(new ServerCredentialsChangedEvent(10UL, serverId));
55+
await Task.Delay(20);
56+
}
57+
58+
await supervisor.Received().EnsureConnectionAsync(10UL, serverId, Arg.Any<CancellationToken>());
59+
60+
await service.StopAsync(default);
61+
}
3862
}

0 commit comments

Comments
 (0)