/// <summary> /// Applies the values of an existing <see cref="MemoryCacheEntryOptions"/> to the entry. /// </summary> /// <param name="entry">The <see cref="ICacheEntry"/>.</param> /// <param name="options">Set the values of these options on the <paramref name="entry"/>.</param> /// <returns>The <see cref="ICacheEntry"/> for chaining.</returns> public static ICacheEntry SetOptions(this ICacheEntry entry, MemoryCacheEntryOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } entry.AbsoluteExpiration = options.AbsoluteExpiration; entry.AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow; entry.SlidingExpiration = options.SlidingExpiration; entry.Priority = options.Priority; entry.Size = options.Size; foreach (IChangeToken expirationToken in options.ExpirationTokens) { entry.AddExpirationToken(expirationToken); } foreach (PostEvictionCallbackRegistration postEvictionCallback in options.PostEvictionCallbacks) { entry.RegisterPostEvictionCallback(postEvictionCallback.EvictionCallback, postEvictionCallback.State); } return(entry); }
/// <summary> /// Applies the values of an existing <see cref="MemoryCacheEntryOptions{TKey}"/> to the entry. /// </summary> /// <param name="entry">The <see cref="ICacheEntry{TKey}"/>.</param> /// <param name="options">Set the values of these options on the <paramref name="entry"/>.</param> /// <returns>The <see cref="ICacheEntry{TKey}"/> for chaining.</returns> public static ICacheEntry <TKey> SetOptions <TKey>(this ICacheEntry <TKey> entry, MemoryCacheEntryOptions <TKey> options) where TKey : notnull { if (options == null) { throw new ArgumentNullException(nameof(options)); } entry.AbsoluteExpiration = options.AbsoluteExpiration; entry.AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow; entry.SlidingExpiration = options.SlidingExpiration; entry.Priority = options.Priority; entry.Size = options.Size; foreach (var expirationToken in options.ExpirationTokens) { entry.AddExpirationToken(expirationToken); } foreach (var postEvictionCallback in options.PostEvictionCallbacks) { entry.RegisterPostEvictionCallback(postEvictionCallback.EvictionCallback, postEvictionCallback.State); } return(entry); }
private FileWatcherData CreateFileWatcherForIntegration(ICacheEntry cacheEntry, string integrationName, string directory, IntegrationFilesModifiedDelegate callback) { var fileWatcher = new FileSystemWatcher(directory) { NotifyFilter = NotifyFilters.Attributes | NotifyFilters.FileName | NotifyFilters.LastWrite, IncludeSubdirectories = false, EnableRaisingEvents = true }; // Add observables for each of the event types. // Each of them just returns a boolean because we need a shared return type from the observable. // We only care that *something* changed, we don't care what actually changed. var changedFileObserable = Observable.FromEventPattern <FileSystemEventHandler, FileSystemEventArgs>(h => fileWatcher.Changed += h, h => fileWatcher.Changed -= h) .Select(x => true); var createdFileObserable = Observable.FromEventPattern <FileSystemEventHandler, FileSystemEventArgs>(h => fileWatcher.Created += h, h => fileWatcher.Created -= h) .Select(x => true); var deletedFileObserable = Observable.FromEventPattern <FileSystemEventHandler, FileSystemEventArgs>(h => fileWatcher.Deleted += h, h => fileWatcher.Deleted -= h) .Select(x => true); var renamedFileObserable = Observable.FromEventPattern <RenamedEventHandler, RenamedEventArgs>(h => fileWatcher.Renamed += h, h => fileWatcher.Renamed -= h) .Select(x => true); // Merge all the different file watcher events into 1 observable // This allows us to throttle all the events coming through so we don't get more than 1 per second. var fileWatcherSubscription = Observable.Merge(changedFileObserable, createdFileObserable, deletedFileObserable, renamedFileObserable) .Throttle(TimeSpan.FromSeconds(1)) .Subscribe(_ => callback?.Invoke(directory, integrationName)); cacheEntry.RegisterPostEvictionCallback(this.OnCacheEntryEvicted); this.ApplicationLifetime.ApplicationStopping.Register(() => this.RemoveFileWatcher(integrationName)); return(new FileWatcherData(fileWatcher, fileWatcherSubscription)); }
private Task <DiscordWebhookClient> MakeCachedClientFor(ICacheEntry entry, IWebhook webhook) { _logger.Information("Client for {Webhook} not found in cache, creating", webhook.Id); // Define expiration for the client cache // 10 minutes *without a query* and it gets yoten entry.SlidingExpiration = TimeSpan.FromMinutes(10); // IMemoryCache won't automatically dispose of its values when the cache gets evicted // so we register a hook to do so here. entry.RegisterPostEvictionCallback((key, value, reason, state) => (value as IDisposable)?.Dispose()); // DiscordWebhookClient has a sync network call in its constructor (!!!) // and we want to punt that onto a task queue, so we do that. return(Task.Run(async() => { try { return new DiscordWebhookClient(webhook); } catch (InvalidOperationException) { // TODO: does this leak stuff inside the DiscordWebhookClient created above? // Webhook itself was found in cache, but has been deleted on the channel // We request a new webhook instead return new DiscordWebhookClient(await _webhookCache.InvalidateAndRefreshWebhook(webhook)); } })); }
/// <summary> /// The given callback will be fired after the cache entry is evicted from the cache. /// </summary> /// <param name="entry">The <see cref="ICacheEntry"/>.</param> /// <param name="callback">The callback to run after the entry is evicted.</param> /// <returns>The <see cref="ICacheEntry"/> for chaining.</returns> public static ICacheEntry RegisterPostEvictionCallback( this ICacheEntry entry, PostEvictionDelegate callback) { ThrowHelper.ThrowIfNull(callback); return(entry.RegisterPostEvictionCallback(callback, state: null)); }
/// <summary> /// The given callback will be fired after the cache entry is evicted from the cache. /// </summary> /// <param name="entry"></param> /// <param name="callback"></param> public static ICacheEntry RegisterPostEvictionCallback(this ICacheEntry entry, PostEvictionDelegate callback) { if (callback == null) { throw new ArgumentNullException("callback"); } return(entry.RegisterPostEvictionCallback(callback, (object)null)); }
/// <summary> /// The given callback will be fired after the cache entry is evicted from the cache. /// </summary> /// <param name="entry">The <see cref="ICacheEntry{TKey}"/>.</param> /// <param name="callback">The callback to run after the entry is evicted.</param> /// <returns>The <see cref="ICacheEntry{TKey}"/> for chaining.</returns> public static ICacheEntry <TKey> RegisterPostEvictionCallback <TKey>( this ICacheEntry <TKey> entry, PostEvictionDelegate <TKey> callback) where TKey : notnull { if (callback == null) { throw new ArgumentNullException(nameof(callback)); } return(entry.RegisterPostEvictionCallback(callback, state: null)); }
/// <summary> /// Create or overwrite an entry in the cache and add key to Dictionary. /// </summary> /// <param name="key">An object identifying the entry.</param> /// <returns>The newly created <see cref="T:Microsoft.Extensions.Caching.Memory.ICacheEntry" /> instance.</returns> public ICacheEntry CreateEntry(object key) { ICacheEntry entry = _memoryCache.CreateEntry(key); entry.RegisterPostEvictionCallback(PostEvictionCallback); _cacheEntries.AddOrUpdate(key, entry, (o, cacheEntry) => { cacheEntry.Value = entry; return(cacheEntry); }); return(entry); }
/// <summary> /// Create or overwrite a empty/blank entry in the cache and add key to Dictionary. IMemoryCache /// </summary> /// <param name="cacheKey">An object identifying the entry.</param> /// <returns>The newly created <see cref="T:Microsoft.Extensions.Caching.Memory.ICacheEntry" /> instance.</returns> private static ICacheEntry CreateEntry(string cacheKey) { // Create empty value for key. Value filled in by caller. like IMemoryCache. // ASSUME lock (_cacheKeys) if (_memoryCache == null) { Init(null); } ICacheEntry entry = _memoryCache.CreateEntry(cacheKey); _cacheKeys.Add(cacheKey); // add or replace. entry.RegisterPostEvictionCallback(PostEvictionCallback); // CacheEntryExtensions return(entry); }
private IDocumentStore SetEntry(ICacheEntry cacheEntry, Guid userId) { cacheEntry.SlidingExpiration = SlidingExpirationSpan; cacheEntry.RegisterPostEvictionCallback( (key, value, reason, state) => { DisposeEntry(value); }); var databaseName = GetDatabaseName(userId); return(_documentStoreHolder.CreateStore(databaseName)); }
private void RegisterEvictionCallback(long?size, ICacheEntry entry) { entry.RegisterPostEvictionCallback(EvictionCallback); void EvictionCallback(object callbackObjKey, object callbackObjValue, EvictionReason reason, object ___) { if (size.HasValue) { Interlocked.Add(ref _totalSize, -(int)size.Value); } Interlocked.Decrement(ref _partitionCount); if (reason == EvictionReason.Capacity) { _logger.LogWarning("Cache Item removed due to Capacity. {Key} {Value}", callbackObjKey, callbackObjValue); } } }
private SemaphoreSlim GetSemaphore(string key, string operationId, ILogger?logger) { object _semaphore; if (_lockCache.TryGetValue(key, out _semaphore)) { return((SemaphoreSlim)_semaphore); } lock (_lockPool[GetLockIndex(key)]) { if (_lockCache.TryGetValue(key, out _semaphore)) { return((SemaphoreSlim)_semaphore); } _semaphore = new SemaphoreSlim(1, 1); using ICacheEntry entry = _lockCache.CreateEntry(key); entry.Value = _semaphore; entry.SlidingExpiration = _slidingExpiration; entry.RegisterPostEvictionCallback((key, value, reason, state) => { try { ((SemaphoreSlim)value).Dispose(); } catch (Exception exc) { if (logger?.IsEnabled(LogLevel.Warning) ?? false) { logger.LogWarning(exc, "FUSION (K={CacheKey}): an error occurred while trying to dispose a SemaphoreSlim in the reactor", key); } } }); return((SemaphoreSlim)_semaphore); } }
private async Task <IntegrationHostData> CreateAndStartIntegrationHost(ICacheEntry cacheEntry, IntegrationRegistration integrationRegistration, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "Startup cancelled." }); } // Pull a fresh copy of the IntegrationConfiguration to do some validation var integrationConfiguration = integrationRegistration.IntegrationConfiguration; var integrationName = integrationConfiguration.Name; var entryAssembly = integrationConfiguration.EntryAssembly; if (integrationName is null) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "Integration Name not set properly in configuration." }); } if (entryAssembly is null) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "Entry assembly not set properly in configuration." }); } if (!integrationConfiguration.IsEnabled) { this.Logger.LogInformation("Did not start {integrationName}. The Integration is currently disabled.", integrationName); return(new IntegrationHostData(null, HostStatus.Disabled)); } var entryAssemblyPath = Path.Combine(integrationRegistration.Directory, entryAssembly); if (!File.Exists(entryAssemblyPath)) { this.Logger.LogError("Failed to Start {integrationName} because the entry assembly {entryAssemblyPath} could not be found", integrationName, entryAssemblyPath); return(new IntegrationHostData(null, HostStatus.Errored) { Message = $"Failed to Start integration {integrationName} because the entry assembly {entryAssemblyPath} could not be found" }); } // Use a custom Assembly load context to allow each Host to use its own dependencies. // If a custom dependency is not defined in the same folder, it will fallback to using any Assemblies loaded in the default context. // The only requirement is that the Mifs.dll cannot be different because that would result in different instances of IInitializationInstance's // making FindAndCreateIntegrationInitializationInstance never find a type to instantiate. var assemblyLoadContext = new IntegrationAssemblyLoadContext(integrationRegistration.Directory); var bootstrapInstance = this.FindAndCreateIntegrationBootstrapInstance(entryAssemblyPath, AssemblyLoadContext.Default); if (bootstrapInstance is null) { return(new IntegrationHostData(null, HostStatus.Errored) { Message = "" }); } var host = await this.IntegrationHostFactory.CreateAndStart(bootstrapInstance, integrationRegistration.Configuration, cancellationToken); if (host is null) { return(new IntegrationHostData(host, HostStatus.Errored) { Message = $"Host failed to create" }); } // Make sure that when the host is shut down the assembly load context gets unloaded. var applicationLifetime = host.Services.GetRequiredService <IHostApplicationLifetime>(); applicationLifetime.ApplicationStopping.Register(() => assemblyLoadContext.Unload()); cacheEntry.RegisterPostEvictionCallback(this.OnCacheEntryEvicted); this.Logger.LogInformation("{integrationName} Started.", integrationName); return(new IntegrationHostData(host, HostStatus.Started)); }