internal static bool IsValid(CacheableKeyRing keyRing, DateTime utcNow) { return(keyRing != null && !keyRing._expirationToken.IsCancellationRequested && keyRing.ExpirationTimeUtc > utcNow); }
internal IKeyRing GetCurrentKeyRingCore(DateTime utcNow, bool forceRefresh = false) { Debug.Assert(utcNow.Kind == DateTimeKind.Utc); // Can we return the cached keyring to the caller? CacheableKeyRing existingCacheableKeyRing = null; if (!forceRefresh) { existingCacheableKeyRing = Volatile.Read(ref _cacheableKeyRing); if (CacheableKeyRing.IsValid(existingCacheableKeyRing, utcNow)) { return(existingCacheableKeyRing.KeyRing); } } // The cached keyring hasn't been created or must be refreshed. We'll allow one thread to // update the keyring, and all other threads will continue to use the existing cached // keyring while the first thread performs the update. There is an exception: if there // is no usable existing cached keyring, all callers must block until the keyring exists. var acquiredLock = false; try { Monitor.TryEnter(_cacheableKeyRingLockObj, (existingCacheableKeyRing != null) ? 0 : Timeout.Infinite, ref acquiredLock); if (acquiredLock) { if (!forceRefresh) { // This thread acquired the critical section and is responsible for updating the // cached keyring. But first, let's make sure that somebody didn't sneak in before // us and update the keyring on our behalf. existingCacheableKeyRing = Volatile.Read(ref _cacheableKeyRing); if (CacheableKeyRing.IsValid(existingCacheableKeyRing, utcNow)) { return(existingCacheableKeyRing.KeyRing); } if (existingCacheableKeyRing != null) { _logger.LogDebug("Existing cached key ring is expired. Refreshing."); // original ILogger extensions calls replaced } } // It's up to us to refresh the cached keyring. // This call is performed *under lock*. CacheableKeyRing newCacheableKeyRing; try { newCacheableKeyRing = CacheableKeyRingProvider.GetCacheableKeyRing(utcNow); } catch (Exception ex) { if (existingCacheableKeyRing != null) { _logger.LogError(ex, "An error occurred while refreshing the key ring. Will try again in 2 minutes."); // original ILogger extensions calls replaced } else { _logger.LogError(ex, "An error occurred while reading the key ring."); // original ILogger extensions calls replaced } // Failures that occur while refreshing the keyring are most likely transient, perhaps due to a // temporary network outage. Since we don't want every subsequent call to result in failure, we'll // create a new keyring object whose expiration is now + some short period of time (currently 2 min), // and after this period has elapsed the next caller will try refreshing. If we don't have an // existing keyring (perhaps because this is the first call), then there's nothing to extend, so // each subsequent caller will keep going down this code path until one succeeds. if (existingCacheableKeyRing != null) { Volatile.Write(ref _cacheableKeyRing, existingCacheableKeyRing.WithTemporaryExtendedLifetime(utcNow)); } // The immediate caller should fail so that he can report the error up his chain. This makes it more likely // that an administrator can see the error and react to it as appropriate. The caller can retry the operation // and will probably have success as long as he falls within the temporary extension mentioned above. throw; } Volatile.Write(ref _cacheableKeyRing, newCacheableKeyRing); return(newCacheableKeyRing.KeyRing); } else { // We didn't acquire the critical section. This should only occur if we passed // zero for the Monitor.TryEnter timeout, which implies that we had an existing // (but outdated) keyring that we can use as a fallback. Debug.Assert(existingCacheableKeyRing != null); #pragma warning disable S2259 // Null pointers should not be dereferenced return(existingCacheableKeyRing.KeyRing); #pragma warning restore S2259 // Null pointers should not be dereferenced } } finally { if (acquiredLock) { Monitor.Exit(_cacheableKeyRingLockObj); } } }