rework MareConfigurationServiceClient

This commit is contained in:
rootdarkarchon
2022-12-28 04:24:53 +01:00
parent 18a9a76dcd
commit de2defe692
7 changed files with 120 additions and 78 deletions

View File

@@ -1,25 +1,30 @@
using Grpc.Net.ClientFactory;
using MareSynchronosShared.Protos;
using MareSynchronosShared.Utils;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.Json;
using static MareSynchronosShared.Protos.ConfigurationService;
namespace MareSynchronosShared.Services;
public class MareConfigurationServiceClient<T> : IConfigurationService<T> where T : class, IMareConfiguration
public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationService<T> where T : class, IMareConfiguration
{
internal record RemoteCachedEntry(object Value, DateTime Inserted);
private readonly T _config;
private readonly ConcurrentDictionary<string, RemoteCachedEntry> _cachedRemoteProperties = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, object> _cachedRemoteProperties = new(StringComparer.Ordinal);
private readonly ILogger<MareConfigurationServiceClient<T>> _logger;
private readonly GrpcClientFactory _grpcClientFactory;
private readonly string _grpcClientName;
private readonly CancellationTokenSource _updateTaskCts = new();
private ConfigurationServiceClient _configurationServiceClient;
private bool _initialized = false;
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptions<T> config, GrpcClientFactory grpcClientFactory, string grpcClientName)
{
@@ -37,90 +42,26 @@ public class MareConfigurationServiceClient<T> : IConfigurationService<T> where
if (prop == null) return defaultValue;
if (prop.PropertyType != typeof(T1)) throw new InvalidCastException($"Invalid Cast: Property {key} is {prop.PropertyType}, wanted: {typeof(T1)}");
bool isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), inherit: true).Any();
if (isRemote)
if (isRemote && _cachedRemoteProperties.TryGetValue(key, out var remotevalue))
{
if (_cachedRemoteProperties.TryGetValue(key, out var existingEntry))
{
return (T1)_cachedRemoteProperties[key].Value;
}
try
{
var result = GetValueFromGrpc(key, defaultValue, prop.PropertyType);
if (result == null) return defaultValue;
_cachedRemoteProperties[key] = result;
return (T1)_cachedRemoteProperties[key].Value;
}
catch (Exception ex)
{
if (existingEntry != null)
{
_logger.LogWarning(ex, "Could not get value for {key} from Grpc, returning existing", key);
return (T1)existingEntry.Value;
}
else
{
_logger.LogWarning(ex, "Could not get value for {key} from Grpc, returning default", key);
return defaultValue;
}
}
return (T1)remotevalue;
}
var value = prop.GetValue(_config);
var defaultPropValue = prop.PropertyType.IsValueType ? Activator.CreateInstance(prop.PropertyType) : null;
if (value == defaultPropValue) return defaultValue;
return (T1)value;
}
private RemoteCachedEntry? GetValueFromGrpc(string key, object defaultValue, Type t)
{
// grab stuff from grpc
try
{
_logger.LogInformation("Getting {key} from Grpc", key);
var configClient = _grpcClientFactory.CreateClient<ConfigurationServiceClient>(_grpcClientName);
var response = configClient.GetConfigurationEntry(new KeyMessage { Key = key, Default = Convert.ToString(defaultValue, CultureInfo.InvariantCulture) });
_logger.LogInformation("Grpc Response for {key} = {value}", key, response.Value);
return new RemoteCachedEntry(JsonSerializer.Deserialize(response.Value, t), DateTime.Now);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failure Getting Cached Entry");
return null;
}
}
public T1 GetValue<T1>(string key)
{
var prop = _config.GetType().GetProperty(key);
if (prop == null) throw new KeyNotFoundException(key);
if (prop.PropertyType != typeof(T1)) throw new InvalidCastException($"Invalid Cast: Property {key} is {prop.PropertyType}, wanted: {typeof(T1)}");
bool isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), inherit: true).Any();
if (isRemote)
if (isRemote && _cachedRemoteProperties.TryGetValue(key, out var remotevalue))
{
if (_cachedRemoteProperties.TryGetValue(key, out var existingEntry))
{
return (T1)_cachedRemoteProperties[key].Value;
}
try
{
var result = GetValueFromGrpc(key, null, prop.PropertyType);
if (result == null) throw new KeyNotFoundException(key);
_cachedRemoteProperties[key] = result;
return (T1)_cachedRemoteProperties[key].Value;
}
catch (Exception ex)
{
if (existingEntry != null)
{
_logger.LogWarning(ex, "Could not get value for {key} from Grpc, returning existing", key);
return (T1)existingEntry.Value;
}
else
{
_logger.LogWarning(ex, "Could not get value for {key} from Grpc, throwing exception", key);
throw new KeyNotFoundException(key);
}
}
return (T1)remotevalue;
}
var value = prop.GetValue(_config);
@@ -141,4 +82,84 @@ public class MareConfigurationServiceClient<T> : IConfigurationService<T> where
}
return sb.ToString();
}
private async Task<T1> GetValueFromGrpc<T1>(ConfigurationServiceClient client, string key, object defaultValue)
{
// grab stuff from grpc
try
{
_logger.LogInformation("Getting {key} from Grpc", key);
var response = await client.GetConfigurationEntryAsync(new KeyMessage { Key = key, Default = Convert.ToString(defaultValue, CultureInfo.InvariantCulture) }).ConfigureAwait(false);
_logger.LogInformation("Grpc Response for {key} = {value}", key, response.Value);
return JsonSerializer.Deserialize<T1>(response.Value);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failure Getting Remote Entry for {key}", key);
return (T1)defaultValue;
}
}
private async Task UpdateRemoteProperties(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
_logger.LogInformation("Getting Properties from GRPC");
try
{
_configurationServiceClient = _grpcClientFactory.CreateClient<ConfigurationServiceClient>(_grpcClientName);
var properties = _config.GetType().GetProperties();
foreach (var prop in properties)
{
_logger.LogInformation("Checking Property " + prop.Name);
try
{
if (!prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()) continue;
var mi = GetType().GetMethod(nameof(GetValueFromGrpc), BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(prop.PropertyType);
var defaultValue = prop.PropertyType.IsValueType ? Activator.CreateInstance(prop.PropertyType) : null;
var task = (Task)mi.Invoke(this, new[] { _configurationServiceClient, prop.Name, defaultValue });
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
var resultValue = resultProperty.GetValue(task);
if (resultValue != defaultValue)
{
_cachedRemoteProperties[prop.Name] = resultValue;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during getting property " + prop.Name);
}
}
if (!_initialized)
{
_initialized = true;
_logger.LogInformation("Finished initial getting properties from GRPC");
_logger.LogInformation(ToString());
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failure getting or updating properties from GRPC, retrying in 30min");
}
await Task.Delay(TimeSpan.FromMinutes(30), ct).ConfigureAwait(false);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting MareConfigurationServiceClient");
_ = UpdateRemoteProperties(_updateTaskCts.Token);
while (!_initialized && !cancellationToken.IsCancellationRequested) await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_updateTaskCts.Cancel();
return Task.CompletedTask;
}
}