@@ -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