1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
public class RedisDistributedLockWithWatchdog : IDistributedLock
{
private readonly IDatabase _db;
private readonly string _lockKey;
private readonly string _lockValue;
private readonly TimeSpan _expiry;
private readonly TimeSpan _renewInterval;
private CancellationTokenSource? _cts;
private bool _disposed;
private static readonly LuaScript UnlockScript = LuaScript.Prepare(
@"if redis.call('get', @key) == @value then
return redis.call('del', @key)
else
return 0
end");
private static readonly LuaScript RenewScript = LuaScript.Prepare(
@"if redis.call('get', @key) == @value then
return redis.call('expire', @key, @ttl)
else
return 0
end");
public RedisDistributedLockWithWatchdog(
IDatabase db, string lockKey, TimeSpan expiry)
{
_db = db;
_lockKey = lockKey;
_lockValue = Guid.NewGuid().ToString();
_expiry = expiry;
_renewInterval = TimeSpan.FromTicks(expiry.Ticks / 3);
}
public async Task<bool> TryAcquireAsync(TimeSpan timeout)
{
var deadline = DateTime.UtcNow + timeout;
var retryDelay = TimeSpan.FromMilliseconds(50);
while (DateTime.UtcNow < deadline)
{
var acquired = await _db.StringSetAsync(
_lockKey, _lockValue, _expiry, When.NotExists);
if (acquired)
{
StartWatchdog();
return true;
}
await Task.Delay(retryDelay);
}
return false;
}
private void StartWatchdog()
{
_cts = new CancellationTokenSource();
_ = Task.Run(async () =>
{
while (!_cts.Token.IsCancellationRequested)
{
await Task.Delay(_renewInterval, _cts.Token);
try
{
await _db.ScriptEvaluateAsync(RenewScript,
new
{
key = (RedisKey)_lockKey,
value = _lockValue,
ttl = (int)_expiry.TotalSeconds
});
}
catch (OperationCanceledException) { break; }
}
}, _cts.Token);
}
public async Task ReleaseAsync()
{
_cts?.Cancel();
await _db.ScriptEvaluateAsync(UnlockScript,
new { key = (RedisKey)_lockKey, value = _lockValue });
}
public void Dispose()
{
if (!_disposed)
{
ReleaseAsync().GetAwaiter().GetResult();
_cts?.Dispose();
_disposed = true;
}
}
}
|