Skip to content

Commit

Permalink
Merge pull request #69 from madelson/release-2.0.1
Browse files Browse the repository at this point in the history
Release 2.0.1
  • Loading branch information
madelson authored Feb 25, 2021
2 parents 1a7a81c + c7ea765 commit 5bd69af
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 308 deletions.
2 changes: 1 addition & 1 deletion DistributedLock.Redis/DistributedLock.Redis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</PropertyGroup>

<PropertyGroup>
<Version>1.0.0</Version>
<Version>1.0.1</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<Authors>Michael Adelson</Authors>
<Description>Provides distributed locking primitives based on Redis</Description>
Expand Down
10 changes: 6 additions & 4 deletions DistributedLock.Redis/Primitives/RedisScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ public RedisScript(string script, Func<TArgument, object> parameters)
this._parameters = parameters;
}

public RedisResult Execute(IDatabase database, TArgument argument, bool fireAndForget = false) =>
this._script.Evaluate(database, this._parameters(argument), flags: RedLockHelper.GetCommandFlags(fireAndForget));
public RedisResult Execute(IDatabase database, TArgument argument, bool fireAndForget = false) =>
// database.ScriptEvaluate must be called instead of _script.Evaluate in order to respect the database's key prefix
database.ScriptEvaluate(this._script, this._parameters(argument), flags: RedLockHelper.GetCommandFlags(fireAndForget));

public Task<RedisResult> ExecuteAsync(IDatabaseAsync database, TArgument argument, bool fireAndForget = false) =>
this._script.EvaluateAsync(database, this._parameters(argument), flags: RedLockHelper.GetCommandFlags(fireAndForget));
public Task<RedisResult> ExecuteAsync(IDatabaseAsync database, TArgument argument, bool fireAndForget = false) =>
// database.ScriptEvaluate must be called instead of _script.Evaluate in order to respect the database's key prefix
database.ScriptEvaluateAsync(this._script, this._parameters(argument), flags: RedLockHelper.GetCommandFlags(fireAndForget));

// send the smallest possible script to the server
private static string RemoveExtraneousWhitespace(string script) => Regex.Replace(script.Trim(), @"\s+", " ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void TestKeepaliveProtectsFromIdleSessionKillerAfterFailedUpgrade()
this._lockProvider.Strategy.KeepaliveCadence = TimeSpan.FromSeconds(.1);
var @lock = this._lockProvider.CreateUpgradeableReaderWriterLock(Guid.NewGuid().ToString());

using var idleSessionKiller = new IdleSessionKiller(this._lockProvider.Strategy.Db, applicationName, idleTimeout: TimeSpan.FromSeconds(.3));
using var idleSessionKiller = new IdleSessionKiller(this._lockProvider.Strategy.Db, applicationName, idleTimeout: TimeSpan.FromSeconds(.5));

using (@lock.AcquireReadLock())
{
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,61 +1,71 @@
using Medallion.Threading.Tests.Redis;
using NUnit.Framework;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Medallion.Threading.Tests.Redis
{
public abstract class TestingRedisDatabaseProvider
{
protected TestingRedisDatabaseProvider(IEnumerable<IDatabase> databases)
{
this.Databases = databases.ToArray();
}

protected TestingRedisDatabaseProvider(int count)
: this(Enumerable.Range(0, count).Select(i => RedisServer.GetDefaultServer(i).Multiplexer.GetDatabase()))
{
}

// publicly settable so that callers can alter the dbs in use
public IReadOnlyList<IDatabase> Databases { get; set; }

public virtual string CrossProcessLockTypeSuffix => this.Databases.Count.ToString();
}

public sealed class TestingRedisSingleDatabaseProvider : TestingRedisDatabaseProvider
{
public TestingRedisSingleDatabaseProvider() : base(count: 1) { }
}

public sealed class TestingRedis3DatabaseProvider : TestingRedisDatabaseProvider
{
public TestingRedis3DatabaseProvider() : base(count: 3) { }
}

public sealed class TestingRedis2x1DatabaseProvider : TestingRedisDatabaseProvider
{
private static readonly IDatabase DeadDatabase;

static TestingRedis2x1DatabaseProvider()
{
var server = new RedisServer(allowAdmin: true);
DeadDatabase = server.Multiplexer.GetDatabase();
using var process = Process.GetProcessById(server.ProcessId);
server.Multiplexer.GetServer($"localhost:{server.Port}").Shutdown(ShutdownMode.Never);
Assert.IsTrue(process.WaitForExit(5000));
}

public TestingRedis2x1DatabaseProvider()
: base(Enumerable.Range(0, 2).Select(i => RedisServer.GetDefaultServer(i).Multiplexer.GetDatabase()).Append(DeadDatabase))
{
}

public override string CrossProcessLockTypeSuffix => "2x1";
}
}
using Medallion.Threading.Tests.Redis;
using NUnit.Framework;
using StackExchange.Redis;
using StackExchange.Redis.KeyspaceIsolation;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Medallion.Threading.Tests.Redis
{
public abstract class TestingRedisDatabaseProvider
{
protected TestingRedisDatabaseProvider(IEnumerable<IDatabase> databases)
{
this.Databases = databases.ToArray();
}

protected TestingRedisDatabaseProvider(int count)
: this(Enumerable.Range(0, count).Select(i => RedisServer.GetDefaultServer(i).Multiplexer.GetDatabase()))
{
}

// publicly settable so that callers can alter the dbs in use
public IReadOnlyList<IDatabase> Databases { get; set; }

public virtual string CrossProcessLockTypeSuffix => this.Databases.Count.ToString();
}

public sealed class TestingRedisSingleDatabaseProvider : TestingRedisDatabaseProvider
{
public TestingRedisSingleDatabaseProvider() : base(count: 1) { }
}

public sealed class TestingRedisWithKeyPrefixSingleDatabaseProvider : TestingRedisDatabaseProvider
{
public TestingRedisWithKeyPrefixSingleDatabaseProvider()
: base(new[] { RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase().WithKeyPrefix("distributed_locks:") }) { }

public override string CrossProcessLockTypeSuffix => "1WithPrefix";
}

public sealed class TestingRedis3DatabaseProvider : TestingRedisDatabaseProvider
{
public TestingRedis3DatabaseProvider() : base(count: 3) { }
}

public sealed class TestingRedis2x1DatabaseProvider : TestingRedisDatabaseProvider
{
private static readonly IDatabase DeadDatabase;

static TestingRedis2x1DatabaseProvider()
{
var server = new RedisServer(allowAdmin: true);
DeadDatabase = server.Multiplexer.GetDatabase();
using var process = Process.GetProcessById(server.ProcessId);
server.Multiplexer.GetServer($"localhost:{server.Port}").Shutdown(ShutdownMode.Never);
Assert.IsTrue(process.WaitForExit(5000));
}

public TestingRedis2x1DatabaseProvider()
: base(Enumerable.Range(0, 2).Select(i => RedisServer.GetDefaultServer(i).Multiplexer.GetDatabase()).Append(DeadDatabase))
{
}

public override string CrossProcessLockTypeSuffix => "2x1";
}
}
Original file line number Diff line number Diff line change
@@ -1,90 +1,90 @@
using Medallion.Threading.Internal;
using Medallion.Threading.Redis;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Medallion.Threading.Tests.Redis
{
public sealed class TestingRedisSynchronizationStrategy<TDatabaseProvider> : TestingSynchronizationStrategy
where TDatabaseProvider : TestingRedisDatabaseProvider, new()
{
private bool _preparedForHandleLost, _preparedForHandleAbandonment;
private Action? _killHandleAction;
private Action<RedisDistributedSynchronizationOptionsBuilder>? _options;

public TDatabaseProvider DatabaseProvider { get; } = new TDatabaseProvider();

public void SetOptions(Action<RedisDistributedSynchronizationOptionsBuilder>? options)
{
this._options = options;
}

public void Options(RedisDistributedSynchronizationOptionsBuilder options)
{
if (this._preparedForHandleLost)
{
options.ExtensionCadence(TimeSpan.FromMilliseconds(30));
}
if (this._preparedForHandleAbandonment)
{
options.Expiry(TimeSpan.FromSeconds(.2))
// the reader writer lock requires that the busy wait sleep time is shorter
// than the expiry, so adjust for that
.BusyWaitSleepTime(TimeSpan.FromSeconds(.01), TimeSpan.FromSeconds(.1));
}

this._options?.Invoke(options);
}

public override IDisposable? PrepareForHandleLost()
{
Invariant.Require(!this._preparedForHandleLost);
this._preparedForHandleLost = true;
return new HandleLostScope(this);
}

public override void PrepareForHandleAbandonment() => this._preparedForHandleAbandonment = true;

public override void PerformAdditionalCleanupForHandleAbandonment()
{
Invariant.Require(this._preparedForHandleAbandonment);
Thread.Sleep(TimeSpan.FromSeconds(.5));
}

public void RegisterKillHandleAction(Action action)
{
if (this._preparedForHandleLost)
{
this._killHandleAction += action;
}
}

private class HandleLostScope : IDisposable
{
private TestingRedisSynchronizationStrategy<TDatabaseProvider>? _strategy;

public HandleLostScope(TestingRedisSynchronizationStrategy<TDatabaseProvider> strategy)
{
this._strategy = strategy;
}

public void Dispose()
{
var strategy = Interlocked.Exchange(ref this._strategy, null);
if (strategy != null)
{
Invariant.Require(strategy._preparedForHandleLost);
try { strategy._killHandleAction?.Invoke(); }
finally
{
strategy._killHandleAction = null;
strategy._preparedForHandleLost = false;
}
}
}
}
}
}
using Medallion.Threading.Internal;
using Medallion.Threading.Redis;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Medallion.Threading.Tests.Redis
{
public sealed class TestingRedisSynchronizationStrategy<TDatabaseProvider> : TestingSynchronizationStrategy
where TDatabaseProvider : TestingRedisDatabaseProvider, new()
{
private bool _preparedForHandleLost, _preparedForHandleAbandonment;
private Action? _killHandleAction;
private Action<RedisDistributedSynchronizationOptionsBuilder>? _options;

public TDatabaseProvider DatabaseProvider { get; } = new TDatabaseProvider();

public void SetOptions(Action<RedisDistributedSynchronizationOptionsBuilder>? options)
{
this._options = options;
}

public void Options(RedisDistributedSynchronizationOptionsBuilder options)
{
if (this._preparedForHandleLost)
{
options.ExtensionCadence(TimeSpan.FromMilliseconds(30));
}
if (this._preparedForHandleAbandonment)
{
options.Expiry(TimeSpan.FromSeconds(.2))
// the reader writer lock requires that the busy wait sleep time is shorter
// than the expiry, so adjust for that
.BusyWaitSleepTime(TimeSpan.FromSeconds(.01), TimeSpan.FromSeconds(.1));
}

this._options?.Invoke(options);
}

public override IDisposable? PrepareForHandleLost()
{
Invariant.Require(!this._preparedForHandleLost);
this._preparedForHandleLost = true;
return new HandleLostScope(this);
}

public override void PrepareForHandleAbandonment() => this._preparedForHandleAbandonment = true;

public override void PerformAdditionalCleanupForHandleAbandonment()
{
Invariant.Require(this._preparedForHandleAbandonment);
Thread.Sleep(TimeSpan.FromSeconds(.5));
}

public void RegisterKillHandleAction(Action action)
{
if (this._preparedForHandleLost)
{
this._killHandleAction += action;
}
}

private class HandleLostScope : IDisposable
{
private TestingRedisSynchronizationStrategy<TDatabaseProvider>? _strategy;

public HandleLostScope(TestingRedisSynchronizationStrategy<TDatabaseProvider> strategy)
{
this._strategy = strategy;
}

public void Dispose()
{
var strategy = Interlocked.Exchange(ref this._strategy, null);
if (strategy != null)
{
Invariant.Require(strategy._preparedForHandleLost);
try { strategy._killHandleAction?.Invoke(); }
finally
{
strategy._killHandleAction = null;
strategy._preparedForHandleLost = false;
}
}
}
}
}
}
15 changes: 15 additions & 0 deletions DistributedLock.Tests/Tests/ApiTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ public void TestLegacyGetSafeNameApisAreRemoved(AssemblyName assemblyName)
}
}

[TestCaseSource(nameof(DistributedLockAssemblies))]
public void TestAssemblyVersioning(AssemblyName assemblyName)
{
var assembly = Assembly.Load(assemblyName);
Assert.IsNotNull(assembly.GetName().GetPublicKeyToken(), "Should be signed");

// scheme based on https://codingforsmarties.wordpress.com/2016/01/21/how-to-version-assemblies-destined-for-nuget/
var version = assembly.GetName().Version;
version!.Minor.ShouldEqual(0);
version.Revision.ShouldEqual(0);
version.Build.ShouldEqual(0);
var informationalVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
Assert.That(informationalVersion?.InformationalVersion, Does.StartWith($"{version.Major}."));
}

private static IEnumerable<Type> GetPublicTypes(Assembly assembly) => assembly.GetTypes()
.Where(IsInPublicApi)
#if DEBUG
Expand Down
Loading

0 comments on commit 5bd69af

Please sign in to comment.