/// <inheritdoc/> public void Set <TValue>(string key, TValue value, FusionCacheEntryOptions?options = null, CancellationToken token = default) { ValidateCacheKey(key); token.ThrowIfCancellationRequested(); MaybeProcessCacheKey(ref key); if (options is null) { options = _options.DefaultEntryOptions; } var operationId = GenerateOperationId(); if (_logger?.IsEnabled(LogLevel.Debug) ?? false) { _logger.LogDebug("FUSION (K={CacheKey} OP={CacheOperationId}): calling Set<T> {Options}", key, operationId, options.ToLogString()); } var entry = FusionCacheMemoryEntry.CreateFromOptions(value, options, false); _mca.SetEntry <TValue>(operationId, key, entry, options); var dca = GetCurrentDistributedAccessor(); if ((dca?.IsCurrentlyUsable() ?? false) == false) { return; } dca.SetEntry <TValue>(operationId, key, entry, options, token); }
private void MaybeBackgroundCompleteTimedOutFactory <TValue>(string operationId, string key, Task <TValue>?factoryTask, FusionCacheEntryOptions options, DistributedCacheAccessor?dca, CancellationToken token) { if (options.AllowTimedOutFactoryBackgroundCompletion == false || factoryTask is null) { return; } if (factoryTask.IsFaulted) { if (_logger?.IsEnabled(_options.FactoryErrorsLogLevel) ?? false) { _logger.Log(_options.FactoryErrorsLogLevel, factoryTask.Exception.GetSingleInnerExceptionOrSelf(), "FUSION (K={CacheKey} OP={CacheOperationId}): a timed-out factory thrown an exception", key, operationId); } return; } // CONTINUE IN THE BACKGROUND TO TRY TO KEEP THE RESULT AS SOON AS IT WILL COMPLETE SUCCESSFULLY if (_logger?.IsEnabled(LogLevel.Debug) ?? false) { _logger.LogDebug("FUSION (K={CacheKey} OP={CacheOperationId}): trying to complete the timed-out factory in the background", key, operationId); } _ = factoryTask.ContinueWith(antecedent => { if (antecedent.Status == TaskStatus.Faulted) { if (_logger?.IsEnabled(_options.FactoryErrorsLogLevel) ?? false) { _logger.Log(_options.FactoryErrorsLogLevel, antecedent.Exception.GetSingleInnerExceptionOrSelf(), "FUSION (K={CacheKey} OP={CacheOperationId}): a timed-out factory thrown an exception", key, operationId); } } else if (antecedent.Status == TaskStatus.RanToCompletion) { if (_logger?.IsEnabled(LogLevel.Debug) ?? false) { _logger.LogDebug("FUSION (K={CacheKey} OP={CacheOperationId}): a timed-out factory successfully completed in the background: keeping the result", key, operationId); } var lateEntry = FusionCacheMemoryEntry.CreateFromOptions(antecedent.Result, options, false); _ = dca?.SetEntryAsync <TValue>(operationId, key, lateEntry, options, token); _mca.SetEntry <TValue>(operationId, key, lateEntry, options); } }); }
private IFusionCacheEntry?GetOrSetEntryInternal <TValue>(string operationId, string key, Func <CancellationToken, 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 = _reactor.AcquireLock(key, operationId, options.LockTimeout, _logger); 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) = dca.TryGetEntry <TValue>(operationId, key, options, _memoryEntry is object, token); } 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 = FusionCacheExecutionUtils.RunSyncFuncWithTimeout(ct => factory(ct), timeout, options.AllowTimedOutFactoryBackgroundCompletion == false, x => factoryTask = x, token); } 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) dca.SetEntry <TValue>(operationId, key, _entry, options); } } // 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); }