Last active
August 29, 2015 14:22
-
-
Save jmcd/ca9188f35ff62036ca90 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Counter | |
{ | |
private readonly string directoryPath; | |
private int count = 0; | |
private static readonly object lockable = new object(); | |
public const string AggregatedCountFilename = "agg_count.txt"; | |
public const string PendingFileExt = "pnd"; | |
public Counter(string directoryPath) | |
{ | |
Action<string, object> log = (s, o) => Console.Error.WriteLine(Thread.CurrentThread.Name + " " + s, o); | |
lock (lockable) | |
{ | |
this.directoryPath = directoryPath; | |
Directory.CreateDirectory(directoryPath); | |
log("in {0}", directoryPath); | |
var aggregatedCount = this.ReadAggregatedCount(); | |
log("read {0}", aggregatedCount); | |
var pathsOfPendingFiles = this.GetPathsOfPendingFiles(); | |
log("read {0} paths of pending files", pathsOfPendingFiles.Length); | |
var newCount = aggregatedCount + pathsOfPendingFiles.Length; | |
this.ResetAggregatedCount(newCount); | |
log("reset addregated count to {0}", newCount); | |
log("starting deleted pending", ""); | |
DeleteFiles(pathsOfPendingFiles); | |
log("deleted pending", ""); | |
} | |
} | |
private static void DeleteFiles(string[] filePaths) | |
{ | |
foreach (var filePath in filePaths) | |
{ | |
try | |
{ | |
File.Delete(filePath); | |
} | |
catch (Exception ex) | |
{ | |
Console.Error.WriteLine(ex); | |
} | |
} | |
} | |
private string[] GetPathsOfPendingFiles() | |
{ | |
return Directory.GetFiles(this.directoryPath, string.Format("*.{0}", PendingFileExt)); | |
} | |
private int ReadAggregatedCount() | |
{ | |
var path = this.AggregatedCountFilePath(); | |
if (File.Exists(path)) | |
{ | |
var fileContent = File.ReadAllText(path).Trim(); | |
return int.Parse(fileContent); | |
} | |
return 0; | |
} | |
private void ResetAggregatedCount(int newCount) | |
{ | |
var path = this.AggregatedCountFilePath(); | |
var contents = newCount.ToString(CultureInfo.InvariantCulture); | |
File.WriteAllText(path, contents); | |
this.count = newCount; | |
} | |
private string AggregatedCountFilePath() | |
{ | |
return Path.Combine(this.directoryPath, AggregatedCountFilename); | |
} | |
public void Increment() | |
{ | |
// lock (lockable) | |
{ | |
//Interlocked.Increment(ref this.count); | |
this.count++; | |
var path = Path.Combine(this.directoryPath, string.Format("{0}.{1}", Guid.NewGuid(), PendingFileExt)); | |
var contents = this.CurrentValue().ToString(CultureInfo.InvariantCulture); | |
File.WriteAllText(path, contents); // just to have something to write, write the current value | |
} | |
} | |
public int CurrentValue() | |
{ | |
return this.count; | |
} | |
} | |
[TestFixture] | |
public class CounterTests | |
{ | |
private string roodDirPath; | |
[TestFixtureSetUp] | |
public void TestFixtureSetUp() | |
{ | |
this.roodDirPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | |
} | |
[TestFixtureTearDown] | |
public void TestFixtureTearDown() | |
{ | |
Directory.Delete(this.roodDirPath, true); | |
} | |
private string MakeDirectoryPath() | |
{ | |
return Path.Combine(this.roodDirPath, Path.GetRandomFileName()); | |
} | |
[Test] | |
public void CanCreateNewCounter() | |
{ | |
var directoryPath = this.MakeDirectoryPath(); | |
var counter = new Counter(directoryPath); | |
Assert.AreEqual(0, counter.CurrentValue()); | |
counter.Increment(); | |
Assert.AreEqual(1, counter.CurrentValue()); | |
} | |
[Test] | |
public void IncrementingCounterCausesPendingFiles() | |
{ | |
var directoryPath = this.MakeDirectoryPath(); | |
var counter = new Counter(directoryPath); | |
for (var i = 0; i < 1000; i++) | |
{ | |
counter.Increment(); | |
} | |
Assert.AreEqual(1000, GetPathsOfPendingFiles(directoryPath).Length); | |
} | |
[Test] | |
public void CreatingANewCounterAggregatesPendingFilesAndResetsTheAggregatedFile() | |
{ | |
var directoryPath = this.MakeDirectoryPath(); | |
var counter0 = new Counter(directoryPath); | |
for (var i = 0; i < 1000; i++) | |
{ | |
counter0.Increment(); | |
} | |
var counter1 = new Counter(directoryPath); | |
Assert.AreEqual(0, GetPathsOfPendingFiles(directoryPath).Length); | |
Assert.AreEqual(1000, int.Parse(File.ReadAllText(AggregatedCountFilePath(directoryPath)))); | |
} | |
[Test] | |
public void UseCaseOf1000SubmissionsADayAndWeeklyAppRestartsFor10Years() | |
{ | |
var directoryPath = this.MakeDirectoryPath(); | |
for (var month = 0; month < 10; month++) | |
{ | |
var counter = default(Counter); | |
for (var day = 0; day < 30; day++) | |
{ | |
if (day%7 == 0) | |
{ | |
counter = new Counter(directoryPath); | |
} | |
for (var i = 0; i < 1000; i++) | |
{ | |
counter.Increment(); | |
} | |
} | |
} | |
var finalCounter = new Counter(directoryPath); | |
Assert.AreEqual(6*30*1000, finalCounter.CurrentValue()); | |
Assert.AreEqual(0, GetPathsOfPendingFiles(directoryPath).Length); | |
Assert.AreEqual(6*30*1000, int.Parse(File.ReadAllText(AggregatedCountFilePath(directoryPath)))); | |
} | |
[Test] | |
public void Multiball() | |
{ | |
var directoryPath = this.MakeDirectoryPath(); | |
const int threadCount = 25; | |
const int numberOfIncrementsPerThread = 1000; | |
var threads = new Thread[threadCount]; | |
for (var threadIndex = 0; threadIndex < threadCount; threadIndex++) | |
{ | |
var t = new Thread(() => | |
{ | |
var counter = new Counter(directoryPath); | |
for (var i = 0; i < 1000; i++) | |
{ | |
counter.Increment(); | |
} | |
}); | |
t.Name = string.Format("T{0}", threadIndex); | |
threads[threadIndex] = t; | |
} | |
foreach (var thread in threads) | |
{ | |
thread.Start(); | |
} | |
foreach (var thread in threads) | |
{ | |
thread.Join(); | |
} | |
var finalCounter = new Counter(directoryPath); | |
Assert.AreEqual(numberOfIncrementsPerThread*threadCount, finalCounter.CurrentValue()); | |
} | |
private static string[] GetPathsOfPendingFiles(string directoryPath) | |
{ | |
return Directory.GetFiles(directoryPath, string.Format("*.{0}", Counter.PendingFileExt)); | |
} | |
private static string AggregatedCountFilePath(string directoryPath) | |
{ | |
return Path.Combine(directoryPath, Counter.AggregatedCountFilename); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Counter | |
{ | |
private static readonly object Lockable = new object(); | |
public const string AggregatedCountFilename = "count.txt"; | |
private readonly string countFilePath; | |
public Counter(string directoryPath) | |
{ | |
lock (Lockable) | |
{ | |
this.countFilePath = Path.Combine(directoryPath, AggregatedCountFilename); | |
Directory.CreateDirectory(directoryPath); | |
} | |
} | |
private int ReadCount() | |
{ | |
var path = this.countFilePath; | |
if (File.Exists(path)) | |
{ | |
var fileContent = File.ReadAllText(path).Trim(); | |
return int.Parse(fileContent); | |
} | |
return 0; | |
} | |
private void WriteCount(int i) | |
{ | |
var contents = i.ToString(CultureInfo.InvariantCulture); | |
File.WriteAllText(this.countFilePath, contents); | |
} | |
public void Increment() | |
{ | |
lock (Lockable) | |
{ | |
var i = this.ReadCount(); | |
i++; | |
this.WriteCount(i); | |
} | |
} | |
public int CurrentValue() | |
{ | |
lock (Lockable) | |
{ | |
return this.ReadCount(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment