Skip to content

Commit 961ed28

Browse files
committed
implement basic TTL to support basic-ops test in primary tests
1 parent fd2d89e commit 961ed28

3 files changed

Lines changed: 163 additions & 18 deletions

File tree

toys/StackExchange.Redis.Server/MemoryCacheRedisServer.cs

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,127 @@ public class MemoryCacheRedisServer : RedisServer
1313
public MemoryCacheRedisServer(EndPoint endpoint = null, TextWriter output = null) : base(endpoint, 1, output)
1414
=> CreateNewCache();
1515

16-
private MemoryCache _cache;
16+
private MemoryCache _cache2;
1717

1818
private void CreateNewCache()
1919
{
20-
var old = _cache;
21-
_cache = new MemoryCache(GetType().Name);
20+
var old = _cache2;
21+
_cache2 = new MemoryCache(GetType().Name);
2222
old?.Dispose();
2323
}
2424

2525
protected override void Dispose(bool disposing)
2626
{
27-
if (disposing) _cache.Dispose();
27+
if (disposing) _cache2.Dispose();
2828
base.Dispose(disposing);
2929
}
3030

31-
protected override long Dbsize(int database) => _cache.GetCount();
31+
protected override long Dbsize(int database) => _cache2.GetCount();
32+
33+
private readonly struct ExpiringValue(object value, DateTime absoluteExpiration)
34+
{
35+
public readonly object Value = value;
36+
public readonly DateTime AbsoluteExpiration = absoluteExpiration;
37+
}
38+
39+
private enum ExpectedType
40+
{
41+
Any = 0,
42+
Stack,
43+
Set,
44+
List,
45+
}
46+
private object Get(in RedisKey key, ExpectedType expectedType)
47+
{
48+
var val = _cache2[key];
49+
switch (val)
50+
{
51+
case null:
52+
return null;
53+
case ExpiringValue ev:
54+
if (ev.AbsoluteExpiration <= Now)
55+
{
56+
_cache2.Remove(key);
57+
return null;
58+
}
59+
return Validate(ev.Value, expectedType);
60+
default:
61+
return Validate(val, expectedType);
62+
}
63+
static object Validate(object value, ExpectedType expectedType)
64+
{
65+
return value switch
66+
{
67+
null => value,
68+
HashSet<RedisValue> set when expectedType is ExpectedType.Set or ExpectedType.Any => value,
69+
HashSet<RedisValue> => Throw(),
70+
Stack<RedisValue> stack when expectedType is ExpectedType.List or ExpectedType.Any => value,
71+
Stack<RedisValue> => Throw(),
72+
_ when expectedType is ExpectedType.Stack or ExpectedType.Any => value,
73+
_ => Throw(),
74+
};
75+
76+
static object Throw() => throw new WrongTypeException();
77+
}
78+
}
79+
protected override TimeSpan? Ttl(int database, in RedisKey key)
80+
{
81+
var val = _cache2[key];
82+
switch (val)
83+
{
84+
case null:
85+
return null;
86+
case ExpiringValue ev:
87+
var delta = ev.AbsoluteExpiration - Now;
88+
if (delta <= TimeSpan.Zero)
89+
{
90+
_cache2.Remove(key);
91+
return null;
92+
}
93+
return delta;
94+
default:
95+
return TimeSpan.MaxValue;
96+
}
97+
}
98+
99+
protected override bool Expire(int database, in RedisKey key, TimeSpan timeout)
100+
{
101+
var val = Get(key, ExpectedType.Any);
102+
if (val is not null)
103+
{
104+
_cache2[key] = new ExpiringValue(val, Now + timeout);
105+
return true;
106+
}
107+
108+
return false;
109+
}
110+
32111
protected override RedisValue Get(int database, in RedisKey key)
33-
=> RedisValue.Unbox(_cache[key]);
112+
{
113+
var val = Get(key, ExpectedType.Stack);
114+
return RedisValue.Unbox(val);
115+
}
116+
34117
protected override void Set(int database, in RedisKey key, in RedisValue value)
35-
=> _cache[key] = value.Box();
118+
=> _cache2[key] = value.Box();
36119
protected override bool Del(int database, in RedisKey key)
37-
=> _cache.Remove(key) != null;
120+
=> _cache2.Remove(key) != null;
38121
protected override void Flushdb(int database)
39122
=> CreateNewCache();
40123

124+
private DateTime Now => DateTime.UtcNow;
41125
protected override bool Exists(int database, in RedisKey key)
42-
=> _cache.Contains(key);
126+
{
127+
var val = Get(key, ExpectedType.Any);
128+
return val != null && !(val is ExpiringValue ev && ev.AbsoluteExpiration <= Now);
129+
}
43130

44131
protected override IEnumerable<RedisKey> Keys(int database, in RedisKey pattern) => GetKeysCore(pattern);
45132
private IEnumerable<RedisKey> GetKeysCore(RedisKey pattern)
46133
{
47-
foreach (var pair in _cache)
134+
foreach (var pair in _cache2)
48135
{
136+
if (pair.Value is ExpiringValue ev && ev.AbsoluteExpiration <= Now) continue;
49137
if (IsMatch(pattern, pair.Key)) yield return pair.Key;
50138
}
51139
}
@@ -60,7 +148,7 @@ protected override bool Srem(int database, in RedisKey key, in RedisValue value)
60148
var set = GetSet(key, false);
61149
if (set != null && set.Remove(value))
62150
{
63-
if (set.Count == 0) _cache.Remove(key);
151+
if (set.Count == 0) _cache2.Remove(key);
64152
return true;
65153
}
66154
return false;
@@ -70,11 +158,11 @@ protected override long Scard(int database, in RedisKey key)
70158

71159
private HashSet<RedisValue> GetSet(RedisKey key, bool create)
72160
{
73-
var set = (HashSet<RedisValue>)_cache[key];
161+
var set = (HashSet<RedisValue>)Get(key, ExpectedType.Set);
74162
if (set == null && create)
75163
{
76164
set = new HashSet<RedisValue>();
77-
_cache[key] = set;
165+
_cache2[key] = set;
78166
}
79167
return set;
80168
}
@@ -86,7 +174,7 @@ protected override RedisValue Spop(int database, in RedisKey key)
86174

87175
var result = set.First();
88176
set.Remove(result);
89-
if (set.Count == 0) _cache.Remove(key);
177+
if (set.Count == 0) _cache2.Remove(key);
90178
return result;
91179
}
92180

@@ -102,7 +190,7 @@ protected override RedisValue Lpop(int database, in RedisKey key)
102190
if (stack == null) return RedisValue.Null;
103191

104192
var val = stack.Pop();
105-
if (stack.Count == 0) _cache.Remove(key);
193+
if (stack.Count == 0) _cache2.Remove(key);
106194
return val;
107195
}
108196

@@ -130,13 +218,13 @@ protected override void LRange(int database, in RedisKey key, long start, Span<T
130218
}
131219
}
132220

133-
private Stack<RedisValue> GetStack(RedisKey key, bool create)
221+
private Stack<RedisValue> GetStack(in RedisKey key, bool create)
134222
{
135-
var stack = (Stack<RedisValue>)_cache[key];
223+
var stack = (Stack<RedisValue>)Get(key, ExpectedType.Stack);
136224
if (stack == null && create)
137225
{
138226
stack = new Stack<RedisValue>();
139-
_cache[key] = stack;
227+
_cache2[key] = stack;
140228
}
141229
return stack;
142230
}

toys/StackExchange.Redis.Server/RedisServer.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,12 +859,61 @@ protected virtual TypedRedisValue Del(RedisClient client, in RedisRequest reques
859859
}
860860
protected virtual bool Del(int database, in RedisKey key) => throw new NotSupportedException();
861861

862+
[RedisCommand(2)]
863+
protected virtual TypedRedisValue GetDel(RedisClient client, in RedisRequest request)
864+
{
865+
var key = request.GetKey(1);
866+
var value = Get(client.Database, key);
867+
if (!value.IsNull) Del(client.Database, key);
868+
return TypedRedisValue.BulkString(value);
869+
}
870+
862871
[RedisCommand(1)]
863872
protected virtual TypedRedisValue Dbsize(RedisClient client, in RedisRequest request)
864873
=> TypedRedisValue.Integer(Dbsize(client.Database));
865874

866875
protected virtual long Dbsize(int database) => throw new NotSupportedException();
867876

877+
[RedisCommand(3)]
878+
protected virtual TypedRedisValue Expire(RedisClient client, in RedisRequest request)
879+
{
880+
var key = request.GetKey(1);
881+
var seconds = request.GetInt32(2);
882+
return TypedRedisValue.Integer(Expire(client.Database, key, TimeSpan.FromSeconds(seconds)) ? 1 : 0);
883+
}
884+
885+
[RedisCommand(3)]
886+
protected virtual TypedRedisValue PExpire(RedisClient client, in RedisRequest request)
887+
{
888+
var key = request.GetKey(1);
889+
var millis = request.GetInt64(2);
890+
return TypedRedisValue.Integer(Expire(client.Database, key, TimeSpan.FromMilliseconds(millis)) ? 1 : 0);
891+
}
892+
893+
[RedisCommand(2)]
894+
protected virtual TypedRedisValue Ttl(RedisClient client, in RedisRequest request)
895+
{
896+
var key = request.GetKey(1);
897+
var ttl = Ttl(client.Database, key);
898+
if (ttl == null || ttl <= TimeSpan.Zero) return TypedRedisValue.Integer(-2);
899+
if (ttl == TimeSpan.MaxValue) return TypedRedisValue.Integer(-1);
900+
return TypedRedisValue.Integer((int)ttl.Value.TotalSeconds);
901+
}
902+
903+
protected virtual TimeSpan? Ttl(int database, in RedisKey key) => throw new NotSupportedException();
904+
905+
[RedisCommand(2)]
906+
protected virtual TypedRedisValue Pttl(RedisClient client, in RedisRequest request)
907+
{
908+
var key = request.GetKey(1);
909+
var ttl = Ttl(client.Database, key);
910+
if (ttl == null || ttl <= TimeSpan.Zero) return TypedRedisValue.Integer(-2);
911+
if (ttl == TimeSpan.MaxValue) return TypedRedisValue.Integer(-1);
912+
return TypedRedisValue.Integer((long)ttl.Value.TotalMilliseconds);
913+
}
914+
915+
protected virtual bool Expire(int database, in RedisKey key, TimeSpan timeout) => throw new NotSupportedException();
916+
868917
[RedisCommand(1)]
869918
protected virtual TypedRedisValue Flushall(RedisClient client, in RedisRequest request)
870919
{

toys/StackExchange.Redis.Server/RespServer.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ public virtual TypedRedisValue Execute(RedisClient client, in RedisRequest reque
497497
Log($"missing command: '{request.GetString(0)}'");
498498
return request.CommandNotFound();
499499
}
500+
catch (WrongTypeException)
501+
{
502+
return TypedRedisValue.Error("WRONGTYPE Operation against a key holding the wrong kind of value");
503+
}
500504
catch (InvalidCastException)
501505
{
502506
return TypedRedisValue.Error("WRONGTYPE Operation against a key holding the wrong kind of value");
@@ -522,6 +526,10 @@ public sealed class KeyMovedException : Exception
522526
public static void Throw(in RedisKey key) => throw new KeyMovedException(GetHashSlot(key));
523527
}
524528

529+
public sealed class WrongTypeException : Exception
530+
{
531+
}
532+
525533
protected internal static int GetHashSlot(in RedisKey key) => s_ClusterSelectionStrategy.HashSlot(key);
526534
private static readonly ServerSelectionStrategy s_ClusterSelectionStrategy = new(null) { ServerType = ServerType.Cluster };
527535

0 commit comments

Comments
 (0)