Skip to content

Commit 22d85df

Browse files
committed
Cache softtimeout
1 parent 71526c6 commit 22d85df

1 file changed

Lines changed: 44 additions & 3 deletions

File tree

Tests/Cache.cs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public RefreshingCache(TimeSpan duration, double eagerRefreshRatio = 0.5, TimeSp
216216
{
217217
_durationTicks = (long)(duration.TotalSeconds * Stopwatch.Frequency);
218218
_eagerRefreshTicks = (long)(_durationTicks * Math.Clamp(eagerRefreshRatio, 0.0, 1.0));
219-
_softTimeout = softTimeout ?? TimeSpan.Zero;
219+
_softTimeout = softTimeout ?? Timeout.InfiniteTimeSpan;
220220
if (remove is { } r) _timer = new Timer(Cleanup, (long)(r.TotalSeconds * Stopwatch.Frequency), r * 1.25, r * 0.25);
221221
}
222222

@@ -243,13 +243,54 @@ public async ValueTask<V> GetOrAdd(K key, Func<K, Task<V>> factory)
243243
if (age >= _eagerRefreshTicks)
244244
{
245245
var updateTask = _cache.Update(key, factory, CallFactory);
246-
if (age >= _durationTicks && (_softTimeout == TimeSpan.Zero || await Task.WhenAny(updateTask, Task.Delay(_softTimeout)) == updateTask))
247-
try { result = await updateTask; } catch { }
246+
if (age >= _durationTicks)
247+
{
248+
try
249+
{
250+
result = _softTimeout == Timeout.InfiniteTimeSpan ? await updateTask : await updateTask.WithDefault(result, _softTimeout);
251+
}
252+
catch { }
253+
}
248254
else
249255
_ = updateTask.ContinueWith(static t => _ = t.Exception, TaskContinuationOptions.OnlyOnFaulted);
250256
}
251257
return result.Value;
252258
}
253259

254260
public void Dispose() => _timer?.Dispose();
261+
}
262+
263+
public static class WithDefaultAwaiter
264+
{
265+
/// <summary>Awaits a task returning a default value if it errors or times out.</summary>
266+
public static WithDefaultAwaiter<T> WithDefault<T>(this Task<T> task, T defaultValue, TimeSpan timeout) => new(task, defaultValue, timeout);
267+
}
268+
269+
public sealed class WithDefaultAwaiter<T>(Task<T> task, T defaultValue, TimeSpan timeout) : ICriticalNotifyCompletion
270+
{
271+
private Action? _continuation;
272+
private Timer? _timer;
273+
public WithDefaultAwaiter<T> GetAwaiter() => this;
274+
public bool IsCompleted => task.IsCompleted;
275+
public T GetResult() => task.IsCompletedSuccessfully ? task.Result : defaultValue;
276+
public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation);
277+
public void UnsafeOnCompleted(Action continuation)
278+
{
279+
_continuation = continuation;
280+
_timer = new Timer(static s => ((WithDefaultAwaiter<T>)s!).Complete(), this, timeout, Timeout.InfiniteTimeSpan);
281+
task.ContinueWith(static (t, s) =>
282+
{
283+
((WithDefaultAwaiter<T>)s!).Complete();
284+
if (t.IsFaulted) _ = t.Exception;
285+
}, this, TaskContinuationOptions.ExecuteSynchronously);
286+
}
287+
private void Complete()
288+
{
289+
if (Interlocked.Exchange(ref _continuation, null) is { } continuation)
290+
{
291+
try { continuation(); }
292+
catch { }
293+
_timer?.Dispose();
294+
}
295+
}
255296
}

0 commit comments

Comments
 (0)