Skip to content

Commit 3621799

Browse files
authored
Improve graceful shutdown (#481)
* Fire new `ServerShuttingDown` method * Fix graceful shutdowns blocking on `ConnectionState`s This would cause the server to never gracefully shutdown for one of two reasons: - On exception, connection states are not cleaned up. - Clients may choose to remain on an old server forever. * Update game packages
1 parent e60b93e commit 3621799

8 files changed

Lines changed: 47 additions & 13 deletions

File tree

SampleMultiplayerClient/MultiplayerClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,5 +410,10 @@ public async Task DisconnectRequested()
410410
Console.WriteLine("Disconnect requested");
411411
await LeaveRoom();
412412
}
413+
414+
public Task ServerShuttingDown()
415+
{
416+
return Task.CompletedTask;
417+
}
413418
}
414419
}

SampleMultiplayerClient/SampleMultiplayerClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="10.0.5" />
1212
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="10.0.5" />
1313
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
14-
<PackageReference Include="ppy.osu.Game" Version="2026.429.0" />
14+
<PackageReference Include="ppy.osu.Game" Version="2026.505.0" />
1515
</ItemGroup>
1616

1717
</Project>

SampleSpectatorClient/SampleSpectatorClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="10.0.5" />
1212
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="10.0.5" />
1313
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
14-
<PackageReference Include="ppy.osu.Game" Version="2026.429.0" />
14+
<PackageReference Include="ppy.osu.Game" Version="2026.505.0" />
1515
</ItemGroup>
1616

1717
</Project>

SampleSpectatorClient/SpectatorClient.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,11 @@ public Task DisconnectRequested()
9090
Console.WriteLine($"{connection.ConnectionId} Disconnect requested");
9191
return Task.CompletedTask;
9292
}
93+
94+
public Task ServerShuttingDown()
95+
{
96+
Console.WriteLine($"{connection.ConnectionId} Shutdown requested");
97+
return Task.CompletedTask;
98+
}
9399
}
94100
}

osu.Server.Spectator.Tests/Multiplayer/DelegatingMultiplayerClient.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,5 +264,11 @@ public async Task DisconnectRequested()
264264
foreach (var c in Clients.OfType<IMultiplayerClient>())
265265
await c.DisconnectRequested();
266266
}
267+
268+
public async Task ServerShuttingDown()
269+
{
270+
foreach (var c in Clients.OfType<IMultiplayerClient>())
271+
await c.ServerShuttingDown();
272+
}
267273
}
268274
}

osu.Server.Spectator/ConcurrentConnectionLimiter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ public async Task OnDisconnectedAsync(HubLifetimeContext context, Exception? exc
110110
{
111111
// if `exception` isn't null then the disconnection is not clean,
112112
// so don't unregister yet in hopes that the user will return after a transient network failure or similar.
113+
//
114+
// TODO: This is never cleaned up properly, leading to gradual user state leakage.
115+
// Maybe not a huge concern, but is persistent in memory until restart.
113116
if (exception == null)
114117
await unregisterConnection(context, exception);
115118
await next(context, exception);

osu.Server.Spectator/GracefulShutdownManager.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
using System.Text;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Microsoft.AspNetCore.SignalR;
1011
using Microsoft.Extensions.Hosting;
1112
using Microsoft.Extensions.Logging;
13+
using osu.Game.Online;
1214
using osu.Game.Online.Multiplayer;
1315
using osu.Server.Spectator.Entities;
1416
using osu.Server.Spectator.Hubs;
@@ -30,31 +32,40 @@ public class GracefulShutdownManager
3032

3133
private readonly List<IEntityStore> dependentStores = new List<IEntityStore>();
3234
private readonly EntityStore<ServerMultiplayerRoom> roomStore;
35+
private readonly IHubContext<MetadataHub> metadataHub;
36+
private readonly IHubContext<MultiplayerHub> multiplayerHub;
37+
private readonly IHubContext<SpectatorHub> spectatorHub;
3338
private readonly BuildUserCountUpdater buildUserCountUpdater;
3439
private readonly ILogger logger;
3540

3641
public GracefulShutdownManager(
3742
EntityStore<ServerMultiplayerRoom> roomStore,
38-
EntityStore<SpectatorClientState> clientStateStore,
43+
EntityStore<SpectatorClientState> spectatorState,
44+
IHubContext<MetadataHub> metadataHub,
45+
IHubContext<MultiplayerHub> multiplayerHub,
46+
IHubContext<SpectatorHub> spectatorHub,
3947
IHostApplicationLifetime hostApplicationLifetime,
4048
ScoreUploader scoreUploader,
41-
EntityStore<ConnectionState> connectionStateStore,
4249
EntityStore<MetadataClientState> metadataClientStore,
4350
BuildUserCountUpdater buildUserCountUpdater,
4451
ILoggerFactory loggerFactory)
4552
{
4653
this.roomStore = roomStore;
54+
55+
this.metadataHub = metadataHub;
56+
this.multiplayerHub = multiplayerHub;
57+
this.spectatorHub = spectatorHub;
58+
4759
this.buildUserCountUpdater = buildUserCountUpdater;
4860
logger = loggerFactory.CreateLogger(nameof(GracefulShutdownManager));
4961

5062
dependentStores.Add(roomStore);
51-
dependentStores.Add(clientStateStore);
63+
dependentStores.Add(spectatorState);
5264
dependentStores.Add(scoreUploader);
53-
dependentStores.Add(connectionStateStore);
5465
dependentStores.Add(metadataClientStore);
5566

5667
// Importantly, we don't block on `MultiplayerClientState` because they're only relevant as long as a `ServerMultiplayerRoom` exists in the first place.
57-
// More so, we want to allow these states to be created so existing rooms can continue to function until they are disbanded.
68+
// More so, we want to allow these states to be created so existing rooms can continue to function until they are disbanded (see `StopAcceptingEntities`).
5869
// Same logic applies to `RefereeClientState`.
5970

6071
hostApplicationLifetime.ApplicationStopping.Register(shutdownSafely);
@@ -71,6 +82,9 @@ private void shutdownSafely()
7182
foreach (var store in dependentStores)
7283
store.StopAcceptingEntities();
7384

85+
foreach (var clients in new[] { metadataHub.Clients, spectatorHub.Clients, multiplayerHub.Clients })
86+
clients.All.SendCoreAsync(nameof(IStatefulUserHubClient.ServerShuttingDown), Array.Empty<object>());
87+
7488
performOnAllRooms(async r =>
7589
{
7690
await r.StartCountdown(new ServerShuttingDownCountdown
@@ -79,7 +93,7 @@ await r.StartCountdown(new ServerShuttingDownCountdown
7993
});
8094
}).Wait();
8195

82-
TimeSpan timeWaited = new TimeSpan();
96+
TimeSpan timeWaited = TimeSpan.Zero;
8397
TimeSpan timeBetweenChecks = TimeSpan.FromSeconds(10);
8498

8599
var stringBuilder = new StringBuilder();

osu.Server.Spectator/osu.Server.Spectator.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
1717
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
1818
<PackageReference Include="OpenSkillSharp" Version="1.1.0" />
19-
<PackageReference Include="ppy.osu.Game" Version="2026.429.0" />
20-
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2026.429.0" />
21-
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2026.429.0" />
22-
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2026.429.0" />
23-
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2026.429.0" />
19+
<PackageReference Include="ppy.osu.Game" Version="2026.505.0" />
20+
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2026.505.0" />
21+
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2026.505.0" />
22+
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2026.505.0" />
23+
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2026.505.0" />
2424
<PackageReference Include="ppy.osu.Server.OsuQueueProcessor" Version="2026.317.0" />
2525
<PackageReference Include="Sentry.AspNetCore" Version="6.2.0" />
2626
</ItemGroup>

0 commit comments

Comments
 (0)