Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit d2179c4

Browse files
author
Nate McMaster
committed
Ensure Redis and SqlServer cache operations silently fail with a logged message
1 parent d13a5ce commit d2179c4

File tree

6 files changed

+260
-88
lines changed

6 files changed

+260
-88
lines changed

src/Microsoft.Extensions.Caching.Redis/RedisCache.cs

Lines changed: 118 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Threading.Tasks;
66
using Microsoft.Extensions.Caching.Distributed;
7+
using Microsoft.Extensions.Logging;
78
using Microsoft.Extensions.Options;
89
using StackExchange.Redis;
910

@@ -30,11 +31,16 @@ public class RedisCache : IDistributedCache, IDisposable
3031

3132
private ConnectionMultiplexer _connection;
3233
private IDatabase _cache;
34+
private readonly ILogger _logger;
3335

3436
private readonly RedisCacheOptions _options;
3537
private readonly string _instance;
3638

3739
public RedisCache(IOptions<RedisCacheOptions> optionsAccessor)
40+
: this(optionsAccessor, loggerFactory: null)
41+
{ }
42+
43+
public RedisCache(IOptions<RedisCacheOptions> optionsAccessor, ILoggerFactory loggerFactory)
3844
{
3945
if (optionsAccessor == null)
4046
{
@@ -45,6 +51,7 @@ public RedisCache(IOptions<RedisCacheOptions> optionsAccessor)
4551

4652
// This allows partitioning a single backend cache for use with multiple apps/services.
4753
_instance = _options.InstanceName ?? string.Empty;
54+
_logger = loggerFactory?.CreateLogger<RedisCache>();
4855
}
4956

5057
public byte[] Get(string key)
@@ -84,20 +91,27 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
8491
throw new ArgumentNullException(nameof(options));
8592
}
8693

87-
Connect();
94+
try
95+
{
96+
Connect();
8897

89-
var creationTime = DateTimeOffset.UtcNow;
98+
var creationTime = DateTimeOffset.UtcNow;
9099

91-
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options);
100+
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options);
92101

93-
var result = _cache.ScriptEvaluate(SetScript, new RedisKey[] { _instance + key },
94-
new RedisValue[]
95-
{
102+
var result = _cache.ScriptEvaluate(SetScript, new RedisKey[] { _instance + key },
103+
new RedisValue[]
104+
{
96105
absoluteExpiration?.Ticks ?? NotPresent,
97106
options.SlidingExpiration?.Ticks ?? NotPresent,
98107
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
99108
value
100-
});
109+
});
110+
}
111+
catch (Exception ex)
112+
{
113+
LogSuppressedException(ex);
114+
}
101115
}
102116

103117
public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options)
@@ -117,20 +131,27 @@ public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOption
117131
throw new ArgumentNullException(nameof(options));
118132
}
119133

120-
await ConnectAsync();
134+
try
135+
{
136+
await ConnectAsync();
121137

122-
var creationTime = DateTimeOffset.UtcNow;
138+
var creationTime = DateTimeOffset.UtcNow;
123139

124-
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options);
140+
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options);
125141

126-
await _cache.ScriptEvaluateAsync(SetScript, new RedisKey[] { _instance + key },
127-
new RedisValue[]
128-
{
142+
await _cache.ScriptEvaluateAsync(SetScript, new RedisKey[] { _instance + key },
143+
new RedisValue[]
144+
{
129145
absoluteExpiration?.Ticks ?? NotPresent,
130146
options.SlidingExpiration?.Ticks ?? NotPresent,
131147
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
132148
value
133-
});
149+
});
150+
}
151+
catch (Exception ex)
152+
{
153+
LogSuppressedException(ex);
154+
}
134155
}
135156

136157
public void Refresh(string key)
@@ -178,34 +199,41 @@ private byte[] GetAndRefresh(string key, bool getData)
178199
throw new ArgumentNullException(nameof(key));
179200
}
180201

181-
Connect();
182-
183-
// This also resets the LRU status as desired.
184-
// TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math.
185-
RedisValue[] results;
186-
if (getData)
202+
try
187203
{
188-
results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey);
189-
}
190-
else
191-
{
192-
results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey);
193-
}
204+
Connect();
194205

195-
// TODO: Error handling
196-
if (results.Length >= 2)
197-
{
198-
// Note we always get back two results, even if they are all null.
199-
// These operations will no-op in the null scenario.
200-
DateTimeOffset? absExpr;
201-
TimeSpan? sldExpr;
202-
MapMetadata(results, out absExpr, out sldExpr);
203-
Refresh(key, absExpr, sldExpr);
204-
}
206+
// This also resets the LRU status as desired.
207+
// TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math.
208+
RedisValue[] results;
209+
if (getData)
210+
{
211+
results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey);
212+
}
213+
else
214+
{
215+
results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey);
216+
}
205217

206-
if (results.Length >= 3 && results[2].HasValue)
218+
// TODO: Error handling
219+
if (results.Length >= 2)
220+
{
221+
// Note we always get back two results, even if they are all null.
222+
// These operations will no-op in the null scenario.
223+
DateTimeOffset? absExpr;
224+
TimeSpan? sldExpr;
225+
MapMetadata(results, out absExpr, out sldExpr);
226+
Refresh(key, absExpr, sldExpr);
227+
}
228+
229+
if (results.Length >= 3 && results[2].HasValue)
230+
{
231+
return results[2];
232+
}
233+
}
234+
catch (Exception ex)
207235
{
208-
return results[2];
236+
LogSuppressedException(ex);
209237
}
210238

211239
return null;
@@ -218,34 +246,41 @@ private async Task<byte[]> GetAndRefreshAsync(string key, bool getData)
218246
throw new ArgumentNullException(nameof(key));
219247
}
220248

221-
await ConnectAsync();
222-
223-
// This also resets the LRU status as desired.
224-
// TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math.
225-
RedisValue[] results;
226-
if (getData)
227-
{
228-
results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey);
229-
}
230-
else
249+
try
231250
{
232-
results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey);
233-
}
251+
await ConnectAsync();
234252

235-
// TODO: Error handling
236-
if (results.Length >= 2)
237-
{
238-
// Note we always get back two results, even if they are all null.
239-
// These operations will no-op in the null scenario.
240-
DateTimeOffset? absExpr;
241-
TimeSpan? sldExpr;
242-
MapMetadata(results, out absExpr, out sldExpr);
243-
await RefreshAsync(key, absExpr, sldExpr);
244-
}
253+
// This also resets the LRU status as desired.
254+
// TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math.
255+
RedisValue[] results;
256+
if (getData)
257+
{
258+
results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey);
259+
}
260+
else
261+
{
262+
results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey);
263+
}
264+
265+
// TODO: Error handling
266+
if (results.Length >= 2)
267+
{
268+
// Note we always get back two results, even if they are all null.
269+
// These operations will no-op in the null scenario.
270+
DateTimeOffset? absExpr;
271+
TimeSpan? sldExpr;
272+
MapMetadata(results, out absExpr, out sldExpr);
273+
await RefreshAsync(key, absExpr, sldExpr);
274+
}
245275

246-
if (results.Length >= 3 && results[2].HasValue)
276+
if (results.Length >= 3 && results[2].HasValue)
277+
{
278+
return results[2];
279+
}
280+
}
281+
catch (Exception ex)
247282
{
248-
return results[2];
283+
LogSuppressedException(ex);
249284
}
250285

251286
return null;
@@ -258,10 +293,16 @@ public void Remove(string key)
258293
throw new ArgumentNullException(nameof(key));
259294
}
260295

261-
Connect();
296+
try
297+
{
298+
Connect();
262299

263-
_cache.KeyDelete(_instance + key);
264-
// TODO: Error handling
300+
_cache.KeyDelete(_instance + key);
301+
}
302+
catch (Exception ex)
303+
{
304+
LogSuppressedException(ex);
305+
}
265306
}
266307

267308
public async Task RemoveAsync(string key)
@@ -270,11 +311,16 @@ public async Task RemoveAsync(string key)
270311
{
271312
throw new ArgumentNullException(nameof(key));
272313
}
314+
try
315+
{
316+
await ConnectAsync();
273317

274-
await ConnectAsync();
275-
276-
await _cache.KeyDeleteAsync(_instance + key);
277-
// TODO: Error handling
318+
await _cache.KeyDeleteAsync(_instance + key);
319+
}
320+
catch (Exception ex)
321+
{
322+
LogSuppressedException(ex);
323+
}
278324
}
279325

280326
private void MapMetadata(RedisValue[] results, out DateTimeOffset? absoluteExpiration, out TimeSpan? slidingExpiration)
@@ -380,6 +426,9 @@ private async Task RefreshAsync(string key, DateTimeOffset? absExpr, TimeSpan? s
380426
return absoluteExpiration;
381427
}
382428

429+
private void LogSuppressedException(Exception ex)
430+
=> _logger?.ExceptionSuppressed(ex);
431+
383432
public void Dispose()
384433
{
385434
if (_connection != null)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Microsoft.Extensions.Caching.Redis
8+
{
9+
internal static class RedisCacheLoggingExtensions
10+
{
11+
private static readonly Action<ILogger, Exception> _suppressedCacheException;
12+
13+
static RedisCacheLoggingExtensions()
14+
{
15+
_suppressedCacheException = LoggerMessage.Define(
16+
LogLevel.Debug,
17+
0,
18+
"An exception during cache operation was suppressed");
19+
}
20+
21+
public static void ExceptionSuppressed(this ILogger logger, Exception ex)
22+
{
23+
_suppressedCacheException(logger, ex);
24+
}
25+
}
26+
}

src/Microsoft.Extensions.Caching.Redis/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
},
2323
"dependencies": {
2424
"Microsoft.Extensions.Caching.Abstractions": "1.1.0-*",
25+
"Microsoft.Extensions.Logging.Abstractions": "1.1.0-*",
2526
"Microsoft.Extensions.Options": "1.1.0-*",
2627
"NETStandard.Library": "1.6.1-*",
2728
"StackExchange.Redis.StrongName": "1.1.605"

0 commit comments

Comments
 (0)