mirror of
https://github.com/ncblakely/GiantsTools
synced 2024-11-23 22:55:37 +01:00
Implement timed cleanup.
This commit is contained in:
parent
aa16e3507c
commit
75c2b9474e
@ -1,9 +1,5 @@
|
|||||||
namespace Giants.DataContract
|
namespace Giants.DataContract
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
public class PlayerInfo
|
public class PlayerInfo
|
||||||
{
|
{
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
services.AddSingleton<IServerRegistryService, ServerRegistryService>();
|
services.AddSingleton<IServerRegistryService, ServerRegistryService>();
|
||||||
services.AddSingleton<IServerRegistryStore, CosmosDbServerRegistryStore>();
|
services.AddSingleton<IServerRegistryStore, CosmosDbServerRegistryStore>();
|
||||||
services.AddSingleton<IDateTimeProvider, DefaultDateTimeProvider>();
|
services.AddSingleton<IDateTimeProvider, DefaultDateTimeProvider>();
|
||||||
|
|
||||||
|
services.AddHostedService<InitializerHostedService>();
|
||||||
|
services.AddHostedService<ServerRegistryCleanupService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.12.0" />
|
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.12.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.6" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
13
Giants.Services/Services/IServerRegistryCleanupService.cs
Normal file
13
Giants.Services/Services/IServerRegistryCleanupService.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Giants.Services
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
public interface IServerRegistryCleanupService : IHostedService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
using System;
|
namespace Giants.Services
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Giants.Services;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace Giants.WebApi
|
|
||||||
{
|
{
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
public class InitializerHostedService : IHostedService
|
public class InitializerHostedService : IHostedService
|
||||||
{
|
{
|
||||||
private readonly IServerRegistryStore serverRegistryStore;
|
private readonly IServerRegistryStore serverRegistryStore;
|
77
Giants.Services/Services/ServerRegistryCleanupService.cs
Normal file
77
Giants.Services/Services/ServerRegistryCleanupService.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
namespace Giants.Services
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Giants.Services.Core;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
public class ServerRegistryCleanupService : IServerRegistryCleanupService, IDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger<ServerRegistryCleanupService> logger;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
private readonly IServerRegistryStore serverRegistryStore;
|
||||||
|
private readonly IDateTimeProvider dateTimeProvider;
|
||||||
|
private readonly TimeSpan timeoutPeriod;
|
||||||
|
private readonly TimeSpan cleanupInterval;
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
public ServerRegistryCleanupService(
|
||||||
|
ILogger<ServerRegistryCleanupService> logger,
|
||||||
|
IConfiguration configuration,
|
||||||
|
IServerRegistryStore serverRegistryStore,
|
||||||
|
IDateTimeProvider dateTimeProvider)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.serverRegistryStore = serverRegistryStore;
|
||||||
|
this.dateTimeProvider = dateTimeProvider;
|
||||||
|
this.timeoutPeriod = TimeSpan.FromMinutes(Convert.ToDouble(this.configuration["ServerTimeoutPeriodInMinutes"]));
|
||||||
|
this.cleanupInterval = TimeSpan.FromMinutes(Convert.ToDouble(this.configuration["ServerCleanupIntervalInMinutes"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
this.timer = new Timer(TimerCallback, null, TimeSpan.Zero, this.cleanupInterval);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
this.timer?.Change(Timeout.Infinite, 0);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.timer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimerCallback(object state)
|
||||||
|
{
|
||||||
|
this.CleanupServers().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CleanupServers()
|
||||||
|
{
|
||||||
|
List<string> expiredServers = (await this
|
||||||
|
.serverRegistryStore
|
||||||
|
.GetServerInfos(whereExpression: s => s.LastHeartbeat < (this.dateTimeProvider.UtcNow - this.timeoutPeriod)))
|
||||||
|
.Select(s => s.id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (expiredServers.Any())
|
||||||
|
{
|
||||||
|
logger.LogInformation("Cleaning up {Count} servers.", expiredServers.Count);
|
||||||
|
|
||||||
|
await this.serverRegistryStore.DeleteServers(expiredServers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,7 @@
|
|||||||
public async Task<IEnumerable<ServerInfo>> GetAllServers()
|
public async Task<IEnumerable<ServerInfo>> GetAllServers()
|
||||||
{
|
{
|
||||||
return (await this.registryStore
|
return (await this.registryStore
|
||||||
.GetServerInfos(c => c.LastHeartbeat > this.dateTimeProvider.UtcNow - this.timeoutPeriod))
|
.GetServerInfos(whereExpression: c => c.LastHeartbeat > this.dateTimeProvider.UtcNow - this.timeoutPeriod))
|
||||||
.Take(this.maxServerCount);
|
.Take(this.maxServerCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,15 @@
|
|||||||
|
|
||||||
public async Task<IEnumerable<TSelect>> GetItems<T, TSelect>(
|
public async Task<IEnumerable<TSelect>> GetItems<T, TSelect>(
|
||||||
Expression<Func<T, TSelect>> selectExpression,
|
Expression<Func<T, TSelect>> selectExpression,
|
||||||
string partitionKey,
|
Expression<Func<T, bool>> whereExpression = null,
|
||||||
Expression<Func<T, bool>> whereExpression = null)
|
string partitionKey = null)
|
||||||
where T : IIdentifiable
|
where T : IIdentifiable
|
||||||
{
|
{
|
||||||
|
if (partitionKey == null)
|
||||||
|
{
|
||||||
|
partitionKey = typeof(T).Name;
|
||||||
|
}
|
||||||
|
|
||||||
IQueryable<T> query = this.container
|
IQueryable<T> query = this.container
|
||||||
.GetItemLinqQueryable<T>(requestOptions: new QueryRequestOptions
|
.GetItemLinqQueryable<T>(requestOptions: new QueryRequestOptions
|
||||||
{
|
{
|
||||||
@ -131,5 +136,25 @@
|
|||||||
|
|
||||||
this.container = containerResponse.Container;
|
this.container = containerResponse.Container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteItem<T>(
|
||||||
|
string id,
|
||||||
|
string partitionKey = null,
|
||||||
|
ItemRequestOptions requestOptions = null)
|
||||||
|
{
|
||||||
|
if (partitionKey == null)
|
||||||
|
{
|
||||||
|
partitionKey = typeof(T).Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.container.DeleteItemAsync<T>(id, new PartitionKey(partitionKey), requestOptions);
|
||||||
|
}
|
||||||
|
catch (CosmosException e) when (e.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,23 +6,40 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Cosmos;
|
using Microsoft.Azure.Cosmos;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
public class CosmosDbServerRegistryStore : IServerRegistryStore
|
public class CosmosDbServerRegistryStore : IServerRegistryStore
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<CosmosDbServerRegistryStore> logger;
|
||||||
private readonly IConfiguration configuration;
|
private readonly IConfiguration configuration;
|
||||||
private CosmosDbClient client;
|
private CosmosDbClient client;
|
||||||
|
|
||||||
public CosmosDbServerRegistryStore(IConfiguration configuration)
|
public CosmosDbServerRegistryStore(
|
||||||
|
ILogger<CosmosDbServerRegistryStore> logger,
|
||||||
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
this.logger = logger;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<ServerInfo>> GetServerInfos(
|
public async Task<IEnumerable<ServerInfo>> GetServerInfos(
|
||||||
Expression<Func<ServerInfo, bool>> whereExpression = null)
|
Expression<Func<ServerInfo, bool>> whereExpression = null,
|
||||||
|
string partitionKey = null)
|
||||||
{
|
{
|
||||||
return await this.client.GetItems<ServerInfo>(whereExpression);
|
return await this.client.GetItems<ServerInfo>(whereExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TSelect>> GetServerInfos<TSelect>(
|
||||||
|
Expression<Func<ServerInfo, TSelect>> selectExpression,
|
||||||
|
Expression<Func<ServerInfo, bool>> whereExpression = null,
|
||||||
|
string partitionKey = null)
|
||||||
|
{
|
||||||
|
return await this.client.GetItems<ServerInfo, TSelect>(
|
||||||
|
selectExpression: selectExpression,
|
||||||
|
whereExpression: whereExpression,
|
||||||
|
partitionKey: partitionKey);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<ServerInfo> GetServerInfo(string ipAddress)
|
public async Task<ServerInfo> GetServerInfo(string ipAddress)
|
||||||
{
|
{
|
||||||
return await this.client.GetItemById<ServerInfo>(ipAddress);
|
return await this.client.GetItemById<ServerInfo>(ipAddress);
|
||||||
@ -30,7 +47,19 @@
|
|||||||
|
|
||||||
public async Task UpsertServerInfo(ServerInfo serverInfo)
|
public async Task UpsertServerInfo(ServerInfo serverInfo)
|
||||||
{
|
{
|
||||||
await this.client.UpsertItem(serverInfo, new PartitionKey(serverInfo.DocumentType));
|
await this.client.UpsertItem(
|
||||||
|
item: serverInfo,
|
||||||
|
partitionKey: new PartitionKey(serverInfo.DocumentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteServers(IEnumerable<string> ids, string partitionKey = null)
|
||||||
|
{
|
||||||
|
foreach (string id in ids)
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("Deleting server for host IP {IPAddress}", id);
|
||||||
|
|
||||||
|
await this.client.DeleteItem<ServerInfo>(id, partitionKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize()
|
public async Task Initialize()
|
||||||
@ -44,4 +73,4 @@
|
|||||||
await this.client.Initialize();
|
await this.client.Initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,12 +7,19 @@
|
|||||||
|
|
||||||
public interface IServerRegistryStore
|
public interface IServerRegistryStore
|
||||||
{
|
{
|
||||||
public Task Initialize();
|
Task Initialize();
|
||||||
|
|
||||||
public Task<IEnumerable<ServerInfo>> GetServerInfos(Expression<Func<ServerInfo, bool>> whereExpression = null);
|
Task<IEnumerable<ServerInfo>> GetServerInfos(Expression<Func<ServerInfo, bool>> whereExpression = null, string partitionKey = null);
|
||||||
|
|
||||||
public Task<ServerInfo> GetServerInfo(string ipAddress);
|
Task<IEnumerable<TSelect>> GetServerInfos<TSelect>(
|
||||||
|
Expression<Func<ServerInfo, TSelect>> selectExpression,
|
||||||
|
Expression<Func<ServerInfo, bool>> whereExpression = null,
|
||||||
|
string partitionKey = null);
|
||||||
|
|
||||||
public Task UpsertServerInfo(ServerInfo serverInfo);
|
Task<ServerInfo> GetServerInfo(string ipAddress);
|
||||||
|
|
||||||
|
Task UpsertServerInfo(ServerInfo serverInfo);
|
||||||
|
|
||||||
|
Task DeleteServers(IEnumerable<string> ids, string partitionKey = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
public interface IUpdaterStore
|
public interface IUpdaterStore
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,6 @@
|
|||||||
{
|
{
|
||||||
private ConcurrentDictionary<string, ServerInfo> servers = new ConcurrentDictionary<string, ServerInfo>();
|
private ConcurrentDictionary<string, ServerInfo> servers = new ConcurrentDictionary<string, ServerInfo>();
|
||||||
|
|
||||||
public Task<IEnumerable<ServerInfo>> GetServerInfos(
|
|
||||||
Expression<Func<ServerInfo, bool>> whereExpression = null)
|
|
||||||
{
|
|
||||||
return Task.FromResult(
|
|
||||||
this.servers.Values.AsEnumerable());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<ServerInfo> GetServerInfo(string ipAddress)
|
public Task<ServerInfo> GetServerInfo(string ipAddress)
|
||||||
{
|
{
|
||||||
if (servers.ContainsKey(ipAddress))
|
if (servers.ContainsKey(ipAddress))
|
||||||
@ -42,5 +35,25 @@
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<ServerInfo>> GetServerInfos(Expression<Func<ServerInfo, bool>> whereExpression = null, string partitionKey = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<TSelect>> GetServerInfos<TSelect>(Expression<Func<ServerInfo, TSelect>> selectExpression, Expression<Func<ServerInfo, bool>> whereExpression = null, string partitionKey = null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteServers(IEnumerable<string> ids, string partitionKey = null)
|
||||||
|
{
|
||||||
|
foreach (string id in ids)
|
||||||
|
{
|
||||||
|
this.servers.TryRemove(id, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Giants.Services;
|
using Giants.Services;
|
||||||
using Giants.WebApi;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
@ -27,7 +26,7 @@ namespace Giants.Web
|
|||||||
|
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||||
services.AddHostedService<InitializerHostedService>();
|
|
||||||
|
|
||||||
ServicesModule.RegisterServices(services, Configuration);
|
ServicesModule.RegisterServices(services, Configuration);
|
||||||
|
|
||||||
|
@ -10,5 +10,6 @@
|
|||||||
"DatabaseId": "DefaultDatabase",
|
"DatabaseId": "DefaultDatabase",
|
||||||
"ContainerId": "DefaultContainer",
|
"ContainerId": "DefaultContainer",
|
||||||
"ServerTimeoutPeriodInMinutes": "7",
|
"ServerTimeoutPeriodInMinutes": "7",
|
||||||
"MaxServerCount": 1000
|
"ServerCleanupIntervalInMinutes": "1",
|
||||||
|
"MaxServerCount": 1000
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user