1
0
mirror of https://github.com/ncblakely/GiantsTools synced 2024-11-24 07:05:37 +01:00

Implement timed cleanup.

This commit is contained in:
Nick Blakely 2020-08-08 17:26:41 -07:00
parent aa16e3507c
commit 75c2b9474e
14 changed files with 194 additions and 35 deletions

View File

@ -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; }

View File

@ -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>();
} }
} }
} }

View File

@ -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>

View 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
{
}
}

View File

@ -1,13 +1,9 @@
using System; namespace Giants.Services
using System.Collections.Generic; {
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Giants.Services;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace Giants.WebApi
{
public class InitializerHostedService : IHostedService public class InitializerHostedService : IHostedService
{ {
private readonly IServerRegistryStore serverRegistryStore; private readonly IServerRegistryStore serverRegistryStore;

View 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);
}
}
}
}

View File

@ -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);
} }
} }

View File

@ -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
}
}
} }
} }

View File

@ -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()

View File

@ -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);
} }
} }

View File

@ -4,7 +4,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
public interface IUpdaterStore public interface IUpdaterStore
{ {
} }

View File

@ -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;
}
} }
} }

View File

@ -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);

View File

@ -10,5 +10,6 @@
"DatabaseId": "DefaultDatabase", "DatabaseId": "DefaultDatabase",
"ContainerId": "DefaultContainer", "ContainerId": "DefaultContainer",
"ServerTimeoutPeriodInMinutes": "7", "ServerTimeoutPeriodInMinutes": "7",
"ServerCleanupIntervalInMinutes": "1",
"MaxServerCount": 1000 "MaxServerCount": 1000
} }