Skip to content
This repository was archived by the owner on Oct 28, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion IntegrationTests/IntegrationTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@
<Compile Include="Azure.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="File.cs" />
<Compile Include="ITestScope.cs" />
<Compile Include="OptimisticFile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scenarios.cs" />
</ItemGroup>
Expand Down
97 changes: 49 additions & 48 deletions IntegrationTests/File.cs → IntegrationTests/OptimisticFile.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@
using System;
using System.IO;
using NUnit.Framework;
using SnowMaker;

namespace IntegrationTests.cs
{
[TestFixture]
public class File : Scenarios<File.TestScope>
{
protected override TestScope BuildTestScope()
{
return new TestScope();
}

protected override IOptimisticDataStore BuildStore(TestScope scope)
{
return new DebugOnlyFileDataStore(scope.DirectoryPath);
}

public class TestScope : ITestScope
{
public TestScope()
{
var ticks = DateTime.UtcNow.Ticks;
IdScopeName = string.Format("snowmakertest{0}", ticks);

DirectoryPath = Path.Combine(Path.GetTempPath(), IdScopeName);
Directory.CreateDirectory(DirectoryPath);
}

public string IdScopeName { get; private set; }
public string DirectoryPath { get; private set; }

public string ReadCurrentPersistedValue()
{
var filePath = Path.Combine(DirectoryPath, string.Format("{0}.txt", IdScopeName));
return System.IO.File.ReadAllText(filePath);
}

public void Dispose()
{
if (Directory.Exists(DirectoryPath))
Directory.Delete(DirectoryPath, true);
}
}
}
}
using System;
using System.IO;
using NUnit.Framework;
using SnowMaker;

namespace IntegrationTests.cs
{
[TestFixture]
public class OptimisticFile : Scenarios<OptimisticFile.TestScope>
{
protected override TestScope BuildTestScope()
{
return new TestScope();
}

protected override IOptimisticDataStore BuildStore(TestScope scope)
{
return new FileOptimisticDataStore(scope.DirectoryPath);
}

public class TestScope : ITestScope
{
public TestScope()
{
var ticks = DateTime.UtcNow.Ticks;
IdScopeName = string.Format("snowmakertest{0}", ticks);

DirectoryPath = Path.Combine(Path.GetTempPath(), IdScopeName);
Directory.CreateDirectory(DirectoryPath);
}

public string IdScopeName { get; private set; }
public string DirectoryPath { get; private set; }

public string ReadCurrentPersistedValue()
{
var filePath = Path.Combine(DirectoryPath, string.Format("{0}.txt", IdScopeName));
using (TextReader reader = new StreamReader(filePath, System.Text.Encoding.Default))
return reader.ReadToEnd();
}

public void Dispose()
{
if (Directory.Exists(DirectoryPath))
Directory.Delete(DirectoryPath, true);
}
}
}
}
27 changes: 15 additions & 12 deletions SnowMaker/BlobOptimisticDataStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System.IO;
using System;

namespace SnowMaker
{
Expand All @@ -24,35 +25,37 @@ public BlobOptimisticDataStore(CloudStorageAccount account, string containerName

blobReferences = new Dictionary<string, ICloudBlob>();
}

public string GetData(string blockName)
public long GetNextBatch(string blockName, int batchSize)
{
if (batchSize <= 0)
throw new ArgumentOutOfRangeException("batchSize");

long id;
var blobReference = GetBlobReference(blockName);
using (var stream = new MemoryStream())
{
blobReference.DownloadToStream(stream);
return Encoding.UTF8.GetString(stream.ToArray());
if (!Int64.TryParse(Encoding.UTF8.GetString(stream.ToArray()), out id))
throw new Exception(String.Format("The id seed returned from the blob for blockName '{0}' was corrupt, and could not be parsed as a long. The data returned was: {1}", blockName, Encoding.UTF8.GetString(stream.ToArray())));
if (id <= 0)
throw new Exception(String.Format("The id seed returned from the blob for blockName '{0}' was {1}", blockName, id));
}
}

public bool TryOptimisticWrite(string scopeName, string data)
{
var blobReference = GetBlobReference(scopeName);
try
{
UploadText(
blobReference,
data,
(id + batchSize).ToString(),
AccessCondition.GenerateIfMatchCondition(blobReference.Properties.ETag));
}
catch (StorageException exc)
{
if (exc.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
return false;

throw;
return -1;
}
return true;

return id;
}

ICloudBlob GetBlobReference(string blockName)
Expand Down
41 changes: 0 additions & 41 deletions SnowMaker/DebugOnlyFileDataStore.cs

This file was deleted.

72 changes: 72 additions & 0 deletions SnowMaker/FileOptimisticDataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.IO;
using System.Text;

namespace SnowMaker
{
public class FileOptimisticDataStore : IOptimisticDataStore
{
public static readonly Encoding Encoding = Encoding.Default;
public const long SeedValue = 1;

readonly string directoryPath;

public FileOptimisticDataStore(string directoryPath)
{
this.directoryPath = directoryPath;
}

public long GetNextBatch(string blockName, int batchSize)
{
if (batchSize <= 0)
throw new ArgumentOutOfRangeException("batchSize");

var blockPath = Path.Combine(directoryPath, string.Format("{0}.txt", blockName));

try
{
using (FileStream stream = File.Open(blockPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
if (stream.Length == 0)
{
// a new file was created
using (StreamWriter writer = new StreamWriter(stream, Encoding))
writer.Write((SeedValue + batchSize).ToString());
return SeedValue;
}

// read the next available id
// can't use StreamReader to read here bc it would call Dispose on the provided stream object when StreamReader is disposed
StringBuilder str = new StringBuilder();
byte[] buffer = new byte[128];
int offset = 0, length;
do
{
length = stream.Read(buffer, offset, buffer.Length);
str.Append(Encoding.GetString(buffer, 0, length));
offset += length;
}
while (stream.Position < stream.Length);

long id;
if (!Int64.TryParse(str.ToString(), out id))
throw new Exception(String.Format("The id seed returned from the file for blockName '{0}' was corrupt, and could not be parsed as a long. The data returned was: {1}", blockName, str.ToString()));
if (id <= 0)
throw new Exception(String.Format("The id seed returned from the file for blockName '{0}' was {1}", blockName, id));

// mark the next batch as taken
stream.Position = 0;
stream.SetLength(Encoding.GetByteCount((id + batchSize).ToString()));
using (StreamWriter writer = new StreamWriter(stream, Encoding))
writer.Write((id + batchSize).ToString());

return id;
}
}
catch (IOException)
{
return -1;
}
}
}
}
9 changes: 7 additions & 2 deletions SnowMaker/IOptimisticDataStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
{
public interface IOptimisticDataStore
{
string GetData(string blockName);
bool TryOptimisticWrite(string blockName, string data);
/// <summary>
/// Marks the next <paramref name="batchSize"/> ids starting at the next available id
/// </summary>
/// <param name="blockName"></param>
/// <param name="batchSize"></param>
/// <returns>The first available id in the given batch size, or -1 if the call was unable to lock the store for exclusive access</returns>
long GetNextBatch(string blockName, int batchSize);
}
}
2 changes: 1 addition & 1 deletion SnowMaker/SnowMaker.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="DictionaryExtensions.cs" />
<Compile Include="DebugOnlyFileDataStore.cs" />
<Compile Include="FileOptimisticDataStore.cs" />
<Compile Include="IOptimisticDataStore.cs">
<SubType>Code</SubType>
</Compile>
Expand Down
21 changes: 6 additions & 15 deletions SnowMaker/UniqueIdGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,13 @@ void UpdateFromSyncStore(string scopeName, ScopeState state)

while (writesAttempted < maxWriteAttempts)
{
var data = optimisticDataStore.GetData(scopeName);

long nextId;
if (!long.TryParse(data, out nextId))
throw new UniqueIdGenerationException(string.Format(
"The id seed returned from storage for scope '{0}' was corrupt, and could not be parsed as a long. The data returned was: {1}",
scopeName,
data));

state.LastId = nextId - 1;
state.HighestIdAvailableInBatch = nextId - 1 + batchSize;
var firstIdInNextBatch = state.HighestIdAvailableInBatch + 1;

if (optimisticDataStore.TryOptimisticWrite(scopeName, firstIdInNextBatch.ToString(CultureInfo.InvariantCulture)))
long id = optimisticDataStore.GetNextBatch(scopeName, batchSize);
if (id != -1)
{
state.LastId = id - 1;
state.HighestIdAvailableInBatch = id - 1 + batchSize;
return;

}
writesAttempted++;
}

Expand Down
Loading