Skip to content

Commit

Permalink
Refactor into one lock
Browse files Browse the repository at this point in the history
  • Loading branch information
mgoodfellow committed Aug 20, 2021
1 parent 3d961e3 commit 3fd404b
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 359 deletions.
139 changes: 0 additions & 139 deletions src/CacheTower.Extensions.Redis/RedisBusyLockExtension.cs

This file was deleted.

69 changes: 0 additions & 69 deletions src/CacheTower.Extensions.Redis/RedisBusyLockOptions.cs

This file was deleted.

31 changes: 31 additions & 0 deletions src/CacheTower.Extensions.Redis/RedisLockExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,38 @@ public async ValueTask<CacheEntry<T>> WithRefreshAsync<T>(string cacheKey, Func<
var tcs = new TaskCompletionSource<bool>();
var cts = new CancellationTokenSource(Options.LockTimeout);
cts.Token.Register(tcs => ((TaskCompletionSource<bool>)tcs).TrySetCanceled(), tcs, useSynchronizationContext: false);

if (Options.UseBusyLockCheck)
{
_ = TestLock(tcs);
}

return tcs;

async Task TestLock(TaskCompletionSource<bool> taskCompletionSource)
{
var spinAttempt = 0;

while (spinAttempt <= Options.SpinAttempts &&
!taskCompletionSource.Task.IsCanceled &&
!taskCompletionSource.Task.IsCompleted)
{
spinAttempt++;

var lockQuery = await Database.LockQueryAsync(lockKey);

if (lockQuery.HasValue)
{
await Task.Delay(Options.SpinTime);
continue;
}

taskCompletionSource.TrySetResult(true);
return;
}

taskCompletionSource.TrySetCanceled();
}
});

//Last minute check to confirm whether waiting is required (in case the notification is missed)
Expand Down
30 changes: 29 additions & 1 deletion src/CacheTower.Extensions.Redis/RedisLockOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class RedisLockOptions
/// - <see cref="RedisChannel"/>: <c>"CacheTower.CacheLock"</c><br/>
/// - <see cref="KeyFormat"/>: <c>"Lock:{0}"</c><br/>
/// - <see cref="DatabaseIndex"/>: <i>The default database configured on the connection.</i>
/// - <see cref="SpinTime"/>: <i>Unused.</i>
/// </para>
/// </remarks>
public static readonly RedisLockOptions Default = new RedisLockOptions();
Expand All @@ -25,20 +26,39 @@ public class RedisLockOptions
/// If not specified, uses the default database as configured on the connection.
/// </summary>
public int DatabaseIndex { get; }

/// <summary>
/// The Redis channel to communicate unlocking events across.
/// </summary>
public string RedisChannel { get; }

/// <summary>
/// How long to wait on the lock before having it expire.
/// </summary>
public TimeSpan LockTimeout { get; }

/// <summary>
/// A <see cref="string.Format(string, object[])"/> compatible string used to create the lock key stored in Redis.
/// The cache key is provided as argument {0}.
/// </summary>
public string KeyFormat { get; }

/// <summary>
/// Is the busy lock check enabled
/// </summary>
public bool UseBusyLockCheck { get; }

/// <summary>
/// How open to recheck the lock when we were not the context that acquired the lock
/// </summary>
public TimeSpan SpinTime { get; }

/// <summary>
/// Number of attempts to check the lock before giving up
/// </summary>
public int SpinAttempts { get; }


/// <summary>
/// Creates a new instance of the <see cref="RedisLockOptions"/>.
/// </summary>
Expand All @@ -53,17 +73,25 @@ public class RedisLockOptions
/// The database index used for the Redis lock.
/// If not specified, uses the default database as configured on the connection.
/// </param>
/// <param name="spinTime">
/// The waiter on the lock also performs a tight loop to check for lock release
/// This can avoid the situation of a missed message on redis pub/sub
/// </param>
public RedisLockOptions(
TimeSpan? lockTimeout = default,
string redisChannel = "CacheTower.CacheLock",
string keyFormat = "Lock:{0}",
int databaseIndex = -1
int databaseIndex = -1,
TimeSpan? spinTime = default
)
{
LockTimeout = lockTimeout ?? TimeSpan.FromMinutes(1);
RedisChannel = redisChannel ?? throw new ArgumentNullException(nameof(redisChannel));
KeyFormat = keyFormat ?? throw new ArgumentNullException(nameof(keyFormat));
DatabaseIndex = databaseIndex;
UseBusyLockCheck = spinTime.HasValue;
SpinTime = spinTime ?? TimeSpan.FromMilliseconds(100);
SpinAttempts = (int)Math.Ceiling(LockTimeout.TotalMilliseconds / SpinTime.TotalMilliseconds);
}
}
}
Loading

0 comments on commit 3fd404b

Please sign in to comment.