Skip to content

Commit 4aa066a

Browse files
committed
Fix RedisCache update that breaks usage against older redis servers dotnet#38860
1 parent 35ae498 commit 4aa066a

File tree

1 file changed

+43
-6
lines changed

1 file changed

+43
-6
lines changed

src/Caching/StackExchangeRedis/src/RedisCache.cs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,41 @@ namespace Microsoft.Extensions.Caching.StackExchangeRedis
1616
/// </summary>
1717
public class RedisCache : IDistributedCache, IDisposable
1818
{
19+
// -- Explanation of why two kinds of SetScript are used --
20+
// * Redis 2.0 had HSET key field value for setting individual hash fields,
21+
// and HMSET key field value [field value ...] for setting multiple hash fields (against the same key)
22+
// * Redis 4.0 observes this redundancy, and adds HSET key field value [field value ...],
23+
// and also marks HMSET as deprecated, although it still works fine.
24+
// But HSET doesn't allows multiple field/value pairs for Redis prior 4.0.
25+
1926
// KEYS[1] = = key
2027
// ARGV[1] = absolute-expiration - ticks as long (-1 for none)
2128
// ARGV[2] = sliding-expiration - ticks as long (-1 for none)
2229
// ARGV[3] = relative-expiration (long, in seconds, -1 for none) - Min(absolute-expiration - Now, sliding-expiration)
2330
// ARGV[4] = data - byte[]
2431
// this order should not change LUA script depends on it
25-
private const string SetScript = (@"
32+
private const string SetScriptPerExtendedSetCommand = (@"
2633
redis.call('HSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4])
2734
if ARGV[3] ~= '-1' then
2835
redis.call('EXPIRE', KEYS[1], ARGV[3])
2936
end
3037
return 1");
38+
private const string DeprecatedSetScript = (@"
39+
redis.call('HMSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4])
40+
if ARGV[3] ~= '-1' then
41+
redis.call('EXPIRE', KEYS[1], ARGV[3])
42+
end
43+
return 1");
3144
private const string AbsoluteExpirationKey = "absexp";
3245
private const string SlidingExpirationKey = "sldexp";
3346
private const string DataKey = "data";
3447
private const long NotPresent = -1;
48+
private static readonly Version ServerVersionWithExtendedSetCommand = new Version(4, 0, 0);
3549

3650
private volatile IConnectionMultiplexer _connection;
3751
private IDatabase _cache;
3852
private bool _disposed;
53+
private string _setScript = SetScriptPerExtendedSetCommand;
3954

4055
private readonly RedisCacheOptions _options;
4156
private readonly string _instance;
@@ -107,7 +122,7 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
107122

108123
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options);
109124

110-
var result = _cache.ScriptEvaluate(SetScript, new RedisKey[] { _instance + key },
125+
var result = _cache.ScriptEvaluate(_setScript, new RedisKey[] { _instance + key },
111126
new RedisValue[]
112127
{
113128
absoluteExpiration?.Ticks ?? NotPresent,
@@ -143,7 +158,7 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
143158

144159
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options);
145160

146-
await _cache.ScriptEvaluateAsync(SetScript, new RedisKey[] { _instance + key },
161+
await _cache.ScriptEvaluateAsync(_setScript, new RedisKey[] { _instance + key },
147162
new RedisValue[]
148163
{
149164
absoluteExpiration?.Ticks ?? NotPresent,
@@ -206,7 +221,7 @@ private void Connect()
206221
_connection = _options.ConnectionMultiplexerFactory().GetAwaiter().GetResult();
207222
}
208223

209-
TryRegisterProfiler();
224+
PrepareConnection();
210225
_cache = _connection.GetDatabase();
211226
}
212227
}
@@ -247,7 +262,7 @@ private void Connect()
247262
_connection = await _options.ConnectionMultiplexerFactory();
248263
}
249264

250-
TryRegisterProfiler();
265+
PrepareConnection();
251266
_cache = _connection.GetDatabase();
252267
}
253268
}
@@ -257,9 +272,31 @@ private void Connect()
257272
}
258273
}
259274

275+
private void PrepareConnection()
276+
{
277+
ValidateServerFeatures();
278+
TryRegisterProfiler();
279+
}
280+
281+
private void ValidateServerFeatures()
282+
{
283+
_ = _connection ?? throw new InvalidOperationException($"{nameof(_connection)} cannot be null.");
284+
285+
foreach (var endPoint in _connection.GetEndPoints())
286+
{
287+
if (_connection.GetServer(endPoint).Version < ServerVersionWithExtendedSetCommand)
288+
{
289+
_setScript = DeprecatedSetScript;
290+
return;
291+
}
292+
}
293+
}
294+
260295
private void TryRegisterProfiler()
261296
{
262-
if (_connection != null && _options.ProfilingSession != null)
297+
_ = _connection ?? throw new InvalidOperationException($"{nameof(_connection)} cannot be null.");
298+
299+
if (_options.ProfilingSession != null)
263300
{
264301
_connection.RegisterProfiler(_options.ProfilingSession);
265302
}

0 commit comments

Comments
 (0)