From 40f756075301facb4c8896b0946c3d3e075d8a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=A4=A7=E7=9F=B3=E5=A4=B4?= Date: Tue, 11 Mar 2025 13:22:08 +0800 Subject: [PATCH 1/5] =?UTF-8?q?[fix]=20=E5=A4=96=E9=83=A8=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E8=80=85=E5=8F=AF=E8=83=BD=E9=9C=80=E8=A6=81=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E4=BD=BF=E7=94=A8=E5=86=85=E9=83=A8=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E7=9A=84OwnerPacket=EF=BC=8C=E6=89=80=E4=BB=A5=E8=BF=99?= =?UTF-8?q?=E9=87=8C=E4=B8=8D=E9=87=8A=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewLife.Redis/Clusters/RedisReplication.cs | 5 ++--- NewLife.Redis/RedisClient.cs | 21 ++++++++++++--------- XUnitTest/HashTest.cs | 15 +++++++++------ 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/NewLife.Redis/Clusters/RedisReplication.cs b/NewLife.Redis/Clusters/RedisReplication.cs index b7e102b..4efbf49 100644 --- a/NewLife.Redis/Clusters/RedisReplication.cs +++ b/NewLife.Redis/Clusters/RedisReplication.cs @@ -1,5 +1,4 @@ -using System.Net.Sockets; -using NewLife.Log; +using NewLife.Log; using NewLife.Net; using NewLife.Threading; @@ -13,7 +12,7 @@ public class RedisReplication : RedisBase, IRedisCluster, IDisposable IList IRedisCluster.Nodes => Nodes.Select(x => (IRedisNode)x).ToList(); /// 节点改变事件 - public event EventHandler NodeChanged; + public event EventHandler? NodeChanged; /// 集群节点 public RedisNode[]? Nodes { get; protected set; } diff --git a/NewLife.Redis/RedisClient.cs b/NewLife.Redis/RedisClient.cs index e7c7b37..a9dfb3f 100644 --- a/NewLife.Redis/RedisClient.cs +++ b/NewLife.Redis/RedisClient.cs @@ -1,6 +1,5 @@ using System.Buffers; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; @@ -690,8 +689,9 @@ public class RedisClient : DisposeBase if (rs is TResult rs2) return rs2; if (TryChangeType(rs, typeof(TResult), out var target)) { - // 释放内部申请的OwnerPacket - rs.TryDispose(); + //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 + //// 释放内部申请的OwnerPacket + //rs.TryDispose(); return (TResult?)target; } @@ -727,8 +727,9 @@ public class RedisClient : DisposeBase if (rs == null) return false; if (TryChangeType(rs, typeof(TResult), out var target)) { - // 释放内部申请的OwnerPacket - rs.TryDispose(); + //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 + //// 释放内部申请的OwnerPacket + //rs.TryDispose(); value = (TResult?)target; return true; } @@ -788,8 +789,9 @@ public class RedisClient : DisposeBase if (rs is TResult rs2) return rs2; if (TryChangeType(rs, typeof(TResult), out var target)) { - // 释放内部申请的OwnerPacket - rs.TryDispose(); + //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 + //// 释放内部申请的OwnerPacket + //rs.TryDispose(); return (TResult?)target; } @@ -939,8 +941,9 @@ public class RedisClient : DisposeBase var rs = list[i]; if (rs != null && TryChangeType(rs, ps[i].Type, out var target) && target != null) { - // 释放内部申请的OwnerPacket - rs.TryDispose(); + //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 + //// 释放内部申请的OwnerPacket + //rs.TryDispose(); list[i] = target; } } diff --git a/XUnitTest/HashTest.cs b/XUnitTest/HashTest.cs index b0db7e1..760e4eb 100644 --- a/XUnitTest/HashTest.cs +++ b/XUnitTest/HashTest.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Runtime.Intrinsics.Arm; - using NewLife.Caching; using NewLife.Log; using Xunit; @@ -105,14 +102,20 @@ public class HashTest var hash = _redis.GetDictionary(key); Assert.NotNull(hash); - var l = hash as RedisHash; + var rh = hash as RedisHash; - foreach(var item in l.GetAll()) + foreach (var item in rh.GetAll()) { XTrace.WriteLine(item.Key); } - l["0"] = "0"; + rh["0"] = new EventInfo { EventId = "1234", EventName = "Stone" }; + } + + class EventInfo + { + public String? EventId { get; set; } + public String? EventName { get; set; } } } From f5bb807a3769c7bd45ba6b49147ec9e8b7e4d181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=A4=A7=E7=9F=B3=E5=A4=B4?= Date: Tue, 11 Mar 2025 13:55:30 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E7=B2=BE=E7=A1=AE=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=86=85=E5=AD=98=E9=87=8A=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewLife.Redis/Queues/RedisQueue.cs | 16 +++++++++-- NewLife.Redis/RedisClient.cs | 41 +++++++++++++++++++---------- NewLife.Redis/RedisHash.cs | 4 +++ NewLife.Redis/RedisStack.cs | 16 +++++++++-- NewLife.Redis/Services/RedisStat.cs | 4 ++- 5 files changed, 62 insertions(+), 19 deletions(-) diff --git a/NewLife.Redis/Queues/RedisQueue.cs b/NewLife.Redis/Queues/RedisQueue.cs index 2725dd1..4735a37 100644 --- a/NewLife.Redis/Queues/RedisQueue.cs +++ b/NewLife.Redis/Queues/RedisQueue.cs @@ -112,7 +112,13 @@ public class RedisQueue : QueueBase, IProducerConsumer if (timeout > 0 && Redis.Timeout < (timeout + 1) * 1000) Redis.Timeout = (timeout + 1) * 1000; var rs = Execute((rc, k) => rc.Execute("BRPOP", Key, timeout), true); - return rs == null || rs.Length < 2 ? default : (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + if (rs == null || rs.Length < 2) return default; + + var msg = (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + + if (typeof(T) != typeof(IPacket)) rs.TryDispose(); + + return msg; } /// 异步消费获取 @@ -126,7 +132,13 @@ public class RedisQueue : QueueBase, IProducerConsumer if (timeout > 0 && Redis.Timeout < (timeout + 1) * 1000) Redis.Timeout = (timeout + 1) * 1000; var rs = await ExecuteAsync((rc, k) => rc.ExecuteAsync("BRPOP", [Key, timeout], cancellationToken), true).ConfigureAwait(false); - return rs == null || rs.Length < 2 ? default : (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + if (rs == null || rs.Length < 2) return default; + + var msg = (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + + if (typeof(T) != typeof(IPacket)) rs.TryDispose(); + + return msg; } /// 异步消费获取 diff --git a/NewLife.Redis/RedisClient.cs b/NewLife.Redis/RedisClient.cs index a9dfb3f..396dd2e 100644 --- a/NewLife.Redis/RedisClient.cs +++ b/NewLife.Redis/RedisClient.cs @@ -678,20 +678,22 @@ public class RedisClient : DisposeBase try { // 管道模式 + var type = typeof(TResult); if (_ps != null) { - _ps.Add(new Command(cmd, args, typeof(TResult))); + _ps.Add(new Command(cmd, args, type)); return default; } var rs = ExecuteCommand(cmd, args); if (rs == null) return default; if (rs is TResult rs2) return rs2; - if (TryChangeType(rs, typeof(TResult), out var target)) + + if (TryChangeType(rs, type, out var target)) { //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 - //// 释放内部申请的OwnerPacket - //rs.TryDispose(); + // 释放内部申请的OwnerPacket + if (type != typeof(IPacket) && type != typeof(IPacket[])) rs.TryDispose(); return (TResult?)target; } @@ -725,11 +727,13 @@ public class RedisClient : DisposeBase value = default; if (rs == null) return false; - if (TryChangeType(rs, typeof(TResult), out var target)) + + var type = typeof(TResult); + if (TryChangeType(rs, type, out var target)) { //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 - //// 释放内部申请的OwnerPacket - //rs.TryDispose(); + // 释放内部申请的OwnerPacket + if (type != typeof(IPacket) && type != typeof(IPacket[])) rs.TryDispose(); value = (TResult?)target; return true; } @@ -778,20 +782,22 @@ public class RedisClient : DisposeBase public virtual async Task ExecuteAsync(String cmd, Object?[] args, CancellationToken cancellationToken) { // 管道模式 + var type = typeof(TResult); if (_ps != null) { - _ps.Add(new Command(cmd, args, typeof(TResult))); + _ps.Add(new Command(cmd, args, type)); return default; } var rs = await ExecuteAsync(cmd, args, cancellationToken).ConfigureAwait(false); if (rs == null) return default; if (rs is TResult rs2) return rs2; - if (TryChangeType(rs, typeof(TResult), out var target)) + + if (TryChangeType(rs, type, out var target)) { //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 - //// 释放内部申请的OwnerPacket - //rs.TryDispose(); + // 释放内部申请的OwnerPacket + if (type != typeof(IPacket) && type != typeof(IPacket[])) rs.TryDispose(); return (TResult?)target; } @@ -812,7 +818,14 @@ public class RedisClient : DisposeBase //var rs = ExecuteCommand(null, null, null); if (rs == null) return default; if (rs is TResult rs2) return rs2; - if (TryChangeType(rs, typeof(TResult), out var target)) return (TResult?)target; + + var type = typeof(TResult); + if (TryChangeType(rs, type, out var target)) + { + // 释放内部申请的OwnerPacket + if (type != typeof(IPacket) && type != typeof(IPacket[])) rs.TryDispose(); + return (TResult?)target; + } return default; } @@ -942,8 +955,8 @@ public class RedisClient : DisposeBase if (rs != null && TryChangeType(rs, ps[i].Type, out var target) && target != null) { //!!! 外部调用者可能需要直接使用内部申请的OwnerPacket,所以这里不释放 - //// 释放内部申请的OwnerPacket - //rs.TryDispose(); + // 释放内部申请的OwnerPacket + if (ps[i].Type != typeof(IPacket) && ps[i].Type != typeof(IPacket[])) rs.TryDispose(); list[i] = target; } } diff --git a/NewLife.Redis/RedisHash.cs b/NewLife.Redis/RedisHash.cs index 0ff6b8e..14f531e 100644 --- a/NewLife.Redis/RedisHash.cs +++ b/NewLife.Redis/RedisHash.cs @@ -68,6 +68,8 @@ public class RedisHash : RedisBase, IDictionary value = Redis.Encoder.Decode(pk)!; //value = (TValue?)Redis.Encoder.Decode(pk, typeof(TValue))!; + if (typeof(TValue) != typeof(IPacket)) pk.TryDispose(); + return true; } @@ -164,6 +166,8 @@ public class RedisHash : RedisBase, IDictionary dic[key] = value; } + if (typeof(TKey) != typeof(IPacket) && typeof(TValue) != typeof(IPacket)) rs.TryDispose(); + return dic; } diff --git a/NewLife.Redis/RedisStack.cs b/NewLife.Redis/RedisStack.cs index d69fda2..26de795 100644 --- a/NewLife.Redis/RedisStack.cs +++ b/NewLife.Redis/RedisStack.cs @@ -83,7 +83,13 @@ public class RedisStack : RedisBase, IProducerConsumer if (timeout < 0) return Execute((rc, k) => rc.Execute("RPOP", Key), true); var rs = Execute((rc, k) => rc.Execute("BRPOP", Key, timeout), true); - return rs == null || rs.Length < 2 ? default : (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + if (rs == null || rs.Length < 2) return default; + + var msg = (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + + if (typeof(T) != typeof(IPacket)) rs.TryDispose(); + + return msg; } /// 异步消费获取 @@ -95,7 +101,13 @@ public class RedisStack : RedisBase, IProducerConsumer if (timeout < 0) return await ExecuteAsync((rc, k) => rc.ExecuteAsync("RPOP", Key), true).ConfigureAwait(false); var rs = await ExecuteAsync((rc, k) => rc.ExecuteAsync("BRPOP", [Key, timeout], cancellationToken), true).ConfigureAwait(false); - return rs == null || rs.Length < 2 ? default : (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + if (rs == null || rs.Length < 2) return default; + + var msg = (T?)Redis.Encoder.Decode(rs[1], typeof(T)); + + if (typeof(T) != typeof(IPacket)) rs.TryDispose(); + + return msg; } /// 异步消费获取 diff --git a/NewLife.Redis/Services/RedisStat.cs b/NewLife.Redis/Services/RedisStat.cs index e575566..0812a77 100644 --- a/NewLife.Redis/Services/RedisStat.cs +++ b/NewLife.Redis/Services/RedisStat.cs @@ -90,7 +90,7 @@ public class RedisStat : DisposeBase if (!_redis.Rename(key, newKey, false)) return; _redis.Remove($"exists:{key}"); - var rs = _redis.Execute(newKey, (r,k) => r.Execute("HGETALL", k)); + var rs = _redis.Execute(newKey, (r, k) => r.Execute("HGETALL", k)); if (rs != null) { var dic = new Dictionary(); @@ -104,6 +104,8 @@ public class RedisStat : DisposeBase OnSave(key, dic); } + rs.TryDispose(); + _redis.Remove(newKey); } #endregion From 9ae8e89ed2fc9d34dcdd4c7e5e262d8aac63dcf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E4=BA=BA=E6=98=93?= Date: Wed, 19 Mar 2025 11:49:17 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20RemoveTest=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=B9=E6=B3=95=E5=B9=B6=E6=9B=B4=E6=96=B0=20EventI?= =?UTF-8?q?nfo=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `HashTest.cs` 文件中,新增了 `RemoveTest` 方法,用于测试从 Redis 哈希中移除元素的功能。该方法验证了移除操作后剩余元素的数量。同时,将 `EventInfo` 类的属性类型从可空字符串 (`String?`) 修改为非可空字符串 (`String`)。 --- XUnitTest/HashTest.cs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/XUnitTest/HashTest.cs b/XUnitTest/HashTest.cs index 760e4eb..bd3e379 100644 --- a/XUnitTest/HashTest.cs +++ b/XUnitTest/HashTest.cs @@ -112,10 +112,33 @@ public class HashTest rh["0"] = new EventInfo { EventId = "1234", EventName = "Stone" }; } + [Fact] + public void RemoveTest() + { + var key = $"NewLife:eventinfo:adsfasdfasdfdsaf"; + + var hash = _redis.GetDictionary(key); + Assert.NotNull(hash); + + var rh = hash as RedisHash; + + foreach (var item in rh.GetAll()) + { + XTrace.WriteLine(item.Key); + } + + rh["0"] = new EventInfo { EventId = "1234", EventName = "Stone" }; + rh["1"] = new EventInfo { EventId = "12345", EventName = "Stone" }; + rh["2"] = new EventInfo { EventId = "123456", EventName = "Stone" }; + + rh.Remove("0"); + Assert.Equal(2, rh.Count); + } + class EventInfo { - public String? EventId { get; set; } - public String? EventName { get; set; } + public String EventId { get; set; } + public String EventName { get; set; } } } From 88f37eccdbaff4442d8032660315f4f0865afc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=A4=A7=E7=9F=B3=E5=A4=B4?= Date: Fri, 28 Mar 2025 17:18:33 +0800 Subject: [PATCH 4/5] =?UTF-8?q?Redis7=E6=94=AF=E6=8C=81LPOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewLife.Redis/RedisList.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/NewLife.Redis/RedisList.cs b/NewLife.Redis/RedisList.cs index 1d472a6..743fe2c 100644 --- a/NewLife.Redis/RedisList.cs +++ b/NewLife.Redis/RedisList.cs @@ -47,14 +47,7 @@ public class RedisList : RedisBase, IList /// 是否包含指定元素 /// /// - public Boolean Contains(T item) - { - var count = Count; - if (count > 1000) throw new NotSupportedException($"[{Key}]的元素个数过多,不支持!"); - - var list = GetAll(); - return list.Contains(item); - } + public Boolean Contains(T item) => IndexOf(item) >= 0; /// 复制到目标数组 /// @@ -72,6 +65,9 @@ public class RedisList : RedisBase, IList /// public Int32 IndexOf(T item) { + // Redis7支持LPOS + if (Redis.Version.Major >= 7) return LPOS(item); + var count = Count; if (count > 1000) throw new NotSupportedException($"[{Key}]的元素个数过多,不支持!"); @@ -217,5 +213,9 @@ public class RedisList : RedisBase, IList /// /// public Int32 LRem(Int32 count, T value) => Execute((r, k) => r.Execute("LREM", Key, count, value), true); + + /// 获取元素位置 + /// + public Int32 LPOS(T item) => Execute((rc, k) => rc.Execute("LPOS", Key, item), false); #endregion } \ No newline at end of file From d38011a409b34dc29bfa80c4248118fdf87af427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=A4=A7=E7=9F=B3=E5=A4=B4?= Date: Fri, 28 Mar 2025 17:31:34 +0800 Subject: [PATCH 5/5] =?UTF-8?q?[feat]=20=E6=96=B0=E5=A2=9ELPOP=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81Redis7=E4=B8=8B=E7=9A=84=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E7=B4=A2=E5=BC=95=E6=9F=A5=E6=89=BE=EF=BC=8CRedis7-=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=80=9A=E8=BF=87=E5=A4=9A=E6=AC=A1LRange=E9=81=8D?= =?UTF-8?q?=E5=8E=86=E5=AE=9E=E7=8E=B0=E3=80=82close:=20https://github.com?= =?UTF-8?q?/NewLifeX/NewLife.Redis/issues/137?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewLife.Redis/Queues/RedisReliableQueue.cs | 2 ++ NewLife.Redis/RedisList.cs | 25 +++++++++++++--- XUnitTest/ListTests.cs | 33 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/NewLife.Redis/Queues/RedisReliableQueue.cs b/NewLife.Redis/Queues/RedisReliableQueue.cs index 3f7ae51..460f635 100644 --- a/NewLife.Redis/Queues/RedisReliableQueue.cs +++ b/NewLife.Redis/Queues/RedisReliableQueue.cs @@ -442,6 +442,7 @@ public class RedisReliableQueue : QueueBase, IProducerConsumer, IDisposabl // 清理已经失去Status的Ack foreach (var key in rds.Search($"{_Key}:Ack:*", 1000)) + { if (!acks.Contains(key)) { var queue = rds.GetList(key) as RedisList; @@ -449,6 +450,7 @@ public class RedisReliableQueue : QueueBase, IProducerConsumer, IDisposabl XTrace.WriteLine("全局清理死信:{0} {1}", key, msgs.ToJson()); rds.Remove(key); } + } return count; } diff --git a/NewLife.Redis/RedisList.cs b/NewLife.Redis/RedisList.cs index 743fe2c..bd8cbf3 100644 --- a/NewLife.Redis/RedisList.cs +++ b/NewLife.Redis/RedisList.cs @@ -68,11 +68,28 @@ public class RedisList : RedisBase, IList // Redis7支持LPOS if (Redis.Version.Major >= 7) return LPOS(item); - var count = Count; - if (count > 1000) throw new NotSupportedException($"[{Key}]的元素个数过多,不支持!"); + var p = 0; + var batch = 100; + while (true) + { + var arr = LRange(p, p + batch - 1); + if (arr == null || arr.Length == 0) break; - var arr = GetAll(); - return Array.IndexOf(arr, item); + var idx = Array.IndexOf(arr, item); + if (idx >= 0) return p + idx; + + if (p >= 1_000_000) throw new NotSupportedException($"[{Key}]的元素个数过多,不支持遍历!"); + + p += batch; + } + + return -1; + + //var count = Count; + //if (count > 1000) + + //var arr = GetAll(); + //return Array.IndexOf(arr, item); } /// 在指定位置插入 diff --git a/XUnitTest/ListTests.cs b/XUnitTest/ListTests.cs index f1c45f3..b046560 100644 --- a/XUnitTest/ListTests.cs +++ b/XUnitTest/ListTests.cs @@ -1,5 +1,6 @@ using NewLife.Caching; using NewLife.Log; +using NewLife.Security; using System; using System.Diagnostics; using System.Linq; @@ -164,6 +165,38 @@ public class ListTests Assert.Equal(vs3[1], item2); } + [Fact] + public void List_IndexOf() + { + var key = "lkey_indexof"; + + // 删除已有 + _redis.Remove(key); + + var rlist = _redis.GetList(key) as RedisList; + Assert.NotNull(rlist); + + // 添加 + var vs = Enumerable.Range(0, 1000).Select(e => Rand.NextString(8)).ToArray(); + rlist.AddRange(vs); + _redis.SetExpire(key, TimeSpan.FromSeconds(60)); + + // 索引 + var idx = rlist.IndexOf(vs[1]); + Assert.Equal(1, idx); + + idx = rlist.IndexOf("abcd2"); + Assert.Equal(-1, idx); + + idx = rlist.IndexOf(vs[321]); + Assert.Equal(321, idx); + + var rs = rlist.Contains(vs[456]); + Assert.True(rs); + + _redis.Remove(key); + } + [Fact] public void RPOPLPUSH_Test() {