Skip to content
This repository has been archived by the owner on Aug 3, 2024. It is now read-only.
/ ServerCommon Public archive

Commit

Permalink
Address feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
loic-sharma committed Jul 6, 2018
1 parent c4198a4 commit 62fe9bc
Show file tree
Hide file tree
Showing 13 changed files with 613 additions and 207 deletions.
128 changes: 128 additions & 0 deletions src/NuGet.Services.KillSwitch/HttpKillswitchClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace NuGet.Services.KillSwitch
{
/// <summary>
/// A killswitch service that queries an HTTP service periodically for the list of active killswitches.
/// </summary>
public class HttpKillswitchClient : IKillswitchClient
{
private readonly HttpClient _httpClient;
private readonly HttpKillswitchConfig _config;
private readonly ILogger<HttpKillswitchClient> _logger;

private int _started;
private KillswitchCache _cache;

public HttpKillswitchClient(
HttpClient httpClient,
HttpKillswitchConfig config,
ILogger<HttpKillswitchClient> logger)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));

_started = 0;
_cache = null;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

// Ensure that this client has not already been started.
if (Interlocked.CompareExchange(ref _started, 1, 0) != 0)
{
_logger.LogError("The killswitch client has already been started!");

throw new InvalidOperationException("The killswitch client has already been started!");
}

// Prime the killswitch cache.
await RefreshAsync(cancellationToken);

// Refresh the killswitch cache on a background thread periodically.
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
await Task.Delay(_config.RefreshInterval);
await RefreshAsync(cancellationToken);
}
catch (Exception e)
{
_logger.LogError(0, e, "Failed to refresh the killswitch cache due to exception");
}
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}

public async Task RefreshAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

_logger.LogInformation("Refreshing killswitches...");

var duration = Stopwatch.StartNew();
var response = await _httpClient.GetAsync(_config.KillswitchEndpoint, cancellationToken);
var content = await response.Content.ReadAsStringAsync();

var items = JsonConvert.DeserializeObject<List<string>>(content);

_cache = new KillswitchCache(items);

_logger.LogInformation("Refreshed killswitches after {ElapsedTime}", duration.Elapsed);
}

public bool IsActive(string name)
{
var cache = _cache;

if (cache == null)
{
_logger.LogError("Cannot check the killswitch {Name} as the client isn't started", name);

throw new InvalidOperationException("Cannot check the killswitch as the client isn't started");
}

if (cache.Staleness.Elapsed >= _config.MaximumStaleness)
{
_logger.LogError(
"Failed to determine whether the killswitch {Name} is active as the client's cache is {Staleness} stale",
name,
cache.Staleness.Elapsed);

throw new InvalidOperationException("Reached maximum killswitch staleness threshold");
}

return cache.Items.Contains(name);
}

private class KillswitchCache
{
public KillswitchCache(List<string> items)
{
Staleness = Stopwatch.StartNew();
Items = new HashSet<string>(items, StringComparer.OrdinalIgnoreCase);
}

public Stopwatch Staleness { get; }
public HashSet<string> Items { get; }
}
}
}
10 changes: 8 additions & 2 deletions src/NuGet.Services.KillSwitch/HttpKillswitchConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace NuGet.Services.KillSwitch
{
/// <summary>
/// The configuration for <see cref="HttpKillswitchService"/>.
/// The configuration for <see cref="HttpKillswitchClient"/>.
/// </summary>
public class HttpKillswitchConfig
{
Expand All @@ -18,6 +18,12 @@ public class HttpKillswitchConfig
/// <summary>
/// How frequently the endpoint should be queried.
/// </summary>
public TimeSpan RefreshInterval { get; set; }
public TimeSpan RefreshInterval { get; set; } = TimeSpan.FromMinutes(5);

/// <summary>
/// The maximum allowed staleness by <see cref="HttpKillswitchClient.IsActive(string)"/>.
/// The method will throw if this threshold is reached.
/// </summary>
public TimeSpan MaximumStaleness { get; set; } = TimeSpan.FromMinutes(20);
}
}
75 changes: 0 additions & 75 deletions src/NuGet.Services.KillSwitch/HttpKillswitchService.cs

This file was deleted.

39 changes: 39 additions & 0 deletions src/NuGet.Services.KillSwitch/IKillswitchClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;

namespace NuGet.Services.KillSwitch
{
/// <summary>
/// Use killswitches to dynamically disable features in your service.
/// </summary>
public interface IKillswitchClient
{
/// <summary>
/// Start tracking the killswitches. This method can be called only once per instance.
/// </summary>
/// <param name="cancellationToken">Token to stop tracking the killswitches.</param>
/// <returns>A task that completes after the killswitches have been loaded.</returns>
Task StartAsync(CancellationToken cancellationToken);

/// <summary>
/// Force a refresh of the list of activated killswitches. This can be called as frequently
/// as desired, regardless of whether <see cref="StartAsync(CancellationToken)"/> has been called.
/// </summary>
/// <param name="cancellationToken">Cancel the refresh operation.</param>
/// <returns>A task that completes when the known killswitches have been refreshed.</returns>
Task RefreshAsync(CancellationToken cancellationToken);

/// <summary>
/// Check whether the killswitch is known to be active. The list of known activated killswitches MUST be
/// loaded (through either <see cref="StartAsync(CancellationToken)"/> or <see cref="RefreshAsync"/>) prior
/// to calling this method. If you'd like to guarantee the result isn't stale, call <see cref="RefreshAsync"/>
/// prior to calling this method.
/// </summary>
/// <param name="name">The name of the killswitch.</param>
/// <returns>True if the killswitch is known to be active.</returns>
bool IsActive(string name);
}
}
26 changes: 0 additions & 26 deletions src/NuGet.Services.KillSwitch/IKillswitchService.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="IKillswitchService.cs" />
<Compile Include="IKillswitchClient.cs" />
<Compile Include="HttpKillswitchConfig.cs" />
<Compile Include="HttpKillswitchService.cs" />
<Compile Include="HttpKillswitchClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Loading

0 comments on commit 62fe9bc

Please sign in to comment.