public void FiresMetricEventWhenEnteringAndLeavingBulkheadAndCommandFails()
            {
                // Arrange

                var key      = AnyString;
                var groupKey = GroupKey.Named(key);

                var mockMetricEvents   = new Mock <IMetricEvents>();
                var mockBreakerInvoker = new Mock <IBreakerInvoker>();

                var mockBulkhead = new Mock <ISemaphoreBulkhead>();

                mockBulkhead.Setup(m => m.TryEnter()).Returns(true);
                mockBulkhead.SetupGet(m => m.Name).Returns(key);

                var mockBulkheadFactory = new Mock <IBulkheadFactory>(MockBehavior.Strict);

                mockBulkheadFactory.Setup(m => m.GetBulkhead(groupKey)).Returns(mockBulkhead.Object);

                var mockConfig = new MjolnirConfiguration {
                    UseCircuitBreakers = false
                };
                // The breaker invoker behavior doesn't matter here, we shouldn't get to the point
                // where we try to use it. Pass a "false" value for useCircuitBreakers to help
                // ensure that.
                var invoker = new BulkheadInvoker(mockBreakerInvoker.Object, mockBulkheadFactory.Object, mockMetricEvents.Object, mockConfig);
                var command = new ConfigurableKeyThrowingCommand(key);

                // Act + Assert

                Assert.Throws <ExpectedTestException>(() => invoker.ExecuteWithBulkhead(command, CancellationToken.None));

                mockMetricEvents.Verify(m => m.EnterBulkhead(key, command.Name));
                mockMetricEvents.Verify(m => m.LeaveBulkhead(key, command.Name));
            }
示例#2
0
        public void GetForceFixed_UsesDefaultValueIfNoSpecificValueOrDefaultValueConfigured_DefaultIsFalse()
        {
            // Arrange

            const bool expectedDefaultForceFixed = false;

            var groupKey = AnyGroupKey;

            var mockConfig = new MjolnirConfiguration
            {
                DefaultBreakerConfiguration = new BreakerConfiguration
                {
                    ForceFixed = expectedDefaultForceFixed
                }
            };
            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetForceFixed(groupKey);

            // Assert

            Assert.Equal(expectedDefaultForceFixed, value);
        }
示例#3
0
        // Default Timeout: The system default timeout (2 seconds). Used if nothing else is set.
        // Constructor Timeout: Value defined in the Command constructor.
        // Configured Timeout: Value provided by config.
        // Invocation Timeout: Value passed into the Invoke() / InvokeAsync() call.
        internal TimeSpan DetermineTimeout(MjolnirConfiguration config, long?invocationTimeoutMillis = null)
        {
            // Thoughts on invocation timeout vs. configured timeout:
            //
            // Generally, I'd recommend using one or the other, but not both. Configurable timeouts
            // are useful for changing the value at runtime, but are (at least in our environment)
            // shared across many services, so they don't permit fine-grained control over
            // individual calls. Invocation timeouts allow that fine-grained control, but require a
            // code deploy to change them.
            //
            // Basically, consider the balance between runtime control and fine-grained per-call
            // control when setting timeouts.


            // Prefer the invocation timeout first. It's more specific than the Constructor
            // Timeout (which is defined by the command author and is treated as a "catch-all"),
            // It's also more local than the Configured Timeout, which is a way to tune
            // the Constructor Timeout more specifically (i.e. still "catch-all" behavior).
            if (invocationTimeoutMillis.HasValue && invocationTimeoutMillis.Value >= 0)
            {
                return(TimeSpan.FromMilliseconds(invocationTimeoutMillis.Value));
            }

            var configured = config.GetCommandConfiguration(_name).Timeout;

            // We don't want to include 0 here. Since this comes from a potentially non-nullable
            // ConfigurableValue, it's possible (and probably likely) that an unconfigured
            // timeout will return a default(long), which will be 0.
            if (configured > 0)
            {
                return(TimeSpan.FromMilliseconds(configured));
            }

            return(_constructorTimeout);
        }
示例#4
0
        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);
        }
示例#5
0
        public void GetMinimumOperations_UsesDefaultValueIfNoSpecificValueOrDefaultValueConfigured_DefaultIs10()
        {
            // Arrange

            const long expectedDefaultMinimumOperations = 10;

            var       groupKey            = AnyGroupKey;
            const int expectedConfigValue = 10;

            var mockConfig = new MjolnirConfiguration
            {
                DefaultBreakerConfiguration = new BreakerConfiguration
                {
                    MinimumOperations = expectedConfigValue
                }
            };

            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetMinimumOperations(groupKey);

            // Assert

            Assert.Equal(expectedDefaultMinimumOperations, value);
        }
示例#6
0
 public BulkheadInvoker(IBreakerInvoker breakerInvoker, IBulkheadFactory bulkheadFactory, IMetricEvents metricEvents, MjolnirConfiguration config)
 {
     _breakerInvoker  = breakerInvoker ?? throw new ArgumentNullException(nameof(breakerInvoker));
     _bulkheadFactory = bulkheadFactory ?? throw new ArgumentNullException(nameof(bulkheadFactory));
     _metricEvents    = metricEvents ?? throw new ArgumentNullException(nameof(metricEvents));
     _config          = config ?? throw new ArgumentNullException(nameof(config));
 }
示例#7
0
 public CommandInvoker(
     MjolnirConfiguration config = null,
     IMjolnirLogFactory logFactory = null,
     IMetricEvents metricEvents = null,
     IBreakerExceptionHandler breakerExceptionHandler = null)
     : this(config, logFactory, metricEvents, breakerExceptionHandler, null)
 { }
示例#8
0
            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 async Task FiresMetricEventWhenRejected()
            {
                // Arrange

                var key      = AnyString;
                var groupKey = GroupKey.Named(key);

                var mockMetricEvents   = new Mock <IMetricEvents>();
                var mockBreakerInvoker = new Mock <IBreakerInvoker>();

                var mockBulkhead = new Mock <ISemaphoreBulkhead>();

                mockBulkhead.Setup(m => m.TryEnter()).Returns(false);
                mockBulkhead.SetupGet(m => m.Name).Returns(key);

                var mockBulkheadFactory = new Mock <IBulkheadFactory>(MockBehavior.Strict);

                mockBulkheadFactory.Setup(m => m.GetBulkhead(groupKey)).Returns(mockBulkhead.Object);

                var mockConfig = new MjolnirConfiguration {
                    UseCircuitBreakers = true
                };

                // The breaker invoker behavior doesn't matter here, we shouldn't get to the point
                // where we try to use it.
                var invoker = new BulkheadInvoker(mockBreakerInvoker.Object, mockBulkheadFactory.Object, mockMetricEvents.Object, mockConfig);
                var command = new ConfigurableKeyAsyncCommand(key);

                // Act + Assert

                await Assert.ThrowsAsync <BulkheadRejectedException>(() => invoker.ExecuteWithBulkheadAsync(command, CancellationToken.None));

                mockMetricEvents.Verify(m => m.RejectedByBulkhead(key, command.Name));
            }
            public void SetsExecutionTimeOnCommandWhenInvokedWithoutBreakerAndCommandFails()
            {
                // Arrange

                var key      = AnyString;
                var groupKey = GroupKey.Named(key);

                var mockMetricEvents   = new Mock <IMetricEvents>();
                var mockBreakerInvoker = new Mock <IBreakerInvoker>();

                var mockBulkhead = new Mock <ISemaphoreBulkhead>();

                mockBulkhead.Setup(m => m.TryEnter()).Returns(true);
                mockBulkhead.SetupGet(m => m.Name).Returns(key);

                var mockBulkheadFactory = new Mock <IBulkheadFactory>(MockBehavior.Strict);

                mockBulkheadFactory.Setup(m => m.GetBulkhead(groupKey)).Returns(mockBulkhead.Object);

                var mockConfig = new MjolnirConfiguration {
                    UseCircuitBreakers = false
                };
                // Pass false for useCircuitBreakers to bypass the breaker; we're testing that here.
                var invoker = new BulkheadInvoker(mockBreakerInvoker.Object, mockBulkheadFactory.Object, mockMetricEvents.Object, mockConfig);
                var command = new ConfigurableKeyThrowingCommand(key);

                // Act + Assert

                Assert.Throws <ExpectedTestException>(() => invoker.ExecuteWithBulkhead(command, CancellationToken.None));

                Assert.True(command.ExecutionTimeMillis > 0);
            }
示例#11
0
        public void Construct_WhenMaxConcurrentConfigIsInvalid_DoesSomething()
        {
            // Arrange

            var       key                  = AnyString;
            var       groupKey             = GroupKey.Named(key);
            const int invalidMaxConcurrent = -1;
            var       mockMetricEvents     = new Mock <IMetricEvents>(); // Not Strict: we're not testing the events here.

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        groupKey.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = invalidMaxConcurrent
                        }
                    }
                }
            };

            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(new DefaultMjolnirLog <SemaphoreBulkheadHolder>());

            // Act + Assert

            var exception = Assert.Throws <ArgumentOutOfRangeException>(() => new SemaphoreBulkheadHolder(groupKey, mockMetricEvents.Object, mockConfig, mockLogFactory.Object));

            Assert.Equal("maxConcurrent", exception.ParamName);
            Assert.Equal(invalidMaxConcurrent, exception.ActualValue);
        }
示例#12
0
        public void GetForceFixed_UsesSpecificValueIfConfigured()
        {
            // Arrange

            var groupKey            = AnyGroupKey;
            var expectedConfigValue = AnyBool;

            var mockConfig = new MjolnirConfiguration
            {
                BreakerConfigurations = new Dictionary <string, BreakerConfiguration>
                {
                    {
                        groupKey.Name,
                        new BreakerConfiguration
                        {
                            ForceFixed = expectedConfigValue
                        }
                    }
                }
            };

            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetForceFixed(groupKey);

            // Assert

            Assert.Equal(expectedConfigValue, value);
        }
示例#13
0
        public void GetThresholdPercentage_UsesDefaultValueIfNoSpecificValueOrDefaultValueConfigured_DefaultIs50()
        {
            // Arrange

            const int expectedDefaultThresholdPercentage = 50;

            var groupKey = AnyGroupKey;

            var mockConfig = new MjolnirConfiguration
            {
                DefaultBreakerConfiguration = new BreakerConfiguration
                {
                    ThresholdPercentage = expectedDefaultThresholdPercentage
                }
            };

            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetThresholdPercentage(groupKey);

            // Assert

            Assert.Equal(expectedDefaultThresholdPercentage, value);
        }
示例#14
0
        public void GetForceTripped_UsesDefaultValueIfNoSpecificValueConfigured()
        {
            // Arrange

            var groupKey            = AnyGroupKey;
            var expectedConfigValue = AnyBool;

            var mockConfig = new MjolnirConfiguration
            {
                DefaultBreakerConfiguration = new BreakerConfiguration
                {
                    ForceTripped = expectedConfigValue
                }
            };

            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetForceTripped(groupKey);

            // Assert

            Assert.Equal(expectedConfigValue, value);
        }
示例#15
0
        public void GetSnapshotTtlMillis_UsesDefaultValueIfNoSpecificValueOrDefaultValueConfigured_DefaultIs1000()
        {
            // Arrange

            const long expectedDefaultSnapshotTtlMillis = 1000;

            var groupKey = AnyGroupKey;

            var mockConfig = new MjolnirConfiguration
            {
                DefaultBreakerConfiguration = new BreakerConfiguration
                {
                    SnapshotTtlMillis = expectedDefaultSnapshotTtlMillis
                }
            };

            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetSnapshotTtlMillis(groupKey);

            // Assert

            Assert.Equal(expectedDefaultSnapshotTtlMillis, value);
        }
            private void TestTimeouts(TimeSpan expected, long?invocationMs, long configuredMs, long?constructorMs)
            {
                // The 'configured' value can't be nullable because the injected config
                // implementation may pass along a default(long) (i.e. 0) if the config value
                // isn't set, and we need to be prepared for that and not treat it as an actual
                // timeout.

                var constructorTs = (constructorMs == null
                    ? (TimeSpan?)null
                    : TimeSpan.FromMilliseconds(constructorMs.Value));
                var command = new TestCommand(AnyString, AnyString, AnyString, constructorTs);


                var mockConfig = new MjolnirConfiguration
                {
                    IsEnabled             = true,
                    IgnoreTimeouts        = false,
                    CommandConfigurations = new Dictionary <string, CommandConfiguration>
                    {
                        {
                            command.Name,
                            new CommandConfiguration
                            {
                                Timeout = configuredMs
                            }
                        }
                    }
                };

                var determined = command.DetermineTimeout(mockConfig, invocationMs);

                Assert.Equal(expected, determined);
            }
示例#17
0
        public void GetMinimumOperations_UsesDefaultValueIfNoSpecificValueConfigured()
        {
            // Arrange

            var groupKey            = AnyGroupKey;
            var expectedConfigValue = AnyPositiveInt;

            var mockConfig = new MjolnirConfiguration
            {
                DefaultBreakerConfiguration = new BreakerConfiguration
                {
                    MinimumOperations = expectedConfigValue
                }
            };

            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetMinimumOperations(groupKey);

            // Assert

            Assert.Equal(expectedConfigValue, value);
        }
示例#18
0
        public void GetSnapshotTtlMillis_UsesSpecificValueIfConfigured()
        {
            // Arrange

            var groupKey            = AnyGroupKey;
            var expectedConfigValue = AnyPositiveInt;

            var mockConfig = new MjolnirConfiguration
            {
                BreakerConfigurations = new Dictionary <string, BreakerConfiguration>
                {
                    {
                        groupKey.Name,
                        new BreakerConfiguration
                        {
                            SnapshotTtlMillis = expectedConfigValue
                        }
                    }
                }
            };
            var config = new FailurePercentageCircuitBreakerConfig(mockConfig);

            // Act

            var value = config.GetSnapshotTtlMillis(groupKey);

            // Assert

            Assert.Equal(expectedConfigValue, value);
        }
示例#19
0
        public void GetBulkhead_WhenInitializingBulkheadAndMaxConcurrentConfigIsInvalid_AndThenConfigChangedToValidValue_CreatesBulkhead()
        {
            // Arrange

            var       key = AnyGroupKey;
            const int invalidMaxConcurrent = -1;
            const int validMaxConcurrent   = 1;

            var mockMetricEvents = new Mock <IMetricEvents>(MockBehavior.Strict);

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        key.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = invalidMaxConcurrent
                        }
                    }
                }
            };

            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <BulkheadFactory>()).Returns(new DefaultMjolnirLog <BulkheadFactory>());
            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(new DefaultMjolnirLog <SemaphoreBulkheadHolder>());
            var factory = new BulkheadFactory(mockMetricEvents.Object, mockConfig, mockLogFactory.Object);

            try
            {
                factory.GetBulkhead(key);
            }
            catch (ArgumentOutOfRangeException)
            {
                // Expected, config is invalid for the first attempt.
            }

            mockConfig.BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
            {
                {
                    key.Name,
                    new BulkheadConfiguration
                    {
                        MaxConcurrent = validMaxConcurrent
                    }
                }
            };
            mockConfig.NotifyAfterConfigUpdate();

            // Act

            var bulkhead = factory.GetBulkhead(key); // Should not throw.

            // Assert

            Assert.Equal(validMaxConcurrent, bulkhead.CountAvailable);
        }
示例#20
0
        public void UpdateMaxConcurrent_ReplacesBulkhead()
        {
            // Arrange

            var       key = AnyGroupKey;
            const int initialExpectedCount = 5;
            const int newExpectedCount     = 6;
            var       mockMetricEvents     = new Mock <IMetricEvents>(MockBehavior.Strict);

            mockMetricEvents.Setup(m => m.BulkheadGauge(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>()));

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        key.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = initialExpectedCount
                        }
                    }
                }
            };

            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(new DefaultMjolnirLog <SemaphoreBulkheadHolder>());

            var holder = new SemaphoreBulkheadHolder(key, mockMetricEvents.Object, mockConfig, mockLogFactory.Object);

            // Act

            var firstBulkhead = holder.Bulkhead;

            holder.UpdateMaxConcurrent(newExpectedCount);

            var secondBulkhead = holder.Bulkhead;

            // Assert

            // Shouldn't change any existing referenced bulkheads...
            Assert.Equal(initialExpectedCount, firstBulkhead.CountAvailable);

            // ...but newly-retrieved bulkheads should get a new instance
            // with the updated count.
            Assert.Equal(newExpectedCount, secondBulkhead.CountAvailable);

            // And they shouldn't be the same bulkhead (which should be obvious by this point).
            Assert.False(firstBulkhead == secondBulkhead);
        }
示例#21
0
        public void Construct_ThrowsIfNullLogFactory()
        {
            // Arrange

            var key = AnyGroupKey;
            var mockMetricEvents = new Mock <IMetricEvents>(MockBehavior.Strict);
            var mockConfig       = new MjolnirConfiguration();

            // Act + Assert

            var exception = Assert.Throws <ArgumentNullException>(() => new SemaphoreBulkheadHolder(key, mockMetricEvents.Object, mockConfig, null));

            Assert.Equal("logFactory", exception.ParamName);
        }
示例#22
0
        public void UpdateMaxConcurrent_IgnoresInvalidValues()
        {
            // Arrange

            var       key = AnyGroupKey;
            const int invalidMaxConcurrent = -1;

            var mockMetricEvents = new Mock <IMetricEvents>(MockBehavior.Strict);

            mockMetricEvents.Setup(m => m.BulkheadGauge(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>()));

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        key.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = AnyPositiveInt
                        }
                    }
                }
            };

            var mockLog = new Mock <IMjolnirLog <SemaphoreBulkheadHolder> >(MockBehavior.Strict);

            mockLog.Setup(m => m.Error(It.IsAny <string>()));

            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(mockLog.Object);

            var holder = new SemaphoreBulkheadHolder(key, mockMetricEvents.Object, mockConfig, mockLogFactory.Object);

            // Act

            var initialBulkhead = holder.Bulkhead;

            holder.UpdateMaxConcurrent(invalidMaxConcurrent);

            // Assert

            // Bulkhead should be unchanged.
            Assert.True(initialBulkhead == holder.Bulkhead);
            mockLog.Verify(m => m.Error($"Semaphore bulkhead config for key {key.Name} changed to an invalid limit of {invalidMaxConcurrent}, the bulkhead will not be changed"), Times.Once);
        }
示例#23
0
        public void Construct_InitializesConfigGauge_GaugeFiresForOneBulkhead()
        {
            // Arrange

            var key      = AnyString;
            var groupKey = GroupKey.Named(key);
            var expectedMaxConcurrent = AnyPositiveInt;
            var mockMetricEvents      = new Mock <IMetricEvents>(MockBehavior.Strict);

            mockMetricEvents.Setup(m => m.BulkheadGauge(groupKey.Name, "semaphore", expectedMaxConcurrent, It.IsAny <int>()));

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        groupKey.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = expectedMaxConcurrent
                        }
                    }
                }
            };

            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <BulkheadFactory>()).Returns(new DefaultMjolnirLog <BulkheadFactory>());
            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(new DefaultMjolnirLog <SemaphoreBulkheadHolder>());
            // Act

            var factory = new BulkheadFactory(mockMetricEvents.Object, mockConfig, mockLogFactory.Object);

            // Add a bulkhead
            factory.GetBulkhead(groupKey);

            // The timer will fire after 1 second.
            Thread.Sleep(TimeSpan.FromMilliseconds(1500));

            // Assert

            // Gauges should fire every second, so wait one second and then verify.
            mockMetricEvents.Verify(m => m.BulkheadGauge(key, "semaphore", expectedMaxConcurrent, It.IsAny <int>()), Times.AtLeastOnce);
        }
            public async Task DoesntSetExecutionTimeOnCommandWhenInvokedWithBreakerAndCommandSucceeds()
            {
                // If we execute on the breaker, the breaker should set the execution time instead
                // of the bulkhead invoker.

                // Arrange

                var key      = AnyString;
                var groupKey = GroupKey.Named(key);

                var mockMetricEvents   = new Mock <IMetricEvents>();
                var mockBreakerInvoker = new Mock <IBreakerInvoker>();

                var mockBulkhead = new Mock <ISemaphoreBulkhead>();

                mockBulkhead.Setup(m => m.TryEnter()).Returns(true);
                mockBulkhead.SetupGet(m => m.Name).Returns(key);

                var mockBulkheadFactory = new Mock <IBulkheadFactory>(MockBehavior.Strict);

                mockBulkheadFactory.Setup(m => m.GetBulkhead(groupKey)).Returns(mockBulkhead.Object);

                var command = new ConfigurableKeyThrowingAsyncCommand(key);

                mockBreakerInvoker.Setup(m => m.ExecuteWithBreakerAsync(command, It.IsAny <CancellationToken>()))
                .Returns(Task.FromResult(true));

                var mockConfig = new MjolnirConfiguration {
                    UseCircuitBreakers = true
                };

                // Pass true for useCircuitBreakers, we need to test that behavior here.
                var invoker = new BulkheadInvoker(mockBreakerInvoker.Object, mockBulkheadFactory.Object, mockMetricEvents.Object, mockConfig);

                // Act

                await invoker.ExecuteWithBulkheadAsync(command, CancellationToken.None);

                // Assert

                Assert.Equal(0, command.ExecutionTimeMillis);
            }
示例#25
0
        public void GetBulkhead_ReturnsSameBulkheadForKey()
        {
            // Bulkheads are long-lived objects and used for many requests. In the absence
            // of any configuration changes, we should be using the same one through the
            // lifetime of the app.

            // Arrange

            var key = AnyGroupKey;

            var mockMetricEvents = new Mock <IMetricEvents>(MockBehavior.Strict);

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        key.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = AnyPositiveInt
                        }
                    }
                }
            };

            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <BulkheadFactory>()).Returns(new DefaultMjolnirLog <BulkheadFactory>());
            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(new DefaultMjolnirLog <SemaphoreBulkheadHolder>());

            var factory = new BulkheadFactory(mockMetricEvents.Object, mockConfig, mockLogFactory.Object);

            // Act

            var bulkhead = factory.GetBulkhead(key);

            // Assert

            Assert.Equal(bulkhead, factory.GetBulkhead(key));
        }
        public MjolnirConfiguration GetConfig()
        {
            if (_currentConfig != null)
            {
                return(_currentConfig);
            }

            const string rootKey = "MjolnirConfiguration";
            var          section = _root.GetSection(rootKey);

            if (section == null || section.Value == null && !section.GetChildren().Any())
            {
                _currentConfig = new MjolnirConfiguration();
                return(default(MjolnirConfiguration));
            }

            var config = new MjolnirConfiguration();

            section.Bind(config);
            _currentConfig = config;
            return(config);
        }
示例#27
0
        public void Construct_SetsInitialBulkhead()
        {
            // Arrange

            var key = AnyGroupKey;
            var expectedMaxConcurrent = AnyPositiveInt;
            var mockMetricEvents      = new Mock <IMetricEvents>(MockBehavior.Strict);

            mockMetricEvents.Setup(m => m.BulkheadGauge(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>()));

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        key.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = expectedMaxConcurrent
                        }
                    }
                }
            };


            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(new DefaultMjolnirLog <SemaphoreBulkheadHolder>());

            // Act

            var holder = new SemaphoreBulkheadHolder(key, mockMetricEvents.Object, mockConfig, mockLogFactory.Object);

            // Assert

            Assert.Equal(key.Name, holder.Bulkhead.Name);
            Assert.Equal(expectedMaxConcurrent, holder.Bulkhead.CountAvailable);
        }
示例#28
0
        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);
        }
示例#29
0
        public void GetBulkhead_WhenInitializingBulkheadAndMaxConcurrentConfigIsInvalid_Throws()
        {
            // Arrange

            var       key = AnyGroupKey;
            const int invalidMaxConcurrent = -1;

            var mockMetricEvents = new Mock <IMetricEvents>(MockBehavior.Strict);

            var mockConfig = new MjolnirConfiguration
            {
                BulkheadConfigurations = new Dictionary <string, BulkheadConfiguration>
                {
                    {
                        key.Name,
                        new BulkheadConfiguration
                        {
                            MaxConcurrent = invalidMaxConcurrent
                        }
                    }
                }
            };


            var mockLogFactory = new Mock <IMjolnirLogFactory>(MockBehavior.Strict);

            mockLogFactory.Setup(m => m.CreateLog <BulkheadFactory>()).Returns(new DefaultMjolnirLog <BulkheadFactory>());
            mockLogFactory.Setup(m => m.CreateLog <SemaphoreBulkheadHolder>()).Returns(new DefaultMjolnirLog <SemaphoreBulkheadHolder>());
            var factory = new BulkheadFactory(mockMetricEvents.Object, mockConfig, mockLogFactory.Object);

            // Act + Assert

            var exception = Assert.Throws <ArgumentOutOfRangeException>(() => factory.GetBulkhead(key));

            Assert.Equal("maxConcurrent", exception.ParamName);
            Assert.Equal(invalidMaxConcurrent, exception.ActualValue);
        }
示例#30
0
        // 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);
                }
            });
        }