public SemaphoreBulkheadHolder(GroupKey key, IMetricEvents metricEvents, MjolnirConfiguration config, IMjolnirLogFactory logFactory) { _key = key; _metricEvents = metricEvents ?? throw new ArgumentNullException(nameof(metricEvents)); _config = config ?? throw new ArgumentNullException(nameof(config)); if (logFactory == null) { throw new ArgumentNullException(nameof(logFactory)); } _log = logFactory.CreateLog <SemaphoreBulkheadHolder>(); if (_log == null) { throw new InvalidOperationException($"{nameof(IMjolnirLogFactory)} implementation returned null from {nameof(IMjolnirLogFactory.CreateLog)} for type {typeof(SemaphoreBulkheadHolder)}, please make sure the implementation returns a non-null log for all calls to {nameof(IMjolnirLogFactory.CreateLog)}"); } // The order of things here is very intentional. // We get the MaxConcurrent value first and then initialize the semaphore bulkhead. // The change handler is registered after that. The order ought to help avoid a // situation where we might fire a config change handler before we add the // semaphore to the dictionary, potentially trying to add two entries with // different values in rapid succession. var value = _config.GetBulkheadConfiguration(key.Name).MaxConcurrent; _bulkhead = new SemaphoreBulkhead(_key, value); // On change, we'll replace the bulkhead. The assumption here is that a caller // using the bulkhead will have kept a local reference to the bulkhead that they // acquired a lock on, and will release the lock on that bulkhead and not one that // has been replaced after a config change. _config.OnConfigurationChanged(c => c.GetBulkheadConfiguration(key.Name).MaxConcurrent, UpdateMaxConcurrent); }
public void GetMaxConcurrent_UsesSpecificValueIfConfigured() { // Arrange var groupKey = AnyGroupKey; var expectedConfigValue = AnyPositiveInt; var config = new MjolnirConfiguration { BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration> { { groupKey.Name, new BulkheadConfiguration { MaxConcurrent = expectedConfigValue } } } }; // Act var value = config.GetBulkheadConfiguration(groupKey.Name).MaxConcurrent; // Assert Assert.Equal(expectedConfigValue, value); }
public void GetMaxConcurrent_UsesDefaultValueIfNoSpecificValueConfigured() { // Arrange var groupKey = AnyGroupKey; var expectedConfigValue = AnyPositiveInt; var config = new MjolnirConfiguration { DefaultBulkheadConfiguration = new BulkheadConfiguration { MaxConcurrent = expectedConfigValue } }; // Act var value = config.GetBulkheadConfiguration(groupKey.Name).MaxConcurrent; // Assert Assert.Equal(expectedConfigValue, value); }
// ReSharper restore PrivateFieldCanBeConvertedToLocalVariable public BulkheadFactory(IMetricEvents metricEvents, MjolnirConfiguration config, IMjolnirLogFactory logFactory) { // No null checks on parameters; we don't use them, we're just passing them through to // the objects we're creating. _metricEvents = metricEvents; _config = config; _logFactory = logFactory ?? throw new ArgumentNullException(nameof(logFactory)); var log = logFactory.CreateLog <BulkheadFactory>(); if (log == null) { throw new InvalidOperationException($"{nameof(IMjolnirLogFactory)} implementation returned null from {nameof(IMjolnirLogFactory.CreateLog)} for type {typeof(BulkheadFactory)}, please make sure the implementation returns a non-null log for all calls to {nameof(IMjolnirLogFactory.CreateLog)}"); } _timer = new GaugeTimer(state => { try { var keys = _bulkheads.Keys; foreach (var key in keys) { if (_bulkheads.TryGetValue(key, out Lazy <SemaphoreBulkheadHolder> holder) && holder.IsValueCreated) { var bulkhead = holder.Value.Bulkhead; _metricEvents.BulkheadGauge(bulkhead.Name, "semaphore", _config.GetBulkheadConfiguration(key.Name).MaxConcurrent, bulkhead.CountAvailable); } } } catch (Exception e) { log.Error($"Error sending {nameof(IMetricEvents.BulkheadGauge)} metric event", e); } }); }
public void GetMaxConcurrent_UsesDefaultValueIfNoSpecificValueOrDefaultValueConfigured_DefaultIs10() { // Arrange const int expectedDefaultMaxConcurrent = 10; var groupKey = AnyGroupKey; var config = new MjolnirConfiguration { DefaultBulkheadConfiguration = new BulkheadConfiguration { MaxConcurrent = expectedDefaultMaxConcurrent } }; // Act var value = config.GetBulkheadConfiguration(groupKey.Name).MaxConcurrent; // Assert Assert.Equal(expectedDefaultMaxConcurrent, value); }