Example #1
0
        /// <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);
        }
Example #2
0
        /// <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);
        }
Example #3
0
        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));
        }
Example #6
0
        /// <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));
        }
Example #7
0
        /// <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));
        }
Example #8
0
        /// <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);
        }
Example #9
0
        /// <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);
        }
Example #10
0
        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));
        }