@@ -16,6 +16,14 @@ 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 added HSET key field value [field value ...] and deprecated HMSET.
23+ //
24+ // On Redis versions that don't have the newer HSET variant, we use SetScriptPreExtendedSetCommand
25+ // which uses the (now deprecated) HMSET.
26+
1927 // KEYS[1] = = key
2028 // ARGV[1] = absolute-expiration - ticks as long (-1 for none)
2129 // ARGV[2] = sliding-expiration - ticks as long (-1 for none)
@@ -28,14 +36,22 @@ public class RedisCache : IDistributedCache, IDisposable
2836 redis.call('EXPIRE', KEYS[1], ARGV[3])
2937 end
3038 return 1" ) ;
39+ private const string SetScriptPreExtendedSetCommand = ( @"
40+ redis.call('HMSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4])
41+ if ARGV[3] ~= '-1' then
42+ redis.call('EXPIRE', KEYS[1], ARGV[3])
43+ end
44+ return 1" ) ;
3145 private const string AbsoluteExpirationKey = "absexp" ;
3246 private const string SlidingExpirationKey = "sldexp" ;
3347 private const string DataKey = "data" ;
3448 private const long NotPresent = - 1 ;
49+ private static readonly Version ServerVersionWithExtendedSetCommand = new Version ( 4 , 0 , 0 ) ;
3550
3651 private volatile IConnectionMultiplexer _connection ;
3752 private IDatabase _cache ;
3853 private bool _disposed ;
54+ private string _setScript = SetScript ;
3955
4056 private readonly RedisCacheOptions _options ;
4157 private readonly string _instance ;
@@ -107,7 +123,7 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
107123
108124 var absoluteExpiration = GetAbsoluteExpiration ( creationTime , options ) ;
109125
110- var result = _cache . ScriptEvaluate ( SetScript , new RedisKey [ ] { _instance + key } ,
126+ var result = _cache . ScriptEvaluate ( _setScript , new RedisKey [ ] { _instance + key } ,
111127 new RedisValue [ ]
112128 {
113129 absoluteExpiration ? . Ticks ?? NotPresent ,
@@ -143,7 +159,7 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
143159
144160 var absoluteExpiration = GetAbsoluteExpiration ( creationTime , options ) ;
145161
146- await _cache . ScriptEvaluateAsync ( SetScript , new RedisKey [ ] { _instance + key } ,
162+ await _cache . ScriptEvaluateAsync ( _setScript , new RedisKey [ ] { _instance + key } ,
147163 new RedisValue [ ]
148164 {
149165 absoluteExpiration ? . Ticks ?? NotPresent ,
@@ -206,7 +222,7 @@ private void Connect()
206222 _connection = _options . ConnectionMultiplexerFactory ( ) . GetAwaiter ( ) . GetResult ( ) ;
207223 }
208224
209- TryRegisterProfiler ( ) ;
225+ PrepareConnection ( ) ;
210226 _cache = _connection . GetDatabase ( ) ;
211227 }
212228 }
@@ -247,7 +263,7 @@ private void Connect()
247263 _connection = await _options . ConnectionMultiplexerFactory ( ) ;
248264 }
249265
250- TryRegisterProfiler ( ) ;
266+ PrepareConnection ( ) ;
251267 _cache = _connection . GetDatabase ( ) ;
252268 }
253269 }
@@ -257,9 +273,31 @@ private void Connect()
257273 }
258274 }
259275
276+ private void PrepareConnection ( )
277+ {
278+ ValidateServerFeatures ( ) ;
279+ TryRegisterProfiler ( ) ;
280+ }
281+
282+ private void ValidateServerFeatures ( )
283+ {
284+ _ = _connection ?? throw new InvalidOperationException ( $ "{ nameof ( _connection ) } cannot be null.") ;
285+
286+ foreach ( var endPoint in _connection . GetEndPoints ( ) )
287+ {
288+ if ( _connection . GetServer ( endPoint ) . Version < ServerVersionWithExtendedSetCommand )
289+ {
290+ _setScript = SetScriptPreExtendedSetCommand ;
291+ return ;
292+ }
293+ }
294+ }
295+
260296 private void TryRegisterProfiler ( )
261297 {
262- if ( _connection != null && _options . ProfilingSession != null )
298+ _ = _connection ?? throw new InvalidOperationException ( $ "{ nameof ( _connection ) } cannot be null.") ;
299+
300+ if ( _options . ProfilingSession != null )
263301 {
264302 _connection . RegisterProfiler ( _options . ProfilingSession ) ;
265303 }
0 commit comments