Skip to content

Commit

Permalink
Merge pull request #11 from Redth/feature-core-package
Browse files Browse the repository at this point in the history
Added File based Barrel implementation
  • Loading branch information
jamesmontemagno committed Dec 20, 2017
2 parents 48dead9 + 3604edf commit 00da36b
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 13 deletions.
220 changes: 207 additions & 13 deletions src/MonkeyCache.FileStore/Barrel.cs
@@ -1,66 +1,260 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Newtonsoft.Json;

namespace MonkeyCache.FileStore
{
public class Barrel : IBarrel
{
public static string ApplicationId { get; set; } = string.Empty;
ReaderWriterLockSlim indexLocker;

static Barrel instance = null;
JsonSerializerSettings jsonSettings;

Barrel()
{
indexLocker = new ReaderWriterLockSlim();

jsonSettings = new JsonSerializerSettings {
ObjectCreationHandling = ObjectCreationHandling.Replace,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.All,
};

index = new Dictionary<string, Tuple<string, DateTime>>();

LoadIndex();
}

public static string ApplicationId { get; set; } = string.Empty;

static Barrel instance = null;

/// <summary>
/// Gets the instance of the Barrel
/// </summary>
public static IBarrel Current => (instance ?? (instance = new Barrel()));

public void Add(string key, string data, TimeSpan expireIn, string eTag = null)
public void Add(string key, string data, TimeSpan expireIn, string eTag = null)
{
throw new NotImplementedException();
if (data == null)
return;

indexLocker.EnterWriteLock();

var hash = Hash(key);
var path = Path.Combine(baseDirectory.Value, hash);

File.WriteAllText(path, data);

index[key] = new Tuple<string, DateTime>(eTag ?? string.Empty, DateTime.UtcNow.Add(expireIn));

WriteIndex();

indexLocker.ExitWriteLock();
}

public void Add<T>(string key, T data, TimeSpan expireIn, string eTag = null)
{
throw new NotImplementedException();
var dataJson = JsonConvert.SerializeObject(data, jsonSettings);

Add(key, dataJson, expireIn, eTag);
}

public void Empty(params string[] key)
{
throw new NotImplementedException();
indexLocker.EnterWriteLock();

foreach (var k in key) {
File.Delete(Path.Combine(baseDirectory.Value, Hash(k)));
index.Remove(k);
}

WriteIndex();

indexLocker.ExitWriteLock();
}

public void EmptyAll()
{
throw new NotImplementedException();
indexLocker.EnterWriteLock();

foreach (var item in index) {
var hash = Hash(item.Key);
File.Delete(Path.Combine(baseDirectory.Value, hash));
}

index.Clear();

WriteIndex();

indexLocker.ExitWriteLock();
}

public void EmptyExpired()
{
throw new NotImplementedException();
indexLocker.EnterWriteLock();

var expired = index.Where(k => k.Value.Item2 < DateTime.UtcNow);

var toRem = new List<string>();

foreach (var item in expired) {
var hash = Hash(item.Key);
File.Delete(Path.Combine(baseDirectory.Value, hash));
toRem.Add(item.Key);
}

foreach (var key in toRem)
index.Remove(key);

WriteIndex();

indexLocker.ExitWriteLock();
}

public bool Exists(string key)
{
throw new NotImplementedException();
var exists = false;

indexLocker.EnterReadLock();

exists = index.ContainsKey(key);

indexLocker.ExitReadLock();

return exists;
}

public string Get(string key)
{
throw new NotImplementedException();
string result = null;

indexLocker.EnterReadLock();

var hash = Hash(key);
var path = Path.Combine(baseDirectory.Value, hash);

if (index.ContainsKey(key) && File.Exists(path))
result = File.ReadAllText(path);

indexLocker.ExitReadLock();

return result;
}

public T Get<T>(string key)
{
throw new NotImplementedException();
T result = default(T);

indexLocker.EnterReadLock();

var hash = Hash(key);
var path = Path.Combine(baseDirectory.Value, hash);

if (index.ContainsKey(key) && File.Exists(path)) {
var contents = File.ReadAllText(path);
result = JsonConvert.DeserializeObject<T>(contents, jsonSettings);
}

indexLocker.ExitReadLock();

return result;
}

public string GetETag(string key)
{
throw new NotImplementedException();
if (key == null)
return null;

string etag = null;

indexLocker.EnterReadLock();

if (index.ContainsKey(key))
etag = index[key]?.Item1;

indexLocker.ExitReadLock();

return etag;
}

public bool IsExpired(string key)
{
throw new NotImplementedException();
var expired = false;

indexLocker.EnterReadLock();

if (index.ContainsKey(key))
expired = index[key].Item2 < DateTime.UtcNow;

indexLocker.ExitReadLock();

return expired;
}

Lazy<string> baseDirectory = new Lazy<string>(() => {
return Path.Combine(Utils.GetBasePath(ApplicationId), "MonkeyCacheFS");
});

Dictionary<string, Tuple<string, DateTime>> index;

const string INDEX_FILENAME = "index.dat";

string indexFile;

void WriteIndex()
{
if (string.IsNullOrEmpty(indexFile))
indexFile = Path.Combine(baseDirectory.Value, INDEX_FILENAME);

if (!Directory.Exists(baseDirectory.Value))
Directory.CreateDirectory(baseDirectory.Value);

using (var f = File.Open(indexFile, FileMode.Create))
using (var sw = new StreamWriter(f)) {
foreach (var kvp in index)
sw.WriteLine($"{kvp.Key}\t{kvp.Value.Item1}\t{kvp.Value.Item2.ToString("o")}");
}
}

void LoadIndex()
{
if (string.IsNullOrEmpty(indexFile))
indexFile = Path.Combine(baseDirectory.Value, INDEX_FILENAME);

if (!File.Exists(indexFile))
return;

index.Clear();

using (var f = File.OpenRead(indexFile))
using (var sw = new StreamReader(f)) {
string line = null;
while ((line = sw.ReadLine()) != null) {
var parts = line.Split('\t');
if (parts.Length == 3) {
var key = parts[0];
var etag = parts[1];
var dt = parts[2];

DateTime date;
if (!string.IsNullOrEmpty(key) && DateTime.TryParse(dt, out date) && !index.ContainsKey(key))
index.Add(key, new Tuple<string, DateTime>(etag, date));
}
}
}
}

static string Hash(string input)
{
MD5 md5Hasher = MD5.Create();
byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
return BitConverter.ToString(data);
}
}
}
1 change: 1 addition & 0 deletions src/MonkeyCache.FileStore/MonkeyCache.FileStore.csproj
Expand Up @@ -41,6 +41,7 @@
<ItemGroup>
<PackageReference Condition=" '$(TargetFramework)' == 'uap10.0.16299' " Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="6.0.5" />
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.2-build.23" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down
91 changes: 91 additions & 0 deletions src/MonkeyCache.TestsShared/BarrelTests.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

Expand Down Expand Up @@ -290,6 +292,95 @@ public void DoesNotExistsTest()
Assert.IsFalse(barrel.Exists(url));
}

#endregion

#region Performance Tests
[TestMethod]
public void PerformanceTests()
{
PerformanceTestRunner(1, true, 1000);
}

[TestMethod]
public void PerformanceTestsMultiThreaded()
{
PerformanceTestRunner(4, false, 1000);
}

[TestMethod]
public void PerformanceTestsMultiThreadedWithDuplicates()
{
PerformanceTestRunner(4, true, 1000);
}

void PerformanceTestRunner (int threads, bool allowDuplicateKeys, int keysPerThread)
{
var tasks = new List<Task>();

var mainStopwatch = new Stopwatch();
mainStopwatch.Start();

for (int i = 0; i < threads; i++) {
var i2 = i;

var task = Task.Factory.StartNew(() => {
var tId = i2;
var keyModifier = allowDuplicateKeys ? string.Empty : tId.ToString();
var keys = Enumerable.Range(0, keysPerThread).Select(x => $"key-{keyModifier}-{x}").ToArray();
var stopwatch = new Stopwatch();
stopwatch.Start();
// Add a lot of items
foreach (var key in keys)
barrel.Add(key: key, data: monkeys, expireIn: TimeSpan.FromDays(1));
stopwatch.Stop();
Debug.WriteLine($"Add ({tId}) took {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
foreach (var key in keys) {
var content = barrel.Get(key);
}
stopwatch.Stop();
Debug.WriteLine($"Gets ({tId}) took {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
foreach (var key in keys) {
var content = barrel.GetETag(key);
}
stopwatch.Stop();
Debug.WriteLine($"Get ({tId}) eTags took {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
// Delete all
barrel.Empty(keys);
stopwatch.Stop();
Debug.WriteLine($"Empty ({tId}) took {stopwatch.ElapsedMilliseconds} ms");
Assert.IsTrue(stopwatch.ElapsedMilliseconds > 1);
});

task.ContinueWith(t => {
if (t.Exception?.InnerException != null)
Debug.WriteLine(t.Exception.InnerException);
Assert.IsNull(t.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
tasks.Add(task);
}

Task.WaitAll(tasks.ToArray());

mainStopwatch.Stop();
Debug.WriteLine($"Entire Test took {mainStopwatch.ElapsedMilliseconds} ms");
}

#endregion

[TestCleanup]
Expand Down

0 comments on commit 00da36b

Please sign in to comment.