Skip to content

Commit

Permalink
Configuration sync disposable config providers (#31)
Browse files Browse the repository at this point in the history
* Sync all versions

All packages are now on inline versioning.

* #30: Sync Logging with EventLog

~ Convert LogLevel property into a getter for dynamic runtime fetching of the log level.
~ Add a fix on Windows for color logging failing by manually updating the console mode flags with ENABLE_VIRTUAL_TERMINAL_PROCESSING.
~ Rename LogLevel.Trace and ILogger.Trace to Verbose and add better docomments to each method and log level to better explain their use cases.
~ Remove ILogger.Log as this was technically an alias to ILogger.Information as it did the same thing.
~ Update all log methods to now take the raw message getter instead of formatting the messages themselves, this will help to introduce better performance by not evaluating the message straight away.
~ Add another ILogger.Error method with an argument to supply a message that precedes the exception trace back.

* Update Configuration:

Convert IVaultProvider to IDisposable
Update current registered auto refresh providers to a dictionary to make concurrent removal easier.
Expose the current cached values to inheritied classes.
Convert mount and path to virutal to not introduce a breaking change by adding set to the mix.
Move the refresh thread from a simple thread wait to a wait event system to allow manual refreshing of all global providers.
Add argument to constructors to control if the provider should be added to the auto refresh thread or not.
Add extra constructor to allow setting mount and path via constructor.
Implement IDisposable
  • Loading branch information
jf-06 authored Oct 4, 2024
1 parent 575c923 commit eeca03d
Show file tree
Hide file tree
Showing 31 changed files with 266 additions and 236 deletions.
2 changes: 2 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
<Copyright>Copyright © $(Company) $([System.DateTime]::Now.ToString(`yyyy`)). All rights reserved.</Copyright>
<Authors>$(Company);Nikita Petko</Authors>

<VersionPrefix>1.0.9</VersionPrefix>

<RepositoryUrl>https://github.com/mfdlabs/grid-bot-libraries</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>HTTP client used to interact with Roblox's Client Settings API Site.</Description>

<Version>1.0.4</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/clients/thumbnails-client/Thumbnails.Client.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>HTTP client used to interact with Roblox's Thumbnails API Site.</Description>

<Version>1.0.4</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/clients/users-client/Users.Client.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>HTTP client used to interact with Roblox's Users API Site.</Description>

<Version>1.0.4</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/configuration/configuration/Configuration.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>C# adaptation for @mfdlabs/environment.</Description>

<Version>1.0.8</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
namespace Configuration;

using System;
using System.Linq;
using System.Collections;

/// <summary>
/// Implementation for <see cref="BaseProvider"/> that uses Environment variables.
Expand Down
102 changes: 74 additions & 28 deletions src/configuration/configuration/Implementation/VaultProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

using Vault;
using Logging;

using Threading.Extensions;


/// <summary>
/// Implementation for <see cref="BaseProvider"/> via Vault.
/// </summary>
Expand All @@ -40,19 +42,25 @@ private static TimeSpan _defaultRefreshInterval
nameof(Path),
};

private static readonly ConcurrentBag<VaultProvider> _providers = new();
private static readonly ConcurrentDictionary<string, VaultProvider> _providers = new();

private IDictionary<string, object> _cachedValues = new Dictionary<string, object>();
private bool _disposed = false;

/// <summary>
/// The raw cached values.
/// </summary>
protected IDictionary<string, object> _CachedValues = new Dictionary<string, object>();

private static readonly ManualResetEvent _refreshRequestEvent = new(false);
private static readonly Thread _refreshThread;
private static readonly IVaultClient _client = VaultClientFactory.Singleton.GetClient();
private static readonly ILogger _staticLogger = Logger.Singleton;

/// <inheritdoc cref="IVaultProvider.Mount"/>
public abstract string Mount { get; }
public virtual string Mount { get; set; }

/// <inheritdoc cref="IVaultProvider.Path"/>
public abstract string Path { get; }
public virtual string Path { get; set; }

/// <summary>
/// Gets the refresh interval for the settings provider.
Expand All @@ -78,16 +86,25 @@ static VaultProvider()
_staticLogger?.Debug("VaultProvider: Started refresh thread!");
}

/// <summary>
/// Refresh all the currently registered providers immediately, this method is asynchronous
/// and will return immediately.
/// </summary>
public static void RefreshAllProviders()
{
_refreshRequestEvent.Set();
}

/// <inheritdoc cref="BaseProvider.SetRawValue{T}(string, T)"/>
protected override void SetRawValue<T>(string variable, T value)
{
if (_client == null) return;

_logger?.Debug("VaultProvider: Set value in vault at path '{0}/{1}/{2}'", Mount, Path, variable);
_logger?.Information("VaultProvider: Set value in vault at path '{0}/{1}/{2}'", Mount, Path, variable);

var realValue = ConvertFrom(value, typeof(T));

_cachedValues[variable] = realValue;
_CachedValues[variable] = realValue;

ApplyCurrent();
}
Expand All @@ -100,7 +117,7 @@ public void ApplyCurrent()
// Build the current from the getters.
var values = GetLatestValues();

_logger?.Debug("VaultProvider: Writing secret '{0}/{1}' to Vault!", Mount, Path);
_logger?.Information("VaultProvider: Writing secret '{0}/{1}' to Vault!", Mount, Path);

_client?.V1.Secrets.KeyValue.V2.WriteSecretAsync(
mountPoint: Mount,
Expand Down Expand Up @@ -128,7 +145,7 @@ private Dictionary<string, object> GetLatestValues()

try
{
_logger?.Debug("VaultProvider: Fetching initial value for {0}.{1}", GetType().Name, getterName);
_logger?.Verbose("VaultProvider: Fetching initial value for {0}.{1}", GetType().Name, getterName);

var value = getter.GetGetMethod().Invoke(this, Array.Empty<object>());
var realValue = value?.ToString() ?? string.Empty;
Expand All @@ -140,7 +157,7 @@ private Dictionary<string, object> GetLatestValues()
}
catch (TargetInvocationException ex)
{
_logger?.Debug("VaultProvider: Error occurred when fetching getter for '{0}.{1}': {2}", GetType().Name, getterName, ex.InnerException.Message);
_logger?.Verbose("VaultProvider: Error occurred when fetching getter for '{0}.{1}': {2}", GetType().Name, getterName, ex.InnerException.Message);

newCachedValues.Add(getterName, string.Empty);
}
Expand All @@ -153,45 +170,64 @@ private Dictionary<string, object> GetLatestValues()
/// Construct a new instance of <see cref="VaultProvider"/>
/// </summary>
/// <param name="logger">The <see cref="ILogger"/></param>
protected VaultProvider(ILogger logger = null)
/// <param name="periodicRefresh">Should this periodically refresh?</param>
protected VaultProvider(ILogger logger = null, bool periodicRefresh = true)
{
logger ??= Logger.Singleton;

SetLogger(logger);

_logger?.Debug("VaultProvider: Setup for '{0}/{1}' to refresh every '{2}' interval!", Mount, Path, RefreshInterval);

if (_providers.Contains(this))
if (periodicRefresh)
{
_logger?.Debug("VaultProvider: Skipping setup for '{0}/{1}' because it is already setup!", Mount, Path);
_logger?.Debug("VaultProvider: Setup for '{0}/{1}' to refresh every '{2}' interval!", Mount, Path, RefreshInterval);

return;
}
if (_providers.TryGetValue(GetType().ToString(), out _))
{
_logger?.Debug("VaultProvider: Skipping setup for '{0}/{1}' because it is already setup!", Mount, Path);

return;
}

_providers.Add(this);
_providers.TryAdd(GetType().ToString(), this);
}

DoRefresh();
}

/// <summary>
/// Construct a new instance of <see cref="VaultProvider"/>
/// </summary>
/// <param name="mount">The <see cref="Mount"/></param>
/// <param name="path">The <see cref="Path"/></param>
/// <param name="logger">The <see cref="ILogger"/></param>
/// <param name="periodicRefresh">Should this periodically refresh?</param>
protected VaultProvider(string mount, string path = "", ILogger logger = null, bool periodicRefresh = true)
: this(logger, periodicRefresh)
{
Mount = mount;
Path = path;
}

private static void RefreshThread()
{
while (true)
{
var providers = _providers.ToArray();

foreach (var provider in providers)
foreach (var kvp in providers)
{
try
{
provider.DoRefresh();
kvp.Value.DoRefresh();
}
catch (Exception ex)
{
_staticLogger?.Error(ex);
}
}

Thread.Sleep(RefreshInterval); // SetClient makes DoRefresh call.
_refreshRequestEvent.WaitOne(RefreshInterval); // SetClient makes DoRefresh call.
_refreshRequestEvent.Reset();
}
}

Expand All @@ -211,8 +247,8 @@ private void DoRefresh()
var values = secret.Data.Data;
InvokePropertyChangedForChangedValues(values);

lock (_cachedValues)
_cachedValues = values;
lock (_CachedValues)
_CachedValues = values;
}
catch (VaultApiException ex)
{
Expand Down Expand Up @@ -245,7 +281,6 @@ private bool HasProperty(ref string name)
if (property == null)
{
_logger?.Debug("VaultProvider: Skipping property changed handler for '{0}' because settings provider '{1}' does not define it!", name, this);
_logger?.Warning("{0}: Unknown property '{1}', make sure it is defined in the settings provider or has a appropriate [{2}] attribute!", GetType().Name, name, nameof(SettingNameAttribute));

return false;
}
Expand All @@ -258,7 +293,7 @@ private bool HasProperty(ref string name)

private void InvokePropertyChangedForChangedValues(IDictionary<string, object> newValues)
{
if (_cachedValues.Count == 0)
if (_CachedValues.Count == 0)
{
foreach (var kvp in newValues)
{
Expand All @@ -272,7 +307,7 @@ private void InvokePropertyChangedForChangedValues(IDictionary<string, object> n
var propertyName = kvp.Key;
if (!HasProperty(ref propertyName)) continue;

_logger?.Debug("VaultProvider: Invoking property changed handler for '{0}'", propertyName);
_logger?.Verbose("VaultProvider: Invoking property changed handler for '{0}'", propertyName);

PropertyChanged?.Invoke(this, new(propertyName));
}
Expand All @@ -282,7 +317,7 @@ private void InvokePropertyChangedForChangedValues(IDictionary<string, object> n

foreach (var kvp in newValues)
{
if (_cachedValues.TryGetValue(kvp.Key, out var value))
if (_CachedValues.TryGetValue(kvp.Key, out var value))
if (value.ToString().Equals(kvp.Value.ToString())) continue;

if (_propertyNamesIgnored.Contains(kvp.Key))
Expand All @@ -309,8 +344,8 @@ protected override bool GetRawValue(string key, out string value)
{
object v;

lock (_cachedValues)
if (!_cachedValues.TryGetValue(key, out v))
lock (_CachedValues)
if (!_CachedValues.TryGetValue(key, out v))
return base.GetRawValue(key, out value);

if (v is JsonElement element)
Expand All @@ -320,4 +355,15 @@ protected override bool GetRawValue(string key, out string value)

return true;
}

/// <inheritdoc cref="IDisposable.Dispose"/>
public void Dispose()
{
if (_disposed) return;

GC.SuppressFinalize(this);

_providers.TryRemove(GetType().ToString(), out _);
_disposed = true;
}
}
4 changes: 2 additions & 2 deletions src/configuration/configuration/Interfaces/IVaultProvider.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace Configuration;

using VaultSharp;
using System;

/// <summary>
/// Represents a <see cref="IConfigurationProvider"/> backed by Vault.
/// </summary>
public interface IVaultProvider : IConfigurationProvider
public interface IVaultProvider : IConfigurationProvider, IDisposable
{
/// <summary>
/// Gets the mount path.
Expand Down
2 changes: 0 additions & 2 deletions src/configuration/core/Configuration.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Helpers for configuration in applications.</Description>

<Version>1.0.5</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/file-system/file-system/FileSystem.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>File system helpers and more!</Description>

<Version>1.0.5</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/floodcheckers/core/FloodCheckers.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Core library for flood checker implementation!</Description>

<Version>1.0.4</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/floodcheckers/redis/FloodCheckers.Redis.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Library for interacting with Redis based flood checkers!</Description>

<Version>1.0.5</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/grid/client/Grid.Client.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>SOAP library shared by grid-server processes!</Description>

<Version>1.0.4</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 0 additions & 2 deletions src/grid/commands/Grid.Commands.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Shared library containing models used by grid-servers for specific script execution actions.</Description>

<Version>1.0.4</Version>
</PropertyGroup>

<!-- Embed all Scripts/*.lua files into the assembly -->
Expand Down
2 changes: 0 additions & 2 deletions src/grid/port-management/Grid.PortManagement.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Shared library for port allocation on grid-servers!</Description>

<Version>1.0.5</Version>
<RootNamespace>Grid</RootNamespace>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Shared library for core interaction with grid server processes!</Description>

<Version>1.0.5</Version>
<RootNamespace>Grid</RootNamespace>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Shared library for interaction with Docker based grid server processes!</Description>

<Version>1.0.4</Version>
<RootNamespace>Grid</RootNamespace>
</PropertyGroup>

Expand Down
Loading

0 comments on commit eeca03d

Please sign in to comment.