public async Task ZeroTimeoutDoesNotStartAsyncFuncAsync() { bool _hasRun = false; await Assert.ThrowsAsync <SyntheticTimeoutException>(async() => { await FusionCacheExecutionUtils.RunAsyncFuncWithTimeoutAsync(async ct => { _hasRun = true; return(42); }, TimeSpan.Zero, false, t => { }); }); Assert.False(_hasRun); }
public async Task CancelingAsyncFuncActuallyCancelsItAsync() { int res = -1; var factoryTerminated = false; var outerCancelDelayMs = 500; var innerDelayMs = 2_000; await Assert.ThrowsAsync <OperationCanceledException>(async() => { var cts = new CancellationTokenSource(outerCancelDelayMs); res = await FusionCacheExecutionUtils.RunAsyncFuncWithTimeoutAsync(async ct => { await Task.Delay(innerDelayMs); ct.ThrowIfCancellationRequested(); factoryTerminated = true; return(42); }, Timeout.InfiniteTimeSpan, true, token: cts.Token); }); await Task.Delay(innerDelayMs); Assert.Equal(-1, res); Assert.False(factoryTerminated); }
public async Task TimeoutEffectivelyWorksAsync() { int res = -1; var timeoutMs = 500; var innerDelayMs = 2_000; var sw = Stopwatch.StartNew(); await Assert.ThrowsAnyAsync <TimeoutException>(async() => { res = await FusionCacheExecutionUtils.RunAsyncFuncWithTimeoutAsync(async ct => { await Task.Delay(innerDelayMs); ct.ThrowIfCancellationRequested(); return(42); }, TimeSpan.FromMilliseconds(timeoutMs)); }); sw.Stop(); Assert.Equal(-1, res); Assert.True(sw.ElapsedMilliseconds >= timeoutMs); Assert.True(sw.ElapsedMilliseconds < innerDelayMs); }
private async Task <IFusionCacheEntry?> GetOrSetEntryInternalAsync <TValue>(string operationId, string key, Func <CancellationToken, Task <TValue> >?factory, FusionCacheEntryOptions?options, CancellationToken token) { if (options is null) { options = _options.DefaultEntryOptions; } token.ThrowIfCancellationRequested(); FusionCacheMemoryEntry?_memoryEntry; bool _memoryEntryIsValid; // DIRECTLY CHECK MEMORY CACHE (TO AVOID LOCKING) (_memoryEntry, _memoryEntryIsValid) = _mca.TryGetEntry <TValue>(operationId, key); if (_memoryEntryIsValid) { if (_logger?.IsEnabled(LogLevel.Trace) ?? false) { _logger.LogTrace("FUSION (K={CacheKey} OP={CacheOperationId}): using memory entry", key, operationId); } return(_memoryEntry); } var dca = GetCurrentDistributedAccessor(); // SHORT-CIRCUIT: NO FACTORY AND NO USABLE DISTRIBUTED CACHE if (factory is null && (dca?.IsCurrentlyUsable() ?? false) == false) { if (options.IsFailSafeEnabled && _memoryEntry is object) { if (_logger?.IsEnabled(LogLevel.Trace) ?? false) { _logger.LogTrace("FUSION (K={CacheKey} OP={CacheOperationId}): using memory entry (expired)", key, operationId); } return(_memoryEntry); } return(null); } IFusionCacheEntry?_entry; // LOCK var lockObj = await _reactor.AcquireLockAsync(key, operationId, options.LockTimeout, _logger, token).ConfigureAwait(false); try { // TRY AGAIN WITH MEMORY CACHE (AFTER THE LOCK HAS BEEN ACQUIRED, MAYBE SOMETHING CHANGED) (_memoryEntry, _memoryEntryIsValid) = _mca.TryGetEntry <TValue>(operationId, key); if (_memoryEntryIsValid) { if (_logger?.IsEnabled(LogLevel.Trace) ?? false) { _logger.LogTrace("FUSION (K={CacheKey} OP={CacheOperationId}): using memory entry", key, operationId); } return(_memoryEntry); } // TRY WITH DISTRIBUTED CACHE (IF ANY) FusionCacheDistributedEntry <TValue>?distributedEntry = null; bool distributedEntryIsValid = false; if (dca?.IsCurrentlyUsable() ?? false) { (distributedEntry, distributedEntryIsValid) = await dca.TryGetEntryAsync <TValue>(operationId, key, options, _memoryEntry is object, token).ConfigureAwait(false); } if (distributedEntryIsValid) { _entry = FusionCacheMemoryEntry.CreateFromOptions(distributedEntry !.Value, options, false); } else { TValue value; bool failSafeActivated = false; if (factory is null) { // NO FACTORY var fallbackEntry = MaybeGetFallbackEntry(operationId, key, distributedEntry, _memoryEntry, options, out failSafeActivated); if (fallbackEntry is object) { value = fallbackEntry.GetValue <TValue>(); } else { return(null); } } else { // FACTORY Task <TValue>?factoryTask = null; try { var timeout = options.GetAppropriateFactoryTimeout(_memoryEntry is object || distributedEntry is object); if (_logger?.IsEnabled(LogLevel.Debug) ?? false) { _logger.LogDebug("FUSION (K={CacheKey} OP={CacheOperationId}): calling the factory (timeout={Timeout})", key, operationId, timeout.ToLogString_Timeout()); } value = await FusionCacheExecutionUtils.RunAsyncFuncWithTimeoutAsync(ct => factory(ct), timeout, options.AllowTimedOutFactoryBackgroundCompletion == false, x => factoryTask = x, token).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception exc) { ProcessFactoryError(operationId, key, exc); MaybeBackgroundCompleteTimedOutFactory <TValue>(operationId, key, factoryTask, options, dca, token); var fallbackEntry = MaybeGetFallbackEntry(operationId, key, distributedEntry, _memoryEntry, options, out failSafeActivated); if (fallbackEntry is object) { value = fallbackEntry.GetValue <TValue>(); } else { throw; } } } _entry = FusionCacheMemoryEntry.CreateFromOptions(value, options, failSafeActivated); if ((dca?.IsCurrentlyUsable() ?? false) && failSafeActivated == false) { // SAVE IN THE DISTRIBUTED CACHE (BUT ONLY IF NO FAIL-SAFE HAS BEEN EXECUTED) await dca.SetEntryAsync <TValue>(operationId, key, _entry, options, token).ConfigureAwait(false); } } // SAVING THE DATA IN THE MEMORY CACHE (EVEN IF IT IS FROM FAIL-SAFE) if (_entry is object) { _mca.SetEntry <TValue>(operationId, key, _entry.AsMemoryEntry(), options); } } finally { ReleaseLock(operationId, key, lockObj); } return(_entry); }