Skip to content

Commit 92ceedf

Browse files
committed
Added a retry policy used for locking
1 parent 8afd725 commit 92ceedf

File tree

5 files changed

+171
-126
lines changed

5 files changed

+171
-126
lines changed

AsyncGenerator.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@
179179
hasAttributeName: ObsoleteAttribute
180180
- conversion: Smart
181181
containingTypeName: AbstractRegionStrategy
182+
- conversion: Smart
183+
containingTypeName: RedisKeyLocker
182184
callForwarding: true
183185
cancellationTokens:
184186
guards: true

StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,6 @@ public override async Task<int> UnlockManyAsync(object[] keys, string lockValue,
189189
/// <inheritdoc />
190190
public override async Task ClearAsync(CancellationToken cancellationToken)
191191
{
192-
cancellationToken.ThrowIfCancellationRequested();
193192
cancellationToken.ThrowIfCancellationRequested();
194193
var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(UpdateVersionLuaScript,
195194
_regionKeyArray, _maxVersionNumber)).ConfigureAwait(false);

StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs

Lines changed: 40 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -41,54 +41,37 @@ public Task<string> LockAsync(string key, string luaScript, RedisKey[] extraKeys
4141
{
4242
return Task.FromCanceled<string>(cancellationToken);
4343
}
44-
return InternalLockAsync();
45-
async Task<string> InternalLockAsync()
44+
var lockKey = $"{key}{_lockKeySuffix}";
45+
string Context() => lockKey;
46+
47+
return _retryPolicy.ExecuteAsync(async () =>
4648
{
47-
var lockKey = $"{key}{_lockKeySuffix}";
48-
var totalAttempts = 0;
49-
var lockTimer = new Stopwatch();
50-
lockTimer.Restart();
51-
do
49+
var lockValue = _lockValueProvider.GetValue();
50+
if (!string.IsNullOrEmpty(luaScript))
5251
{
53-
if (totalAttempts > 0)
52+
var keys = new RedisKey[] {lockKey};
53+
if (extraKeys != null)
5454
{
55-
var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay);
56-
await (Task.Delay(retryDelay, cancellationToken)).ConfigureAwait(false);
55+
keys = keys.Concat(extraKeys).ToArray();
5756
}
58-
var lockValue = _lockValueProvider.GetValue();
59-
if (!string.IsNullOrEmpty(luaScript))
57+
var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds};
58+
if (extraValues != null)
6059
{
61-
var keys = new RedisKey[] {lockKey};
62-
if (extraKeys != null)
63-
{
64-
keys = keys.Concat(extraKeys).ToArray();
65-
}
66-
var values = new RedisValue[] {lockValue, (long)_lockTimeout.TotalMilliseconds};
67-
if (extraValues != null)
68-
{
69-
values = values.Concat(extraValues).ToArray();
70-
}
71-
cancellationToken.ThrowIfCancellationRequested();
72-
var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false);
73-
if ((bool) result[0])
74-
{
75-
return lockValue;
76-
}
60+
values = values.Concat(extraValues).ToArray();
7761
}
78-
else if (await (_database.LockTakeAsync(lockKey, lockValue, _lockTimeout)).ConfigureAwait(false))
62+
var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false);
63+
if ((bool) result[0])
7964
{
8065
return lockValue;
8166
}
82-
totalAttempts++;
83-
84-
} while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout);
67+
}
68+
else if (await (_database.LockTakeAsync(lockKey, lockValue, _lockTimeout)).ConfigureAwait(false))
69+
{
70+
return lockValue;
71+
}
8572

86-
throw new CacheException("Unable to acquire cache lock: " +
87-
$"region='{_regionName}', " +
88-
$"key='{key}', " +
89-
$"total attempts='{totalAttempts}', " +
90-
$"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'");
91-
}
73+
return null; // retry
74+
}, Context, cancellationToken);
9275
}
9376

9477
/// <summary>
@@ -115,51 +98,33 @@ public Task<string> LockManyAsync(string[] keys, string luaScript, RedisKey[] ex
11598
{
11699
return Task.FromCanceled<string>(cancellationToken);
117100
}
118-
return InternalLockManyAsync();
119-
async Task<string> InternalLockManyAsync()
120-
{
101+
string Context() => string.Join(",", keys.Select(o => $"{o}{_lockKeySuffix}"));
121102

103+
return _retryPolicy.ExecuteAsync(async () =>
104+
{
122105
var lockKeys = new RedisKey[keys.Length];
123106
for (var i = 0; i < keys.Length; i++)
124107
{
125108
lockKeys[i] = $"{keys[i]}{_lockKeySuffix}";
126109
}
127-
var totalAttempts = 0;
128-
var lockTimer = new Stopwatch();
129-
lockTimer.Restart();
130-
do
110+
var lockValue = _lockValueProvider.GetValue();
111+
if (extraKeys != null)
131112
{
132-
if (totalAttempts > 0)
133-
{
134-
var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay);
135-
await (Task.Delay(retryDelay, cancellationToken)).ConfigureAwait(false);
136-
}
137-
var lockValue = _lockValueProvider.GetValue();
138-
if (extraKeys != null)
139-
{
140-
lockKeys = lockKeys.Concat(extraKeys).ToArray();
141-
}
142-
var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds};
143-
if (extraValues != null)
144-
{
145-
values = values.Concat(extraValues).ToArray();
146-
}
147-
cancellationToken.ThrowIfCancellationRequested();
148-
var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false);
149-
if ((bool) result[0])
150-
{
151-
return lockValue;
152-
}
153-
totalAttempts++;
154-
155-
} while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout);
113+
lockKeys = lockKeys.Concat(extraKeys).ToArray();
114+
}
115+
var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds};
116+
if (extraValues != null)
117+
{
118+
values = values.Concat(extraValues).ToArray();
119+
}
120+
var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false);
121+
if ((bool) result[0])
122+
{
123+
return lockValue;
124+
}
156125

157-
throw new CacheException("Unable to acquire cache lock: " +
158-
$"region='{_regionName}', " +
159-
$"keys='{string.Join(",", lockKeys)}', " +
160-
$"total attempts='{totalAttempts}', " +
161-
$"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'");
162-
}
126+
return null; // retry
127+
}, Context, cancellationToken);
163128
}
164129

165130
/// <summary>

StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs

Lines changed: 33 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,11 @@ namespace NHibernate.Caches.StackExRedis
1212
/// </summary>
1313
internal partial class RedisKeyLocker
1414
{
15-
private readonly string _regionName;
1615
private readonly string _lockKeySuffix;
1716
private readonly TimeSpan _lockTimeout;
18-
private readonly double _acquireLockTimeout;
19-
private readonly int _retryTimes;
20-
private readonly TimeSpan _maxRetryDelay;
21-
private readonly TimeSpan _minRetryDelay;
22-
private readonly ICacheLockRetryDelayProvider _lockRetryDelayProvider;
2317
private readonly ICacheLockValueProvider _lockValueProvider;
2418
private readonly IDatabase _database;
19+
private readonly RetryPolicy<string, Func<string>> _retryPolicy;
2520

2621
/// <summary>
2722
/// Default constructor.
@@ -34,16 +29,29 @@ public RedisKeyLocker(
3429
IDatabase database,
3530
RedisCacheLockConfiguration configuration)
3631
{
37-
_regionName = regionName;
3832
_database = database;
3933
_lockKeySuffix = configuration.KeySuffix;
4034
_lockTimeout = configuration.KeyTimeout;
41-
_acquireLockTimeout = configuration.AcquireTimeout.TotalMilliseconds;
42-
_retryTimes = configuration.RetryTimes;
43-
_maxRetryDelay = configuration.MaxRetryDelay;
44-
_minRetryDelay = configuration.MinRetryDelay;
45-
_lockRetryDelayProvider = configuration.RetryDelayProvider;
4635
_lockValueProvider = configuration.ValueProvider;
36+
37+
var acquireTimeout = configuration.AcquireTimeout;
38+
var retryTimes = configuration.RetryTimes;
39+
var maxRetryDelay = configuration.MaxRetryDelay;
40+
var minRetryDelay = configuration.MinRetryDelay;
41+
var lockRetryDelayProvider = configuration.RetryDelayProvider;
42+
43+
_retryPolicy = new RetryPolicy<string, Func<string>>(
44+
retryTimes,
45+
acquireTimeout,
46+
() => lockRetryDelayProvider.GetValue(minRetryDelay, maxRetryDelay)
47+
)
48+
.ShouldRetry(s => s == null)
49+
.OnFaliure((totalAttempts, elapsedMs, getKeysFn) =>
50+
throw new CacheException("Unable to acquire cache lock: " +
51+
$"region='{regionName}', " +
52+
$"keys='{getKeysFn()}', " +
53+
$"total attempts='{totalAttempts}', " +
54+
$"total acquiring time= '{elapsedMs}ms'"));
4755
}
4856

4957
/// <summary>
@@ -62,16 +70,10 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu
6270
throw new ArgumentNullException(nameof(key));
6371
}
6472
var lockKey = $"{key}{_lockKeySuffix}";
65-
var totalAttempts = 0;
66-
var lockTimer = new Stopwatch();
67-
lockTimer.Restart();
68-
do
73+
string Context() => lockKey;
74+
75+
return _retryPolicy.Execute(() =>
6976
{
70-
if (totalAttempts > 0)
71-
{
72-
var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay);
73-
Thread.Sleep(retryDelay);
74-
}
7577
var lockValue = _lockValueProvider.GetValue();
7678
if (!string.IsNullOrEmpty(luaScript))
7779
{
@@ -80,7 +82,7 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu
8082
{
8183
keys = keys.Concat(extraKeys).ToArray();
8284
}
83-
var values = new RedisValue[] {lockValue, (long)_lockTimeout.TotalMilliseconds};
85+
var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds};
8486
if (extraValues != null)
8587
{
8688
values = values.Concat(extraValues).ToArray();
@@ -95,15 +97,9 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu
9597
{
9698
return lockValue;
9799
}
98-
totalAttempts++;
99-
100-
} while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout);
101100

102-
throw new CacheException("Unable to acquire cache lock: " +
103-
$"region='{_regionName}', " +
104-
$"key='{key}', " +
105-
$"total attempts='{totalAttempts}', " +
106-
$"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'");
101+
return null; // retry
102+
}, Context);
107103
}
108104

109105
/// <summary>
@@ -125,21 +121,14 @@ public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, Re
125121
{
126122
throw new ArgumentNullException(nameof(luaScript));
127123
}
124+
string Context() => string.Join(",", keys.Select(o => $"{o}{_lockKeySuffix}"));
128125

129-
var lockKeys = new RedisKey[keys.Length];
130-
for (var i = 0; i < keys.Length; i++)
131-
{
132-
lockKeys[i] = $"{keys[i]}{_lockKeySuffix}";
133-
}
134-
var totalAttempts = 0;
135-
var lockTimer = new Stopwatch();
136-
lockTimer.Restart();
137-
do
126+
return _retryPolicy.Execute(() =>
138127
{
139-
if (totalAttempts > 0)
128+
var lockKeys = new RedisKey[keys.Length];
129+
for (var i = 0; i < keys.Length; i++)
140130
{
141-
var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay);
142-
Thread.Sleep(retryDelay);
131+
lockKeys[i] = $"{keys[i]}{_lockKeySuffix}";
143132
}
144133
var lockValue = _lockValueProvider.GetValue();
145134
if (extraKeys != null)
@@ -156,15 +145,9 @@ public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, Re
156145
{
157146
return lockValue;
158147
}
159-
totalAttempts++;
160-
161-
} while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout);
162148

163-
throw new CacheException("Unable to acquire cache lock: " +
164-
$"region='{_regionName}', " +
165-
$"keys='{string.Join(",", lockKeys)}', " +
166-
$"total attempts='{totalAttempts}', " +
167-
$"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'");
149+
return null; // retry
150+
}, Context);
168151
}
169152

170153
/// <summary>

0 commit comments

Comments
 (0)