diff --git a/README.md b/README.md
index ba79324..63c78e9 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ FreeRedis is a redis client based on .NET, supports .NET Core 2.1+, .NET Framewo
- 🎣 Support Redis Master-Slave
- 📡 Support Redis Pub-Sub
- 📃 Support Redis Lua Scripting
-- 💻 Support Pipeline、Transaction、DelayQueue、RediSearch
+- 💻 Support Pipeline, Transaction, DelayQueue, RediSearch
- 🌴 Support Geo type commands (requires redis-server 3.2 and above)
- 🌲 Support Streams type commands (requires redis-server 5.0 and above)
- ⚡ Support Client-side-caching (requires redis-server 6.0 and above)
diff --git a/src/FreeRedis.DistributedCache/FreeRedis.DistributedCache.csproj b/src/FreeRedis.DistributedCache/FreeRedis.DistributedCache.csproj
index 1e6584d..db67a3c 100644
--- a/src/FreeRedis.DistributedCache/FreeRedis.DistributedCache.csproj
+++ b/src/FreeRedis.DistributedCache/FreeRedis.DistributedCache.csproj
@@ -4,7 +4,7 @@
FreeRedis.DistributedCache
FreeRedis.DistributedCache
FreeRedis.DistributedCache
- 1.3.2
+ 1.3.3
true
https://github.com/2881099/FreeRedis
分布式缓存 FreeRedis 实现 Microsoft.Extensions.Caching
diff --git a/src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj b/src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj
index 5176498..c61736d 100644
--- a/src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj
+++ b/src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj
@@ -5,7 +5,7 @@
FreeRedis.OpenTelemetry
FreeRedis.OpenTelemetry
FreeRedis.OpenTelemetry
- 1.3.2
+ 1.3.3
true
https://github.com/2881099/FreeRedis
https://github.com/2881099/FreeRedis
diff --git a/src/FreeRedis/FreeRedis.csproj b/src/FreeRedis/FreeRedis.csproj
index da75881..d3819c1 100644
--- a/src/FreeRedis/FreeRedis.csproj
+++ b/src/FreeRedis/FreeRedis.csproj
@@ -5,7 +5,7 @@
FreeRedis
FreeRedis
FreeRedis
- 1.3.2
+ 1.3.3
true
https://github.com/2881099/FreeRedis
FreeRedis is .NET redis client, supports cluster, sentinel, master-slave, pipeline, transaction and connection pool.
diff --git a/src/FreeRedis/FreeRedis.sln b/src/FreeRedis/FreeRedis.sln
deleted file mode 100644
index 9f2bd1f..0000000
--- a/src/FreeRedis/FreeRedis.sln
+++ /dev/null
@@ -1,25 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.5.002.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeRedis", "FreeRedis.csproj", "{9C8F4D2B-B78E-43B1-BF70-1B6100C57BCA}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {9C8F4D2B-B78E-43B1-BF70-1B6100C57BCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9C8F4D2B-B78E-43B1-BF70-1B6100C57BCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9C8F4D2B-B78E-43B1-BF70-1B6100C57BCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9C8F4D2B-B78E-43B1-BF70-1B6100C57BCA}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {0488E242-36D8-4669-8CFA-05A7F1D9C8EF}
- EndGlobalSection
-EndGlobal
diff --git a/src/FreeRedis/FreeRedis.xml b/src/FreeRedis/FreeRedis.xml
index 03530d5..167cb77 100644
--- a/src/FreeRedis/FreeRedis.xml
+++ b/src/FreeRedis/FreeRedis.xml
@@ -1615,6 +1615,11 @@
CancellationToken 自动取消锁
+
+
+ 当刷新锁时间的看门狗线程失去与Redis连接时,导致无法刷新延长锁时间时,触发此HandelLostToken Cancel
+
+
延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
diff --git a/src/FreeRedis/RedisClient/Modules/Lock.cs b/src/FreeRedis/RedisClient/Modules/Lock.cs
index 8c9623c..805c997 100644
--- a/src/FreeRedis/RedisClient/Modules/Lock.cs
+++ b/src/FreeRedis/RedisClient/Modules/Lock.cs
@@ -5,145 +5,170 @@
namespace FreeRedis
{
- partial class RedisClient
- {
- ///
- /// 开启分布式锁,若超时返回null
- ///
- /// 锁名称
- /// 超时(秒)
- /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
- ///
- public LockController Lock(string name, int timeoutSeconds, bool autoDelay = true)
- {
- name = $"RedisClientLock:{name}";
- var startTime = DateTime.Now;
- while (DateTime.Now.Subtract(startTime).TotalSeconds < timeoutSeconds)
- {
- var value = Guid.NewGuid().ToString();
- if (SetNx(name, value, timeoutSeconds) == true)
- {
- double refreshSeconds = (double)timeoutSeconds / 2.0;
- return new LockController(this, name, value, timeoutSeconds, refreshSeconds, autoDelay, CancellationToken.None);
- }
- Thread.CurrentThread.Join(3);
- }
- return null;
- }
-
- ///
- /// 开启分布式锁,若超时返回null
- ///
- /// 锁名称
- /// 独占锁过期时间
- /// 每隔多久自动延长一次锁,时间要比 timeoutSeconds 小,否则没有意义
- /// 等待锁释放超时时间,如果这个时间内获取不到锁,则 LockController 为 null
- /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
- /// CancellationToken 自动取消锁
- ///
- public LockController Lock(string name, int timeoutSeconds, int refrshTimeoutSeconds, int waitTimeoutSeconds, bool autoDelay = true, CancellationToken token = default)
- {
- if (refrshTimeoutSeconds == 0) throw new ArgumentException(nameof(refrshTimeoutSeconds), "刷新间隔时间不能为0");
- if (waitTimeoutSeconds == 0) waitTimeoutSeconds = refrshTimeoutSeconds;
-
- name = $"RedisClientLock:{name}";
- var startTime = DateTime.Now;
-
- // 规定时间内等待锁释放
- while (DateTime.Now.Subtract(startTime).TotalSeconds < waitTimeoutSeconds)
- {
- var value = Guid.NewGuid().ToString();
- if (SetNx(name, value, timeoutSeconds) == true)
- {
- return new LockController(this, name, value, timeoutSeconds, refrshTimeoutSeconds, autoDelay, token);
- }
- if (token.IsCancellationRequested) return null;
- Thread.CurrentThread.Join(millisecondsTimeout: 3);
- }
- return null;
- }
-
- public class LockController : IDisposable
- {
-
- RedisClient _client;
- string _name;
- string _value;
- int _timeoutSeconds;
- Timer _autoDelayTimer;
- private readonly CancellationToken _token;
- internal LockController(RedisClient rds, string name, string value, int timeoutSeconds, double refreshSeconds, bool autoDelay, CancellationToken token)
- {
- _client = rds;
- _name = name;
- _value = value;
- _timeoutSeconds = timeoutSeconds;
- _token = token;
- if (autoDelay)
- {
- var refreshMilli = (int)(refreshSeconds * 1000);
- var timeoutMilli = timeoutSeconds * 1000;
- _autoDelayTimer = new Timer(state2 => Refresh(timeoutMilli), null, refreshMilli, refreshMilli);
- }
- }
-
- ///
- /// 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
- ///
- /// 延长的毫秒数
- /// 成功/失败
- public bool Delay(int milliseconds)
- {
- var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
+ partial class RedisClient
+ {
+ ///
+ /// 开启分布式锁,若超时返回null
+ ///
+ /// 锁名称
+ /// 超时(秒)
+ /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
+ ///
+ public LockController Lock(string name, int timeoutSeconds, bool autoDelay = true)
+ {
+ name = $"RedisClientLock:{name}";
+ var startTime = DateTime.Now;
+ while (DateTime.Now.Subtract(startTime).TotalSeconds < timeoutSeconds)
+ {
+ var value = Guid.NewGuid().ToString();
+ if (SetNx(name, value, timeoutSeconds) == true)
+ {
+ double refreshSeconds = (double)timeoutSeconds / 2.0;
+ return new LockController(this, name, value, timeoutSeconds, refreshSeconds, autoDelay, CancellationToken.None);
+ }
+ Thread.CurrentThread.Join(3);
+ }
+ return null;
+ }
+
+ ///
+ /// 开启分布式锁,若超时返回null
+ ///
+ /// 锁名称
+ /// 独占锁过期时间
+ /// 每隔多久自动延长一次锁,时间要比 timeoutSeconds 小,否则没有意义
+ /// 等待锁释放超时时间,如果这个时间内获取不到锁,则 LockController 为 null
+ /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
+ /// CancellationToken 自动取消锁
+ ///
+ public LockController Lock(string name, int timeoutSeconds, int refrshTimeoutSeconds, int waitTimeoutSeconds, bool autoDelay = true, CancellationToken token = default)
+ {
+ if (refrshTimeoutSeconds == 0) throw new ArgumentException(nameof(refrshTimeoutSeconds), "刷新间隔时间不能为0");
+ if (waitTimeoutSeconds == 0) waitTimeoutSeconds = refrshTimeoutSeconds;
+
+ name = $"RedisClientLock:{name}";
+ var startTime = DateTime.Now;
+
+ // 规定时间内等待锁释放
+ while (DateTime.Now.Subtract(startTime).TotalSeconds < waitTimeoutSeconds)
+ {
+ var value = Guid.NewGuid().ToString();
+ if (SetNx(name, value, timeoutSeconds) == true)
+ {
+ return new LockController(this, name, value, timeoutSeconds, refrshTimeoutSeconds, autoDelay, token);
+ }
+ if (token.IsCancellationRequested) return null;
+ Thread.CurrentThread.Join(millisecondsTimeout: 3);
+ }
+ return null;
+ }
+
+ public class LockController : IDisposable
+ {
+
+ RedisClient _client;
+ string _name;
+ string _value;
+ int _timeoutSeconds;
+ Timer _autoDelayTimer;
+ private CancellationTokenSource _handleLostTokenSource;
+ private readonly CancellationToken _token;
+
+ ///
+ /// 当刷新锁时间的看门狗线程失去与Redis连接时,导致无法刷新延长锁时间时,触发此HandelLostToken Cancel
+ ///
+ public CancellationToken? HandleLostToken { get; }
+
+ internal LockController(RedisClient rds, string name, string value, int timeoutSeconds, double refreshSeconds, bool autoDelay, CancellationToken token)
+ {
+ _client = rds;
+ _name = name;
+ _value = value;
+ _timeoutSeconds = timeoutSeconds;
+ _token = token;
+ if (autoDelay)
+ {
+ _handleLostTokenSource = new CancellationTokenSource();
+ HandleLostToken = _handleLostTokenSource.Token;
+
+ var refreshMilli = (int)(refreshSeconds * 1000);
+ var timeoutMilli = timeoutSeconds * 1000;
+ _autoDelayTimer = new Timer(state2 => Refresh(timeoutMilli), null, refreshMilli, refreshMilli);
+ }
+ }
+
+ ///
+ /// 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
+ ///
+ /// 延长的毫秒数
+ /// 成功/失败
+ public bool Delay(int milliseconds)
+ {
+ var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
local ttlva = redis.call('PTTL', KEYS[1])
redis.call('PEXPIRE', KEYS[1], ARGV[2] + ttlva)
return 1
end
return 0", new[] { _name }, _value, milliseconds)?.ToString() == "1";
- if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
- return ret;
- }
-
- ///
- /// 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
- ///
- /// 刷新的毫秒数
- /// 成功/失败
- public bool Refresh(int milliseconds)
- {
- var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
+ if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
+ return ret;
+ }
+
+ ///
+ /// 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
+ ///
+ /// 刷新的毫秒数
+ /// 成功/失败
+ public bool Refresh(int milliseconds)
+ {
+ if (_token.IsCancellationRequested)
+ {
+ _autoDelayTimer?.Dispose();
+ }
+
+ try
+ {
+ var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
redis.call('PEXPIRE', KEYS[1], ARGV[2])
return 1
end
return 0", new[] { _name }, _value, milliseconds)?.ToString() == "1";
- if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
-
- if (_token.IsCancellationRequested)
- {
- _autoDelayTimer?.Dispose();
- }
- return ret;
- }
-
- ///
- /// 释放分布式锁
- ///
- /// 成功/失败
- public bool Unlock()
- {
- _autoDelayTimer?.Dispose();
- return _client.Eval(@"local gva = redis.call('GET', KEYS[1])
+ if (ret == false)
+ {
+ _handleLostTokenSource?.Cancel();
+ _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
+ }
+
+ return ret;
+ }
+ catch
+ {
+ _handleLostTokenSource?.Cancel();
+ _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
+ return false;//这里必须要吞掉异常,否则会导致整个程序崩溃,因为Timer的异常没有地方去处理
+ }
+ }
+
+ ///
+ /// 释放分布式锁
+ ///
+ /// 成功/失败
+ public bool Unlock()
+ {
+ _handleLostTokenSource?.Dispose();
+ _autoDelayTimer?.Dispose();
+ return _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
redis.call('DEL', KEYS[1])
return 1
end
return 0", new[] { _name }, _value)?.ToString() == "1";
- }
+ }
- public void Dispose() => this.Unlock();
- }
- }
+ public void Dispose() => this.Unlock();
+ }
+ }
}
diff --git a/src/FreeRedis/RedisClient/Modules/RediSearchBuilder/AggregateBuilder.cs b/src/FreeRedis/RedisClient/Modules/RediSearchBuilder/AggregateBuilder.cs
index df19e25..39e4418 100644
--- a/src/FreeRedis/RedisClient/Modules/RediSearchBuilder/AggregateBuilder.cs
+++ b/src/FreeRedis/RedisClient/Modules/RediSearchBuilder/AggregateBuilder.cs
@@ -87,7 +87,7 @@ CommandPacket GetCommandPacket()
private List _groupByReduces = new List();
private List