Skip to content

Commit

Permalink
Merge pull request #490 from MUnique/dev/restart-game-servers-adminpanel
Browse files Browse the repository at this point in the history
Restart game servers through admin panel
  • Loading branch information
sven-n authored Sep 8, 2024
2 parents edd4cca + 91e0e41 commit e98abff
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 26 deletions.
36 changes: 36 additions & 0 deletions src/Dapr/AdminPanel.Host/ServerRestarter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// <copyright file="ServerRestarter.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.AdminPanel.Host;

using MUnique.OpenMU.Interfaces;

/// <summary>
/// An implementation of <see cref="ISupportServerRestart"/>.
/// It restarts the server by stopping and starting it.
/// </summary>
public class ServerRestarter : ISupportServerRestart
{
private readonly IServerProvider _serverProvider;

/// <summary>
/// Initializes a new instance of the <see cref="ServerRestarter"/> class.
/// </summary>
/// <param name="serverProvider">The server provider.</param>
public ServerRestarter(IServerProvider serverProvider)
{
this._serverProvider = serverProvider;
}

/// <inheritdoc />
public async ValueTask RestartAllAsync(bool onDatabaseInit)
{
var gameServers = this._serverProvider.Servers.Where(server => server.Type == ServerType.GameServer).ToList();
foreach (var gameServer in gameServers)
{
await gameServer.ShutdownAsync().ConfigureAwait(false);
// It's started again automatically by the docker host.
}
}
}
11 changes: 11 additions & 0 deletions src/Dapr/GameServer.Host/GameServerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ public GameServerController(GameServer gameServer)
this._gameServer = gameServer;
}

/// <summary>
/// Shuts down the server gracefully.
/// </summary>
/// <returns></returns>
[HttpPost(nameof(IGameServer.ShutdownAsync))]
public async ValueTask ShutdownAsync()
{
await this._gameServer.ShutdownAsync().ConfigureAwait(false);
Environment.Exit(0);
}

/// <summary>
/// Sends a chat message to all connected guild members.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions src/Interfaces/ISupportServerRestart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// <copyright file="ISupportServerRestart.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Interfaces;

/// <summary>
/// Interface for a class which supports to restart a server.
/// </summary>
public interface ISupportServerRestart
{
/// <summary>
/// Restarts all servers of this container.
/// </summary>
/// <param name="onDatabaseInit">If set to <c>true</c>, this method is called during a database initialization.</param>
/// <returns></returns>

Check warning on line 16 in src/Interfaces/ISupportServerRestart.cs

View workflow job for this annotation

GitHub Actions / build

ValueTask RestartAllAsync(bool onDatabaseInit);
}
11 changes: 9 additions & 2 deletions src/Persistence/EntityFramework/PersistenceContextProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace MUnique.OpenMU.Persistence.EntityFramework;
using System.Threading;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Persistence.EntityFramework.Model;
using Nito.Disposables;
using Npgsql;
Expand Down Expand Up @@ -173,7 +172,7 @@ public async Task<IDisposable> ReCreateDatabaseAsync()
await this.ApplyAllPendingUpdatesAsync().ConfigureAwait(false);

// We create a new repository provider, so that the previously loaded data is not effective anymore.
this.RepositoryProvider = new CacheAwareRepositoryProvider(this._loggerFactory, changePublisher);
this.ResetCache();
}
catch
{
Expand All @@ -186,6 +185,14 @@ public async Task<IDisposable> ReCreateDatabaseAsync()
});
}

/// <summary>
/// Resets the cache of this instance.
/// </summary>
public void ResetCache()
{
this.RepositoryProvider = new CacheAwareRepositoryProvider(this._loggerFactory, this._changeListener);
}

/// <inheritdoc />
public IContext CreateNewContext()
{
Expand Down
5 changes: 5 additions & 0 deletions src/Persistence/IMigratableDatabaseContextProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,9 @@ public interface IMigratableDatabaseContextProvider : IPersistenceContextProvide
/// </summary>
/// <returns>The disposable which should be disposed when the data creation process is finished.</returns>
Task<IDisposable> ReCreateDatabaseAsync();

/// <summary>
/// Resets the cache of this instance.
/// </summary>
void ResetCache();
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,10 @@ public Task<IDisposable> ReCreateDatabaseAsync()
this._repositoryProvider = new();
return Task.FromResult<IDisposable>(new Disposable(() => { }));
}

/// <inheritdoc />
public void ResetCache()
{
// do nothing here
}
}
10 changes: 10 additions & 0 deletions src/Startup/GameServerContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ public void Dispose()
}
}

/// <inheritdoc />
protected override async ValueTask BeforeStartAsync(bool onDatabaseInit, CancellationToken cancellationToken)
{
await base.BeforeStartAsync(onDatabaseInit, cancellationToken);
if (!onDatabaseInit)
{
(this._persistenceContextProvider as IMigratableDatabaseContextProvider)?.ResetCache();
}
}

/// <inheritdoc />
protected override async Task StartInnerAsync(CancellationToken cancellationToken)
{
Expand Down
3 changes: 3 additions & 0 deletions src/Startup/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ private async Task<IHost> CreateHostAsync(string[] args)
.AddSingleton<IChatServer>(s => s.GetService<ChatServer>()!)
.AddSingleton<ConnectServerFactory>()
.AddSingleton<ConnectServerContainer>()
.AddSingleton<GameServerContainer>()
.AddSingleton<ISupportServerRestart>(provider => provider.GetService<GameServerContainer>()!)
.AddScoped<IMapFactory, JavascriptMapFactory>()
.AddSingleton<SetupService>()
.AddSingleton<IEnumerable<IConnectServer>>(provider => provider.GetService<ConnectServerContainer>() ?? throw new Exception($"{nameof(ConnectServerContainer)} not registered."))
Expand All @@ -282,6 +284,7 @@ private async Task<IHost> CreateHostAsync(string[] args)
.AddSingleton<IDataSource<GameConfiguration>, GameConfigurationDataSource>()
.AddHostedService<ChatServerContainer>()
.AddHostedService<GameServerContainer>()
.AddHostedService(provider => provider.GetService<GameServerContainer>()!)
.AddHostedService(provider => provider.GetService<ConnectServerContainer>()!)
.AddControllers().AddApplicationPart(typeof(ServerController).Assembly);

Expand Down
31 changes: 27 additions & 4 deletions src/Startup/ServerContainerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ namespace MUnique.OpenMU.Startup;
using System.Threading;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Web.AdminPanel.Services;

/// <summary>
/// Base class for a server container, which reacts on database recreations.
/// </summary>
public abstract class ServerContainerBase : IHostedService
public abstract class ServerContainerBase : IHostedService, ISupportServerRestart
{
private readonly SetupService _setupService;
private readonly ILogger _logger;
Expand Down Expand Up @@ -42,6 +43,19 @@ public async Task StopAsync(CancellationToken cancellationToken)
this._setupService.DatabaseInitialized -= this.OnDatabaseInitializedAsync;
}

/// <summary>
/// Restarts all servers of this container.
/// </summary>
/// <param name="onDatabaseInit">If set to <c>true</c>, this method is called during a database initialization.</param>
/// <returns></returns>
public virtual async ValueTask RestartAllAsync(bool onDatabaseInit)
{
await this.StopAsync(default).ConfigureAwait(false);
await this.BeforeStartAsync(onDatabaseInit, default).ConfigureAwait(false);
await this.StartAsync(default).ConfigureAwait(false);
await this.StartListenersAsync(default).ConfigureAwait(false);
}

/// <summary>
/// Starts the hosted service.
/// </summary>
Expand All @@ -60,13 +74,22 @@ public async Task StopAsync(CancellationToken cancellationToken)
/// <param name="cancellationToken">The cancellation token.</param>
protected abstract Task StartListenersAsync(CancellationToken cancellationToken);

/// <summary>
/// Befores the start asynchronous.
/// </summary>
/// <param name="onDatabaseInit">If set to <c>true</c>, this method is called during a database initialization.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
protected virtual async ValueTask BeforeStartAsync(bool onDatabaseInit, CancellationToken cancellationToken)
{
// can be overwritten
}

private async ValueTask OnDatabaseInitializedAsync()
{
try
{
await this.StopAsync(default).ConfigureAwait(false);
await this.StartAsync(default).ConfigureAwait(false);
await this.StartListenersAsync(default).ConfigureAwait(false);
await this.RestartAllAsync(true);
}
catch (Exception exception)
{
Expand Down
79 changes: 59 additions & 20 deletions src/Web/AdminPanel/Pages/Servers.razor
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,64 @@
else
{
<div>
<table>
<thead>
<tr>
<th></th>
<th class="col-sm-7">Server Name</th>
<th class="col-sm-1">Players</th>
<th class="col-sm-2">Current State</th>
<th class="col-sm-2">Action</th>
</tr>
</thead>
<tfoot>
<TotalOnlineCounter Servers=@_servers />
</tfoot>
<tbody>
@foreach (var server in this._servers.OrderBy(s => s.Type).ThenBy(s => s.Description))
{
<ServerItem Server=@server />
}
</tbody>
</table>
<table>
<thead>
<tr>
<th></th>
<th class="col-sm-7">Server Name</th>
<th class="col-sm-1">Players</th>
<th class="col-sm-2">Current State</th>
<th class="col-sm-2">Action</th>
</tr>
</thead>
<tfoot>
<TotalOnlineCounter Servers=@_servers />
</tfoot>
<tbody>
@foreach (var server in this._servers.OrderBy(s => s.Type).ThenBy(s => s.Description))
{
<ServerItem Server=@server />
}
</tbody>
</table>
<hr/>
@if (this.ServerRestartSupporter is not null)
{
@if (this._isRestarting)
{
<button type="button" class="btn-warning" disabled="disabled">
<div class="spinner-border text-secondary" role="status">
</div>
</button>
}
else
{
<button type="button" class="btn-warning" @onclick="this.OnReloadAndRestartClickAsync">
<span class="oi oi-reload"></span>
Reload configuration and restart all Game Servers
</button>
}
}
</div>
}

@code {
private IList<IManageableServer>? _servers;

private bool _isRestarting;

/// <summary>
/// Gets or sets the <see cref="IServerProvider"/>.
/// </summary>
[Inject]
public IServerProvider ServerProvider { get; set; } = null!;

/// <summary>
/// Gets or sets the <see cref="ISupportServerRestart"/>.
/// </summary>
[Inject] // TODO: Allow Default
public ISupportServerRestart? ServerRestartSupporter { get; set; }

/// <inheritdoc />
public void Dispose()
{
Expand All @@ -67,4 +93,17 @@ else
this.InvokeAsync(this.StateHasChanged);
}
}

private async Task OnReloadAndRestartClickAsync()
{
this._isRestarting = true;
try
{
await this.ServerRestartSupporter!.RestartAllAsync(false);
}
finally
{
this._isRestarting = false;
}
}
}

0 comments on commit e98abff

Please sign in to comment.