Skip to content

Commit 9df90bc

Browse files
committed
simplify sync batch wait (tiny race conditions) made the old approach brittle)
1 parent 0b99297 commit 9df90bc

File tree

1 file changed

+5
-31
lines changed

1 file changed

+5
-31
lines changed

src/Caching/StackExchangeRedis/src/RedisCache.cs

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -155,37 +155,8 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
155155
var batch = cache.CreateBatch();
156156
var setFields = batch.HashSetAsync(prefixedKey, fields);
157157
var setTtl = batch.KeyExpireAsync(prefixedKey, TimeSpan.FromSeconds(ttl.GetValueOrDefault()));
158-
batch.Execute(); // synchronous wait-for-all
159-
160-
// we *expect* that they are both complete; if not, something is *already*
161-
// horribly wrong, so: we'll assert that
162-
if (setFields.IsCompleted && setTtl.IsCompleted)
163-
{
164-
// can check synchronously without adding a sync-over-async
165-
if (setFields.IsFaulted && setTtl.IsFaulted)
166-
{
167-
// both faulted? look at ttl so not "unobserved", and
168-
// use the error from the fiels as the primary fault
169-
try
170-
{
171-
setTtl.GetAwaiter().GetResult();
172-
}
173-
catch { } // this is a deliberate swallow; we know setFields is also doomed
174-
setFields.GetAwaiter().GetResult();
175-
}
176-
else
177-
{
178-
// at most one faulted; just check the results the simple way
179-
// (emphasis: they're already complete)
180-
setFields.GetAwaiter().GetResult();
181-
setTtl.GetAwaiter().GetResult();
182-
}
183-
}
184-
else
185-
{
186-
// something weird happened; we do not want to add a sync-over-async
187-
throw new InvalidOperationException("Batch did not complete setting cache entry");
188-
}
158+
batch.Execute(); // synchronous wait-for-all; the two tasks should be either complete or *literally about to* (race conditions)
159+
cache.WaitAll(setFields, setTtl); // note this applies usual SE.Redis timeouts etc
189160
}
190161
}
191162
catch (Exception ex)
@@ -555,6 +526,9 @@ private async Task RefreshAsync(IDatabase cache, string key, DateTimeOffset? abs
555526
}
556527
}
557528

529+
// it is not an oversight that this returns seconds rather than TimeSpan (which SE.Redis can accept directly); by
530+
// leaving this as an integer, we use TTL rather than PTTL, which has better compatibility between servers
531+
// (it also takes a handful fewer bytes, but that isn't a motivating factor)
558532
private static long? GetExpirationInSeconds(DateTimeOffset creationTime, DateTimeOffset? absoluteExpiration, DistributedCacheEntryOptions options)
559533
{
560534
if (absoluteExpiration.HasValue && options.SlidingExpiration.HasValue)

0 commit comments

Comments
 (0)