diff --git a/.paket/Paket.Restore.targets b/.paket/Paket.Restore.targets
index e7c1bc0c..52f41c60 100644
--- a/.paket/Paket.Restore.targets
+++ b/.paket/Paket.Restore.targets
@@ -11,23 +11,49 @@
$(MSBuildThisFileDirectory)..\
$(PaketRootPath)paket-files\paket.restore.cached
$(PaketRootPath)paket.lock
+ classic
+ proj
+ assembly
+ native
/Library/Frameworks/Mono.framework/Commands/mono
mono
-
- $(PaketRootPath)paket.exe
- $(PaketToolsPath)paket.exe
- "$(PaketExePath)"
- $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
-
+
+ $(PaketRootPath)paket.bootstrapper.exe
+ $(PaketToolsPath)paket.bootstrapper.exe
+ $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\
+
+
+
+
+ $(PaketRootPath)paket.exe
+ $(PaketToolsPath)paket.exe
+ $(PaketToolsPath)paket.exe
+ $(_PaketBootStrapperExeDir)paket.exe
+ paket.exe
+
+
+ $(PaketRootPath)paket
+ $(PaketToolsPath)paket
+ $(PaketToolsPath)paket
+
+
+ $(PaketRootPath)paket.exe
+ $(PaketToolsPath)paket.exe
+
+
+ $(PaketBootStrapperExeDir)paket.exe
+
+
+ paket
+
+
<_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))
- dotnet "$(PaketExePath)"
+ dotnet "$(PaketExePath)"
+ $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
+ "$(PaketExePath)"
-
- "$(PaketExePath)"
- $(PaketRootPath)paket.bootstrapper.exe
- $(PaketToolsPath)paket.bootstrapper.exe
"$(PaketBootStrapperExePath)"
$(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"
@@ -36,30 +62,40 @@
true
true
+
+
+ True
-
+
+
+
+
+
true
- $(NoWarn);NU1603
+ $(NoWarn);NU1603;NU1604;NU1605;NU1608
- /usr/bin/shasum $(PaketRestoreCacheFile) | /usr/bin/awk '{ print $1 }'
- /usr/bin/shasum $(PaketLockFilePath) | /usr/bin/awk '{ print $1 }'
+ /usr/bin/shasum "$(PaketRestoreCacheFile)" | /usr/bin/awk '{ print $1 }'
+ /usr/bin/shasum "$(PaketLockFilePath)" | /usr/bin/awk '{ print $1 }'
-
+
-
+
+
+
+
$([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))
@@ -69,11 +105,26 @@
true
+
+
+ true
+
+
-
+
+
+
+
+
+
+
+
$(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).paket.references.cached
@@ -82,7 +133,9 @@
$(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
$(MSBuildProjectDirectory)\paket.references
- $(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).$(TargetFramework).paket.resolved
+
+ false
+ true
true
references-file-or-cache-not-found
@@ -101,32 +154,43 @@
-
+
true
- target-framework '$(TargetFramework)'
+ target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)
-
+
+
-
+
+ false
+ true
+
+
-
+
-
+
+ $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)
$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])
$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])
$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])
+ $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])
%(PaketReferencesFileLinesInfo.PackageVersion)
- All
+ All
+ runtime
+ runtime
+ true
+ true
@@ -158,19 +222,27 @@
false
+ $(MSBuildVersion)
+ 15.8.0
<_NuspecFilesNewLocation Include="$(BaseIntermediateOutputPath)$(Configuration)\*.nuspec"/>
+
+
$(MSBuildProjectDirectory)/$(MSBuildProjectFile)
true
- false
- true
+ false
+ true
+ false
+ true
+ false
+ true
$(BaseIntermediateOutputPath)$(Configuration)
$(BaseIntermediateOutputPath)
@@ -183,11 +255,54 @@
-
-
+
-
+
+
- > Monitors = new Dictionary>();
-
+ public ConcurrentDictionary> Monitors = new ConcurrentDictionary>();
public void Dispose()
{
}
-
public ComponentHealthMonitor Get(string component)
{
throw new NotImplementedException();
}
-
public Dictionary GetData(string component)
{
throw new NotImplementedException();
}
-
public ComponentHealthMonitor SetHealthFunction(string component, Func check, Func> healthData = null)
{
- Monitors[component] = check;
+ Monitors.AddOrUpdate(component, k => check, (k, v) => check);
return new ComponentHealthMonitor(component, check);
}
}
diff --git a/Gigya.Microdot.ServiceProxy/Caching/AsyncCache.cs b/Gigya.Microdot.ServiceProxy/Caching/AsyncCache.cs
index 7dcc81c7..65b97844 100644
--- a/Gigya.Microdot.ServiceProxy/Caching/AsyncCache.cs
+++ b/Gigya.Microdot.ServiceProxy/Caching/AsyncCache.cs
@@ -35,48 +35,74 @@
using Gigya.ServiceContract.HttpService;
using Metrics;
using System.Threading.Tasks.Dataflow;
+using Gigya.Microdot.SharedLogic.Collections;
+
+// ReSharper disable InconsistentlySynchronizedField // Stats/Metrics filed related
namespace Gigya.Microdot.ServiceProxy.Caching
{
-
public sealed class AsyncCache : IMemoryCacheManager, IServiceProvider, IDisposable
{
+ private class Statistics
+ {
+ private const double Mb = 1048576;
+ private MetricsContext Metrics { get; }
+ public MetricsContext Hits { get; }
+ public MetricsContext Misses { get; }
+ public MetricsContext JoinedTeam { get; }
+ public MetricsContext AwaitingResult { get; }
+ public MetricsContext Failed { get; }
+ public Counter ClearCache { get; }
+ public MetricsContext Items { get; }
+ public MetricsContext Revokes { get; }
+
+ public Statistics(AsyncCache subject, MetricsContext metrics)
+ {
+ // Gauges
+ Metrics = metrics;
+ Metrics.Gauge("Size", () => subject.LastCacheSizeBytes / Mb, Unit.MegaBytes);
+ Metrics.Gauge("Entries", () => subject.MemoryCache.GetCount(), Unit.Items);
+ Metrics.Gauge("SizeLimit", () => subject.MemoryCache.CacheMemoryLimit / Mb, Unit.MegaBytes);
+ Metrics.Gauge("RamUsageLimit", () => subject.MemoryCache.PhysicalMemoryLimit, Unit.Percent);
+
+ // Counters
+ AwaitingResult = Metrics.Context("AwaitingResult");
+ ClearCache = Metrics.Counter("ClearCache", Unit.Calls);
+
+ // Meters
+ Hits = Metrics.Context("Hits");
+ Misses = Metrics.Context("Misses");
+ JoinedTeam = Metrics.Context("JoinedTeam");
+ Failed = Metrics.Context("Failed");
+
+ Items = Metrics.Context("Items");
+ Revokes = Metrics.Context("Revoke");
+ }
+ }
private IDateTime DateTime { get; }
private Func GetRevokeConfig { get; }
private ILog Log { get; }
- private MemoryCache MemoryCache { get; set; }
+ private MemoryCache MemoryCache { get; set; } // , where cache key = hash(method name + params)
private long LastCacheSizeBytes { get; set; }
- private MetricsContext Metrics { get; }
-
- internal ConcurrentDictionary> RevokeKeyToCacheKeysIndex { get; set; } = new ConcurrentDictionary>();
-
- private MetricsContext Hits { get; set; }
- private MetricsContext Misses { get; set; }
- private MetricsContext JoinedTeam { get; set; }
- private MetricsContext AwaitingResult { get; set; }
- private MetricsContext Failed { get; set; }
- private Counter ClearCache { get; set; }
-
- private MetricsContext Items { get; set; }
- private MetricsContext Revokes { get; set; }
+ private Statistics Stats { get; }
+ private readonly CancellationTokenSource _cleanUpToken;
+ private readonly TimeBoundConcurrentQueue _revokesQueue = new TimeBoundConcurrentQueue();
+ internal ConcurrentDictionary RevokeKeyToCacheKeysIndex { get; set; } = new ConcurrentDictionary();
private IDisposable RevokeDisposable { get; }
- private const double MB = 1048576.0;
- private int _clearCount;
-
- public int RevokeKeysCount => RevokeKeyToCacheKeysIndex.Count;
+ private int _clearCount;
///
/// Not thread safe used for testing
///
- internal int CacheKeyCount => RevokeKeyToCacheKeysIndex.Sum(item => item.Value.Count);
-
+ internal int CacheKeyCount => RevokeKeyToCacheKeysIndex.Sum(item => item.Value.CacheKeysSet.Count);
public AsyncCache(ILog log, MetricsContext metrics, IDateTime dateTime, IRevokeListener revokeListener, Func getRevokeConfig)
{
DateTime = dateTime;
GetRevokeConfig = getRevokeConfig;
+ Stats = new Statistics(this, metrics);
Log = log;
if (ObjectCache.Host == null)
@@ -86,171 +112,160 @@ public AsyncCache(ILog log, MetricsContext metrics, IDateTime dateTime, IRevokeL
Clear();
- Metrics = metrics;
- InitMetrics();
- var onRevoke = new ActionBlock(OnRevoke);
- RevokeDisposable = revokeListener.RevokeSource.LinkTo(onRevoke);
+ RevokeDisposable = revokeListener.RevokeSource.LinkTo(new ActionBlock(OnRevoke));
+ // Clean up queue of revokes periodically
+ _cleanUpToken = new CancellationTokenSource();
+ Task.Run(OnMaintain).ContinueWith(_ => { try{_cleanUpToken.Dispose();}catch (Exception ){ /*ignore already disposed*/ }});
}
+
private Task OnRevoke(string revokeKey)
{
- var shouldLog = GetRevokeConfig().LogRevokes;
+ var logRevokes = GetRevokeConfig().LogRevokes;
if (string.IsNullOrEmpty(revokeKey))
{
- Log.Warn("Error while revoking cache, revokeKey can't be null");
+ Log.Warn("Error while revoking cache, revokeKey can't be null or empty");
return Task.FromResult(false);
}
try
{
- if (shouldLog)
- Log.Info(x=>x("Revoke request received", unencryptedTags: new {revokeKey}));
+ if (logRevokes)
+ Log.Info(x => x("Revoke request received", unencryptedTags: new {revokeKey}));
+
+ // Save before gaining control over reverse item
+ var now = DateTime.UtcNow;
- if (RevokeKeyToCacheKeysIndex.TryGetValue(revokeKey, out HashSet cacheKeys))
+ // We need to handle race between Enqueue and factory/AlreadyRevoked
+ var rItem = RevokeKeyToCacheKeysIndex.GetOrAdd(revokeKey, k =>
{
- lock (cacheKeys)
- {
- var arrayOfCacheKeys = cacheKeys.ToArray();// To prevent iteration over modified collection.
- if (shouldLog && arrayOfCacheKeys.Length==0)
- Log.Info(x => x("There is no CacheKey to Revoke", unencryptedTags: new { revokeKey }));
+ _revokesQueue.Enqueue(now, revokeKey);
+ return new ReverseItem { WhenRevoked = now };
+ });
- foreach (var cacheKey in arrayOfCacheKeys)
- {
- if (shouldLog)
- Log.Info(x => x("Revoking cacheKey", unencryptedTags: new { revokeKey, cacheKey }));
+ rItem.WhenRevoked = now;
- var unused = (AsyncCacheItem)MemoryCache.Remove(cacheKey);
- }
- }
- Revokes.Meter("Succeeded", Unit.Events).Mark();
- }
- else
+ // Lock wide while processing ALL the keys, to compete with possible call (and insertion to cache) to be in consistent state
+ lock (rItem)
{
- if (shouldLog)
- Log.Info(x => x("Key is not cached. No revoke is needed", unencryptedTags: new { revokeKey }));
+ // We have to copy aside, else MemoryCache remove call back 100% modifying CacheKeysSet
+ var cacheKeys = rItem.CacheKeysSet.ToArray();
- Revokes.Meter("Discarded", Unit.Events).Mark();
- }
+ if (logRevokes && cacheKeys.Length == 0)
+ Log.Info(x => x("There is no CacheKey to Revoke", unencryptedTags: new { revokeKey }));
+
+ foreach (var cacheKey in cacheKeys)
+ {
+ if (logRevokes)
+ Log.Info(x => x("Revoking cacheKey", unencryptedTags: new { revokeKey, cacheKey }));
+
+ MemoryCache.Remove(cacheKey);
+ }
+ }
+ Stats.Revokes.Meter("Succeeded", Unit.Events).Mark();
}
catch (Exception ex)
{
- Revokes.Meter("Failed", Unit.Events).Mark();
+ Stats.Revokes.Meter("Failed", Unit.Events).Mark();
Log.Warn("Error while revoking cache", exception: ex, unencryptedTags: new {revokeKey});
}
return Task.FromResult(true);
}
-
- private void InitMetrics()
+ ///
+ /// Cleanup revoke keys that are has no associated cache keys and older/equal than interval.
+ ///
+ private async Task OnMaintain()
{
- // Gauges
- Metrics.Gauge("Size", () => LastCacheSizeBytes / MB, Unit.MegaBytes);
- Metrics.Gauge("Entries", () => MemoryCache.GetCount(), Unit.Items);
- Metrics.Gauge("SizeLimit", () => MemoryCache.CacheMemoryLimit / MB, Unit.MegaBytes);
- Metrics.Gauge("RamUsageLimit", () => MemoryCache.PhysicalMemoryLimit, Unit.Percent);
-
- // Counters
- AwaitingResult = Metrics.Context("AwaitingResult");
- ClearCache = Metrics.Counter("ClearCache", Unit.Calls);
-
- // Meters
- Hits = Metrics.Context("Hits");
- Misses = Metrics.Context("Misses");
- JoinedTeam = Metrics.Context("JoinedTeam");
- Failed = Metrics.Context("Failed");
-
- Items = Metrics.Context("Items");
- Revokes = Metrics.Context("Revoke");
+ try
+ {
+ while (!_cleanUpToken.Token.IsCancellationRequested)
+ {
+ var periodMs = GetRevokeConfig().RevokesCleanupMs;
+ var revokeKeys = _revokesQueue.Dequeue(DateTime.UtcNow.AddMilliseconds(-1 * periodMs));
+
+ foreach (var revokeKey in revokeKeys)
+ if (RevokeKeyToCacheKeysIndex.TryGetValue(revokeKey.Data, out var reverseItem))
+ if (!reverseItem.CacheKeysSet.Any())
+ // We compete with possible call, adding the value to cache, exactly when dequeue-ing values
+ lock (reverseItem.CacheKeysSet)
+ if (!reverseItem.CacheKeysSet.Any())
+ RevokeKeyToCacheKeysIndex.TryRemove(revokeKey.Data, out _);
+
+ await Task.Delay(periodMs, _cleanUpToken.Token).ConfigureAwait(false);
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ }
}
-
- public Task GetOrAdd(string key, Func factory, Type taskResultType, CacheItemPolicyEx policy, string groupName, string logData, string[] metricsKeys)
+ public Task GetOrAdd(string cacheKey, Func factory, Type taskResultType, CacheItemPolicyEx policy, string cacheGroup, string cacheData, string[] metricsKeys)
{
- var getValueTask = GetOrAdd(key, () => TaskConverter.ToWeaklyTypedTask(factory(), taskResultType), policy, groupName, logData, metricsKeys, taskResultType);
+ var getValueTask = GetOrAdd(cacheKey, () => TaskConverter.ToWeaklyTypedTask(factory(), taskResultType), policy, cacheGroup, cacheData, metricsKeys, taskResultType);
return TaskConverter.ToStronglyTypedTask(getValueTask, taskResultType);
}
-
- private Task