Background services implementation (#6)

This commit is contained in:
2022-01-17 15:51:28 +01:00
committed by GitHub
parent 9bab9f2585
commit 35a45a395f
19 changed files with 309 additions and 11 deletions
@@ -0,0 +1,19 @@
using MTSC.ServerSide.BackgroundServices;
namespace MTSC.UnitTests.BackgroundServices
{
public sealed class IteratingBackgroundService : BackgroundServiceBase
{
private readonly IteratingService iteratingService;
public IteratingBackgroundService(IteratingService iteratingService)
{
this.iteratingService = iteratingService;
}
public override void Execute()
{
this.iteratingService.Iterate();
}
}
}
@@ -0,0 +1,23 @@
using MTSC.ServerSide;
namespace MTSC.UnitTests.BackgroundServices
{
public class IteratingService
{
private readonly Server server;
public int Iteration { get; private set; } = 0;
public IteratingService(Server server)
{
this.server = server;
}
public void Iterate()
{
this.server.Log("Iterating");
this.Iteration++;
this.server.Log("Iterated");
}
}
}
+23
View File
@@ -1,3 +1,4 @@
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MTSC.Common.Http;
@@ -6,6 +7,7 @@ using MTSC.Exceptions;
using MTSC.ServerSide.Handlers;
using MTSC.ServerSide.Schedulers;
using MTSC.ServerSide.UsageMonitors;
using MTSC.UnitTests.BackgroundServices;
using MTSC.UnitTests.RoutingModules;
using Newtonsoft.Json;
using System;
@@ -56,12 +58,15 @@ namespace MTSC.UnitTests
.AddRoute<LongRunningModule>(HttpMessage.HttpMethods.Get, "long-running")
.AddRoute<MultipartModule>(HttpMessage.HttpMethods.Post, "multipart")
.AddRoute<SomeRoutingModule>(HttpMessage.HttpMethods.Post, "some-module/{someValue}/test/{intValue}/test")
.AddRoute<IterationModule>(HttpMessage.HttpMethods.Get, "iteration")
.WithFragmentsExpirationTime(TimeSpan.FromMilliseconds(3000))
.WithMaximumSize(250000))
.AddBackgroundService<IteratingBackgroundService>(interval: TimeSpan.FromSeconds(5))
.AddExceptionHandler(new ExceptionConsoleLogger())
.SetScheduler(new ParallelScheduler())
.WithSslAuthenticationTimeout(TimeSpan.FromMilliseconds(100));
Server.ServiceManager.RegisterSingleton<ILogger, ConsoleLogger>();
Server.ServiceManager.RegisterSingleton<IteratingService>();
Server.RunAsync();
}
@@ -384,6 +389,24 @@ namespace MTSC.UnitTests
}
}
[TestMethod]
public async Task BackgroundService_GetsPeriodicallyCalled()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://localhost:800");
var result = await httpClient.GetAsync("iteration");
result.StatusCode.Should().Be(HttpStatusCode.OK);
var iteration = int.Parse(await result.Content.ReadAsStringAsync());
await Task.Delay(5000);
var result2 = await httpClient.GetAsync("iteration");
result2.StatusCode.Should().Be(HttpStatusCode.OK);
var iteration2 = int.Parse(await result2.Content.ReadAsStringAsync());
Assert.IsTrue(iteration == iteration2 - 1);
}
[ClassCleanup]
public static void CleanupServer()
{
@@ -0,0 +1,30 @@
using MTSC.Common.Http;
using MTSC.Common.Http.RoutingModules;
using MTSC.UnitTests.BackgroundServices;
using System;
using System.Threading.Tasks;
namespace MTSC.UnitTests.RoutingModules
{
public sealed class IterationModule : HttpRouteBase
{
private readonly IteratingService iteratingService;
public IterationModule(
IteratingService iteratingService)
{
this.iteratingService = iteratingService ?? throw new ArgumentNullException(nameof(iteratingService));
}
public override Task<HttpResponse> HandleRequest(HttpRequestContext request)
{
var iteration = this.iteratingService.Iteration;
return Task.FromResult(new HttpResponse
{
StatusCode = HttpMessage.StatusCodes.OK,
BodyString = iteration.ToString()
});
}
}
}
@@ -6,7 +6,7 @@ namespace MTSC.Common.WebSockets.ClientModules
{
public sealed class ChatModule : IWebsocketModule
{
static RNGCryptoServiceProvider rng = new();
static readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
#region Public Methods
public void SendMessage(WebsocketHandler websocketHandler, string message)
{
+3 -3
View File
@@ -5,13 +5,13 @@
<TargetFrameworks>net48;netstandard2.0;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<ApplicationIcon />
<StartupObject />
<Version>4.8.0</Version>
<Version>5.0</Version>
<LangVersion>latest</LangVersion>
<Authors>Alexandru-Victor Macocian</Authors>
<Product>MTSC</Product>
<Description>Modular TCP Server and Client</Description>
<AssemblyVersion>4.8.0.0</AssemblyVersion>
<FileVersion>4.8.0.0</FileVersion>
<AssemblyVersion>5.0.0.0</AssemblyVersion>
<FileVersion>5.0.0.0</FileVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Platforms>AnyCPU;x64</Platforms>
<PackageProjectUrl>https://github.com/AlexMacocian/MTSC</PackageProjectUrl>
@@ -0,0 +1,15 @@
using System.Threading.Tasks;
namespace MTSC.ServerSide.BackgroundServices
{
public abstract class AsyncBackgroundServiceBase : BackgroundServiceBase
{
public override sealed void Execute()
{
var task = Task.Run(this.ExecuteAsync);
Task.WaitAll(task);
}
public abstract Task ExecuteAsync();
}
}
@@ -0,0 +1,39 @@
using System;
namespace MTSC.ServerSide.BackgroundServices
{
/// <summary>
/// Base class for background services.
/// These services will be called periodically by the server, the period being defined by the <see cref="BackgroundServiceBase.ActivationInterval"/>.
/// </summary>
public abstract class BackgroundServiceBase : ISetBackgroundServiceProperties
{
/// <summary>
/// <see cref="ServerSide.Server"/> containing the <see cref="BackgroundServiceBase"/>.
/// </summary>
public Server Server { get; private set; }
/// <summary>
/// Interval between calls from server.
/// </summary>
public TimeSpan ActivationInterval { get; private set; }
/// <summary>
/// Last time the <see cref="BackgroundServiceBase"/> was called.
/// </summary>
public DateTime LastActivationTime { get; private set; }
public abstract void Execute();
void ISetBackgroundServiceProperties.SetActivationInterval(TimeSpan activationInterval)
{
this.ActivationInterval = activationInterval;
}
void ISetBackgroundServiceProperties.SetLastActivationTime(DateTime dateTime)
{
this.LastActivationTime = dateTime;
}
void ISetBackgroundServiceProperties.SetServer(Server server)
{
this.Server = server ?? throw new ArgumentNullException(nameof(server));
}
}
}
@@ -0,0 +1,12 @@
using System;
namespace MTSC.ServerSide.BackgroundServices
{
internal class BackgroundServiceMetadata
{
public DateTime LastActivationTime { get; set; }
public TimeSpan ActivationInterval { get; set; }
public Type RegisteredType { get; set; }
public BackgroundServiceBase InitializedService { get; set; }
}
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
namespace MTSC.ServerSide.BackgroundServices
{
public sealed class BackgroundServicesHolder
{
private readonly List<BackgroundServiceMetadata> backgroundServices = new();
private readonly Server server;
public BackgroundServicesHolder(Server server)
{
this.server = server ?? throw new ArgumentNullException();
}
public void RegisterBackgroundService<T>(TimeSpan activationInterval)
where T : BackgroundServiceBase
{
this.server.ServiceManager.RegisterSingleton<T>();
this.backgroundServices.Add(new BackgroundServiceMetadata { ActivationInterval = activationInterval, RegisteredType = typeof(T) });
}
public void Initialize()
{
foreach(var backgroundServiceMetadata in this.backgroundServices)
{
this.server.Log($"Initializing background service {backgroundServiceMetadata.RegisteredType.Name}");
backgroundServiceMetadata.InitializedService = this.server.ServiceManager.GetService(backgroundServiceMetadata.RegisteredType) as BackgroundServiceBase;
(backgroundServiceMetadata.InitializedService as ISetBackgroundServiceProperties).SetServer(this.server);
(backgroundServiceMetadata.InitializedService as ISetBackgroundServiceProperties).SetActivationInterval(backgroundServiceMetadata.ActivationInterval);
}
}
public void Tick()
{
foreach(var backgroundServiceMetadata in this.backgroundServices)
{
if (backgroundServiceMetadata.InitializedService is null)
{
continue;
}
if (DateTime.Now - backgroundServiceMetadata.LastActivationTime > backgroundServiceMetadata.ActivationInterval)
{
this.server.Log($"Activating background service {backgroundServiceMetadata.RegisteredType.Name}");
var activationTime = DateTime.Now;
backgroundServiceMetadata.LastActivationTime = activationTime;
(backgroundServiceMetadata.InitializedService as ISetBackgroundServiceProperties).SetLastActivationTime(activationTime);
this.server.Scheduler.ScheduleBackgroundService(backgroundServiceMetadata.InitializedService);
}
}
}
}
}
@@ -0,0 +1,11 @@
using System;
namespace MTSC.ServerSide.BackgroundServices
{
internal interface ISetBackgroundServiceProperties
{
void SetLastActivationTime(DateTime dateTime);
void SetServer(Server server);
void SetActivationInterval(TimeSpan activationInterval);
}
}
+2 -2
View File
@@ -176,10 +176,10 @@ namespace MTSC.ServerSide.Handlers
client.ResetAffinityIfMe(this);
return false;
}
catch (Exception e)
catch (Exception)
{
client.ResetAffinityIfMe(this);
throw e;
throw;
}
// The message has been parsed. If there was a cache for the current message, remove it.
@@ -1,4 +1,5 @@
using MTSC.Common;
using MTSC.ServerSide.BackgroundServices;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -7,6 +8,11 @@ namespace MTSC.ServerSide.Schedulers
{
public sealed class FireTasksAndForgetScheduler : IScheduler
{
public void ScheduleBackgroundService(BackgroundServiceBase backgroundServiceBase)
{
Task.Run(() => backgroundServiceBase.Execute());
}
public void ScheduleHandling(List<(ClientData, IConsumerQueue<Message>)> clientsQueues, Action<ClientData, IConsumerQueue<Message>> messageHandlingProcedure)
{
foreach(var tuple in clientsQueues)
+2
View File
@@ -1,4 +1,5 @@
using MTSC.Common;
using MTSC.ServerSide.BackgroundServices;
using System;
using System.Collections.Generic;
@@ -7,5 +8,6 @@ namespace MTSC.ServerSide.Schedulers
public interface IScheduler
{
void ScheduleHandling(List<(ClientData, IConsumerQueue<Message>)> clientsQueues, Action<ClientData, IConsumerQueue<Message>> messageHandlingProcedure);
void ScheduleBackgroundService(BackgroundServiceBase backgroundServiceBase);
}
}
@@ -1,4 +1,5 @@
using MTSC.Common;
using MTSC.ServerSide.BackgroundServices;
using System;
using System.Collections.Generic;
using System.Threading;
@@ -7,6 +8,14 @@ namespace MTSC.ServerSide.Schedulers
{
public sealed class MultiThreadScheduler : IScheduler
{
public void ScheduleBackgroundService(BackgroundServiceBase backgroundServiceBase)
{
new Thread(() =>
{
backgroundServiceBase.Execute();
}).Start();
}
public void ScheduleHandling(List<(ClientData, IConsumerQueue<Message>)> clientsQueues, Action<ClientData, IConsumerQueue<Message>> messageHandlingProcedure)
{
foreach((var client, var messageQueue) in clientsQueues)
@@ -1,4 +1,5 @@
using MTSC.Common;
using MTSC.ServerSide.BackgroundServices;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,6 +9,11 @@ namespace MTSC.ServerSide.Schedulers
{
public sealed class ParallelScheduler : IScheduler
{
public void ScheduleBackgroundService(BackgroundServiceBase backgroundServiceBase)
{
Parallel.Invoke(backgroundServiceBase.Execute);
}
public void ScheduleHandling(List<(ClientData, IConsumerQueue<Message>)> clientsQueues, Action<ClientData, IConsumerQueue<Message>> messageHandlingProcedure)
{
Parallel.Invoke(clientsQueues.Select(tuple => new Action(() => messageHandlingProcedure.Invoke(tuple.Item1, tuple.Item2))).ToArray());
@@ -1,4 +1,5 @@
using MTSC.Common;
using MTSC.ServerSide.BackgroundServices;
using System;
using System.Collections.Generic;
@@ -6,6 +7,11 @@ namespace MTSC.ServerSide.Schedulers
{
public sealed class SequentialProcessingScheduler : IScheduler
{
public void ScheduleBackgroundService(BackgroundServiceBase backgroundServiceBase)
{
backgroundServiceBase.Execute();
}
public void ScheduleHandling(List<(ClientData, IConsumerQueue<Message>)> clientsQueues, Action<ClientData, IConsumerQueue<Message>> messageHandlingProcedure)
{
foreach(var tuple in clientsQueues)
@@ -1,4 +1,5 @@
using MTSC.Common;
using MTSC.ServerSide.BackgroundServices;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -7,6 +8,12 @@ namespace MTSC.ServerSide.Schedulers
{
public sealed class TaskAwaiterScheduler : IScheduler
{
public void ScheduleBackgroundService(BackgroundServiceBase backgroundServiceBase)
{
(backgroundServiceBase as ISetBackgroundServiceProperties).SetLastActivationTime(DateTime.Now);
var task = Task.Run(() => backgroundServiceBase.Execute());
}
public void ScheduleHandling(List<(ClientData, IConsumerQueue<Message>)> clientsQueues, Action<ClientData, IConsumerQueue<Message>> messageHandlingProcedure)
{
var tasks = new List<Task>();
+41 -5
View File
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using MTSC.Common;
using MTSC.Exceptions;
using MTSC.ServerSide.BackgroundServices;
using MTSC.ServerSide.Handlers;
using MTSC.ServerSide.Listeners;
using MTSC.ServerSide.Schedulers;
@@ -17,10 +18,11 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
// TODO: Setup basic background services
namespace MTSC.ServerSide
{
/// <summary>
/// Basic server class to handle TCP connections.
/// Main server class.
/// </summary>
public sealed class Server
{
@@ -28,6 +30,7 @@ namespace MTSC.ServerSide
private CancellationToken cancellationToken;
private bool running;
private X509Certificate2 certificate;
private readonly BackgroundServicesHolder backgroundServicesHolder;
private readonly ProducerConsumerQueue<ClientData> addQueue = new();
private readonly List<ClientData> clients = new();
private readonly List<ClientData> toRemove = new();
@@ -113,6 +116,7 @@ namespace MTSC.ServerSide
{
this.ServiceManager.RegisterServiceManager();
this.ServiceManager.RegisterSingleton<Server, Server>(sp => this);
this.backgroundServicesHolder = new(this);
}
/// <summary>
/// Creates an instance of server.
@@ -123,6 +127,7 @@ namespace MTSC.ServerSide
this.Port = port;
this.ServiceManager.RegisterServiceManager();
this.ServiceManager.RegisterSingleton<Server, Server>(sp => this);
this.backgroundServicesHolder = new(this);
}
/// <summary>
/// Creates an instance of server.
@@ -135,6 +140,7 @@ namespace MTSC.ServerSide
this.Port = port;
this.ServiceManager.RegisterServiceManager();
this.ServiceManager.RegisterSingleton<Server, Server>(sp => this);
this.backgroundServicesHolder = new(this);
}
/// <summary>
/// Creates an instance of server.
@@ -147,6 +153,7 @@ namespace MTSC.ServerSide
this.Port = port;
this.ServiceManager.RegisterServiceManager();
this.ServiceManager.RegisterSingleton<Server, Server>(sp => this);
this.backgroundServicesHolder = new(this);
}
/// <summary>
/// Creates an instance of server.
@@ -161,6 +168,7 @@ namespace MTSC.ServerSide
this.IPAddress = ipAddress;
this.ServiceManager.RegisterServiceManager();
this.ServiceManager.RegisterSingleton<Server, Server>(sp => this);
this.backgroundServicesHolder = new(this);
}
#endregion
#region Public Methods
@@ -249,6 +257,18 @@ namespace MTSC.ServerSide
return this;
}
/// <summary>
/// Adds a <see cref="BackgroundServiceBase"/> to the server.
/// </summary>
/// <param name="interval">Interval between ticks.</param>
/// <typeparam name="TBackgroundService">Type of the background service.</typeparam>
/// <returns>This <see cref="Server"/> object.</returns>
public Server AddBackgroundService<TBackgroundService>(TimeSpan interval)
where TBackgroundService : BackgroundServiceBase
{
this.backgroundServicesHolder.RegisterBackgroundService<TBackgroundService>(interval);
return this;
}
/// <summary>
/// Sets the <see cref="ReadTimeout"/> property.
/// </summary>
/// <param name="timeout"></param>
@@ -531,6 +551,11 @@ namespace MTSC.ServerSide
toBeRunOnStartup.OnStartup(this);
}
/*
* Initialize background services.
*/
this.backgroundServicesHolder.Initialize();
DateTime startLoopTime;
while (this.running)
{
@@ -614,6 +639,18 @@ namespace MTSC.ServerSide
this.TickHandler(handler);
}
/*
* Tick background services
*/
try
{
this.backgroundServicesHolder.Tick();
}
catch(Exception e)
{
this.HandleException(e);
}
/*
* Check if there are messages queued to be sent.
*/
@@ -644,10 +681,7 @@ namespace MTSC.ServerSide
}
catch (Exception e)
{
foreach (var handler in this.exceptionHandlers)
{
handler.HandleException(e);
}
this.HandleException(e);
}
}
@@ -876,6 +910,8 @@ namespace MTSC.ServerSide
}
private void HandleException(Exception exception)
{
this.Log($"Encountered {exception.GetType().Name}");
this.LogDebug(exception.ToString());
foreach (var exceptionHandler in this.exceptionHandlers)
{
if (exceptionHandler.HandleException(exception))