Skip to content

Commit

Permalink
feat: core switchable parallel yggdrasil engine integration
Browse files Browse the repository at this point in the history
  • Loading branch information
daveleek committed Nov 30, 2023
1 parent 93df4f8 commit 58370d3
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/Unleash/Communication/FetchTogglesResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ internal class FetchTogglesResult
public ToggleCollection ToggleCollection { get; set; }
public bool HasChanged { get; set; }
public string Etag { get; set; }
public string ResponseContent { get; set; }
}
}
2 changes: 2 additions & 0 deletions src/Unleash/Communication/IUnleashApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Unleash.Metrics;
using EngineBucket = Yggdrasil.MetricsBucket;

namespace Unleash.Communication
{
Expand All @@ -9,5 +10,6 @@ internal interface IUnleashApiClient
Task<FetchTogglesResult> FetchToggles(string etag, CancellationToken cancellationToken);
Task<bool> RegisterClient(ClientRegistration registration, CancellationToken cancellationToken);
Task<bool> SendMetrics(ThreadSafeMetricsBucket metrics, CancellationToken cancellationToken);
Task<bool> SendEngineMetrics(EngineBucket metrics, CancellationToken cancellationToken);
}
}
58 changes: 54 additions & 4 deletions src/Unleash/Communication/UnleashApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Unicode;
using System.Threading;
using System.Threading.Tasks;
using Unleash.Events;
using Unleash.Internal;
using Unleash.Logging;
using Unleash.Metrics;
using Unleash.Serialization;
using EngineBucket = Yggdrasil.MetricsBucket;

namespace Unleash.Communication
{
Expand Down Expand Up @@ -90,7 +93,7 @@ public async Task<FetchTogglesResult> FetchToggles(string etag, CancellationToke
return await HandleErrorResponse(response, resourceUri);
}

return await HandleSuccessResponse(response, etag);
return await HandleSuccessResponse(response, etag, cancellationToken);
}
}
}
Expand Down Expand Up @@ -141,7 +144,7 @@ private void ConfigurationError(HttpResponseMessage response, string requestUri)
}
}

private async Task<FetchTogglesResult> HandleSuccessResponse(HttpResponseMessage response, string etag)
private async Task<FetchTogglesResult> HandleSuccessResponse(HttpResponseMessage response, string etag, CancellationToken cancellationToken)
{
featureRequestsToSkip = Math.Max(0, featureRequestsToSkip - 1);

Expand All @@ -156,7 +159,17 @@ private async Task<FetchTogglesResult> HandleSuccessResponse(HttpResponseMessage
};
}

var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);

if (string.IsNullOrEmpty(content))
{
return new FetchTogglesResult
{
HasChanged = false
};
}

var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
var toggleCollection = jsonSerializer.Deserialize<ToggleCollection>(stream);

if (toggleCollection == null)
Expand All @@ -172,7 +185,8 @@ private async Task<FetchTogglesResult> HandleSuccessResponse(HttpResponseMessage
{
HasChanged = true,
Etag = newEtag,
ToggleCollection = toggleCollection
ToggleCollection = toggleCollection,
ResponseContent = content
};
}

Expand Down Expand Up @@ -206,6 +220,42 @@ public async Task<bool> RegisterClient(ClientRegistration registration, Cancella
}
}

public async Task<bool> SendEngineMetrics(EngineBucket metrics, CancellationToken cancellationToken)
{
const string requestUri = "client/metrics";

var memoryStream = new MemoryStream();

jsonSerializer.Serialize(memoryStream, new
{
AppName = clientRequestHeaders.AppName,
InstanceId = clientRequestHeaders.InstanceTag,
Bucket = metrics
});

const int bufferSize = 1024 * 4;

using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
{
request.Content = new StreamContent(memoryStream, bufferSize);
request.Content.Headers.AddContentTypeJson();

SetRequestHeaders(request, clientRequestHeaders);

using (var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false))
{
if (response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.NotModified)
{
HandleMetricsSuccessResponse(response);
return true;
}

await HandleMetricsErrorResponse(response, requestUri);
return false;
}
}
}

public async Task<bool> SendMetrics(ThreadSafeMetricsBucket metrics, CancellationToken cancellationToken)
{
if (metricsRequestsToSkip > metricsRequestsSkipped)
Expand Down
67 changes: 53 additions & 14 deletions src/Unleash/DefaultUnleash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ public DefaultUnleash(UnleashSettings settings, params IStrategy[] strategies)
///// <param name="overrideDefaultStrategies">When true, it overrides the default strategies.</param>
///// <param name="strategies">Custom strategies.</param>
public DefaultUnleash(UnleashSettings settings, bool overrideDefaultStrategies, params IStrategy[] strategies)
: this(settings, overrideDefaultStrategies, strategies, yggdrasilStrategies: null)
{ }

///// <summary>
///// Initializes a new instance of Unleash client.
///// </summary>
///// <param name="config">Unleash settings</param>
///// <param name="overrideDefaultStrategies">When true, it overrides the default strategies.</param>
///// <param name="strategies">Custom strategies.</param>
///// <param name="yggdrasilStrategies">Yggdrasil custom strategies.</param>
public DefaultUnleash(UnleashSettings settings, bool overrideDefaultStrategies, IStrategy[] strategies, params Yggdrasil.IStrategy[] yggdrasilStrategies)
{
var currentInstanceNo = Interlocked.Increment(ref InitializedInstanceCount);

Expand All @@ -70,7 +81,7 @@ public DefaultUnleash(UnleashSettings settings, bool overrideDefaultStrategies,
strategies = SelectStrategies(strategies, overrideDefaultStrategies);
strategyMap = BuildStrategyMap(strategies);

services = new UnleashServices(settings, EventConfig, strategyMap);
services = new UnleashServices(settings, EventConfig, strategyMap, yggdrasilStrategies?.ToList());

Logger.Info($"UNLEASH: Unleash instance number { currentInstanceNo } is initialized and configured with: {settings}");

Expand Down Expand Up @@ -105,10 +116,23 @@ public bool IsEnabled(string toggleName, UnleashContext context)

public bool IsEnabled(string toggleName, UnleashContext context, bool defaultSetting)
{
var enabled = CheckIsEnabled(toggleName, context, defaultSetting).Enabled;
RegisterCount(toggleName, enabled);
if (settings.UseYggdrasil)
{
var enabled = services.YggdrasilEngine.IsEnabled(toggleName, context) ?? defaultSetting;
services.YggdrasilEngine.CountFeature(toggleName, enabled);
if (services.YggdrasilEngine.ShouldEmitImpressionEvent(toggleName))
{
EmitImpressionEvent("isEnabled", context, enabled, toggleName);
}

return enabled;
return enabled;
}
else
{
var enabled = CheckIsEnabled(toggleName, context, defaultSetting).Enabled;
RegisterCount(toggleName, enabled);
return enabled;
}
}

private FeatureEvaluationResult CheckIsEnabled(
Expand Down Expand Up @@ -247,22 +271,37 @@ public Variant GetVariant(string toggleName, UnleashContext context)

public Variant GetVariant(string toggleName, UnleashContext context, Variant defaultValue)
{
var toggle = GetToggle(toggleName);
if (settings.UseYggdrasil)
{
var engineVariant = services.YggdrasilEngine.GetVariant(toggleName, context);
var variant = engineVariant != null ? Variant.FromEngineVariant(engineVariant) : defaultValue;
services.YggdrasilEngine.CountVariant(toggleName, variant.Name);
context.ApplyStaticFields(settings);
if (services.YggdrasilEngine.ShouldEmitImpressionEvent(toggleName))
{
EmitImpressionEvent("getVariant", context, variant.IsEnabled, toggleName, variant.Name);
}

var evaluationResult = CheckIsEnabled(toggleName, context, false, defaultValue);
return variant;
}
else {
var toggle = GetToggle(toggleName);

RegisterCount(toggleName, evaluationResult.Enabled);
var evaluationResult = CheckIsEnabled(toggleName, context, false, defaultValue);

RegisterVariant(toggleName, evaluationResult.Variant);
RegisterCount(toggleName, evaluationResult.Enabled);

var enhancedContext = context.ApplyStaticFields(settings);
RegisterVariant(toggleName, evaluationResult.Variant);

if (toggle?.ImpressionData ?? false)
{
EmitImpressionEvent("getVariant", enhancedContext, evaluationResult.Enabled, toggle.Name, evaluationResult.Variant?.Name);
}
var enhancedContext = context.ApplyStaticFields(settings);

if (toggle?.ImpressionData ?? false)
{
EmitImpressionEvent("getVariant", enhancedContext, evaluationResult.Enabled, toggle.Name, evaluationResult.Variant?.Name);
}

return evaluationResult.Variant;
return evaluationResult.Variant;
}
}

public IEnumerable<VariantDefinition> GetVariants(string toggleName)
Expand Down
21 changes: 19 additions & 2 deletions src/Unleash/Internal/UnleashServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Unleash.Metrics;
using Unleash.Scheduling;
using Unleash.Strategies;
using YggdrasilEngine = Yggdrasil.YggdrasilEngine;

namespace Unleash
{
Expand All @@ -24,14 +25,26 @@ internal class UnleashServices : IDisposable
internal bool IsMetricsDisabled { get; }
internal ThreadSafeMetricsBucket MetricsBucket { get; }
internal FetchFeatureTogglesTask FetchFeatureTogglesTask { get; }
internal YggdrasilEngine YggdrasilEngine { get; }

public UnleashServices(UnleashSettings settings, EventCallbackConfig eventConfig, Dictionary<string, IStrategy> strategyMap)

public UnleashServices(
UnleashSettings settings,
EventCallbackConfig eventConfig,
Dictionary<string, IStrategy> strategyMap,
List<Yggdrasil.IStrategy> strategies = null)
{
if (settings.FileSystem == null)
{
settings.FileSystem = new FileSystem(settings.Encoding);
}

if (settings.UseYggdrasil)
{
YggdrasilEngine = new YggdrasilEngine(strategies);
}


var backupFile = settings.GetFeatureToggleFilePath();
var etagBackupFile = settings.GetFeatureToggleETagFilePath();

Expand Down Expand Up @@ -84,6 +97,7 @@ public UnleashServices(UnleashSettings settings, EventCallbackConfig eventConfig
settings.JsonSerializer,
settings.FileSystem,
eventConfig,
YggdrasilEngine,
backupFile,
etagBackupFile)
{
Expand Down Expand Up @@ -113,7 +127,8 @@ public UnleashServices(UnleashSettings settings, EventCallbackConfig eventConfig
var clientMetricsBackgroundTask = new ClientMetricsBackgroundTask(
apiClient,
settings,
MetricsBucket)
MetricsBucket,
YggdrasilEngine)
{
Interval = settings.SendMetricsInterval.Value
};
Expand All @@ -131,6 +146,8 @@ public void Dispose()
cancellationTokenSource.Cancel();
}

YggdrasilEngine?.Dispose();

scheduledTaskManager?.Dispose();
ToggleCollection?.Dispose();
}
Expand Down
9 changes: 9 additions & 0 deletions src/Unleash/Internal/Variant.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Unleash.Variants;
using YggdrasilVariant = Yggdrasil.Variant;

namespace Unleash.Internal
{
Expand All @@ -18,5 +19,13 @@ public Variant(string name, Payload payload, bool enabled, bool feature_enabled)
public Payload Payload { get; }
public bool IsEnabled { get; }
public bool FeatureEnabled { get; internal set; }

public static Variant FromEngineVariant(YggdrasilVariant variant)
{
if (variant == null)
return DISABLED_VARIANT;

return new Variant(variant.Name, new Payload(variant.Payload.PayloadType, variant.Payload.Value), variant.Enabled, variant.FeatureEnabled);
}
}
}
19 changes: 17 additions & 2 deletions src/Unleash/Scheduling/ClientMetricsBackgroundTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Unleash.Communication;
using Unleash.Logging;
using Unleash.Metrics;
using YggdrasilEngine = Yggdrasil.YggdrasilEngine;

namespace Unleash.Scheduling
{
Expand All @@ -15,23 +16,37 @@ internal class ClientMetricsBackgroundTask : IUnleashScheduledTask
private readonly IUnleashApiClient apiClient;
private readonly UnleashSettings settings;
private readonly ThreadSafeMetricsBucket metricsBucket;
private readonly YggdrasilEngine engine;

public ClientMetricsBackgroundTask(
IUnleashApiClient apiClient,
UnleashSettings settings,
ThreadSafeMetricsBucket metricsBucket)
ThreadSafeMetricsBucket metricsBucket,
YggdrasilEngine engine)
{
this.apiClient = apiClient;
this.settings = settings;
this.metricsBucket = metricsBucket;
this.engine = engine;
}

public async Task ExecuteAsync(CancellationToken cancellationToken)
{
if (settings.SendMetricsInterval == null)
return;

var result = await apiClient.SendMetrics(metricsBucket, cancellationToken).ConfigureAwait(false);
var result = false;

if (settings.UseYggdrasil)
{
var engineBucket = engine.GetMetrics();

result = await apiClient.SendEngineMetrics(engineBucket, cancellationToken).ConfigureAwait(false);
}
else
{
result = await apiClient.SendMetrics(metricsBucket, cancellationToken).ConfigureAwait(false);
}

// Ignore return value
if (!result)
Expand Down
Loading

0 comments on commit 58370d3

Please sign in to comment.