Пример #1
0
        public void Construct_WithIsolationKey_UsesIsolationKeyAsAllKeys()
        {
            var command = new KeyTestCommand("test", "foo");

            Assert.Equal(GroupKey.Named("foo"), command.BreakerKey);
            Assert.Equal(GroupKey.Named("foo"), command.PoolKey);
        }
Пример #2
0
 private static void AssertInvalidName(string name)
 {
     Assert.Throws <ArgumentException>(() =>
     {
         GroupKey.Named(name);
     });
 }
            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 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));
            }
            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);
            }
Пример #6
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);
        }
        public void TryEnterAndReleaseALot()
        {
            var semaphore = new SemaphoreSlimIsolationSemaphore(GroupKey.Named("Test"), new TransientConfigurableValue <int>(5), new IgnoringStats());

            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            Assert.False(semaphore.TryEnter());
            Assert.False(semaphore.TryEnter());
            Assert.False(semaphore.TryEnter());
            semaphore.Release();
            Assert.True(semaphore.TryEnter());
            Assert.False(semaphore.TryEnter());
            semaphore.Release();
            semaphore.Release();
            semaphore.Release();
            semaphore.Release();
            semaphore.Release();
            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            semaphore.Release();
            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            Assert.True(semaphore.TryEnter());
            Assert.False(semaphore.TryEnter());
            Assert.False(semaphore.TryEnter());
            semaphore.Release();
            semaphore.Release();
            semaphore.Release();
            semaphore.Release();
            semaphore.Release();
        }
            public void Run()
            {
                var mockMetrics = CreateMockMetricsWithSnapshot(_metricsTotal, _metricsPercent);
                var config      = CreateMockBreakerConfig(_breakerTotal, _breakerPercent, 30000);
                var breaker     = new FailurePercentageCircuitBreaker(GroupKey.Named("Test"), mockMetrics.Object, new IgnoringMetricEvents(), config.Object, new DefaultMjolnirLogFactory());

                Assert.NotEqual(_shouldTrip, breaker.IsAllowing());
            }
            public void Run()
            {
                var mockMetrics = CreateMockMetricsWithSnapshot(_metricsTotal, _metricsPercent);
                var properties  = CreateBreakerProperties(_breakerTotal, _breakerPercent, 30000);
                var breaker     = new FailurePercentageCircuitBreaker(GroupKey.Named("Test"), mockMetrics.Object, new IgnoringStats(), properties);

                Assert.NotEqual(_shouldTrip, breaker.IsAllowing());
            }
        public void TryEnter_WhenSemaphoreIsAvailable_ReturnsTrueImmediately()
        {
            var semaphore = new SemaphoreSlimIsolationSemaphore(GroupKey.Named("Test"), new TransientConfigurableValue <int>(1), new IgnoringStats());

            var stopwatch = Stopwatch.StartNew();

            Assert.True(semaphore.TryEnter());
            Assert.True(stopwatch.ElapsedMilliseconds < 10);
        }
Пример #11
0
            public void SetsBreakerKey()
            {
                var key      = AnyString;
                var expected = GroupKey.Named(key);

                var command = new TestCommand(AnyString, key, AnyString, ValidTimeout);

                Assert.Equal(expected, command.BreakerKey);
            }
Пример #12
0
 private static StandardCommandMetrics CreateMetrics(string key, IMock <IStats> mockStats, IClock clock = null,
                                                     long?windowMillis = null, long?snapshotTtlMillis = null)
 {
     return(new StandardCommandMetrics(
                GroupKey.Named(key),
                new TransientConfigurableValue <long>(windowMillis ?? 30000),
                new TransientConfigurableValue <long>(snapshotTtlMillis ?? 10000),
                (clock ?? new ManualTestClock()),
                mockStats.Object));
 }
        public void Release_WhenSemaphoreNotAvailable_MakesItAvailable()
        {
            var semaphore = new SemaphoreSlimIsolationSemaphore(GroupKey.Named("Test"), new TransientConfigurableValue <int>(1), new IgnoringStats());

            semaphore.TryEnter();
            Assert.False(semaphore.TryEnter());

            semaphore.Release();
            Assert.True(semaphore.TryEnter());
        }
Пример #14
0
        public async Task Construct_CreatesGauges()
        {
            const long gaugeIntervalMillis = 50;

            var mockStats = new Mock <IStats>();
            var semaphore = new SemaphoreSlimIsolationSemaphore(GroupKey.Named("Test"), new TransientConfigurableValue <int>(10), mockStats.Object, new TransientConfigurableValue <long>(gaugeIntervalMillis));

            await Task.Delay(TimeSpan.FromMilliseconds(gaugeIntervalMillis + 50));

            mockStats.Verify(m => m.Gauge("mjolnir fallback-semaphore Test available", "Available", 10), Times.AtLeastOnce);
        }
Пример #15
0
        private StpIsolationThreadPool CreateAndStartPool(int threadCount, int queueLength)
        {
            var pool = new StpIsolationThreadPool(
                GroupKey.Named("Test"),
                new TransientConfigurableValue <int>(threadCount),
                new TransientConfigurableValue <int>(queueLength),
                new IgnoringStats());

            pool.Start();
            return(pool);
        }
        public void Start_Elapsed()
        {
            var mockStats = new Mock <IStats>();
            var pool      = new StpIsolationThreadPool(
                GroupKey.Named("Test"),
                new TransientConfigurableValue <int>(10),
                new TransientConfigurableValue <int>(20),
                mockStats.Object);

            pool.Start();

            mockStats.Verify(m => m.Elapsed("mjolnir pool Test Start", null, It.IsAny <TimeSpan>()), Times.Once);
        }
Пример #17
0
        public void MarkCommandFailure_BeforeFirstSnapshot_GetsIncludedInSnapshot()
        {
            var metrics = new StandardCommandMetrics(
                GroupKey.Named("Test"),
                new TransientConfigurableValue <long>(30000),
                new TransientConfigurableValue <long>(1000),
                new IgnoringStats());

            metrics.MarkCommandFailure();

            var snapshot = metrics.GetSnapshot();

            Assert.Equal(1, snapshot.Total);
            Assert.Equal(100, snapshot.ErrorPercentage);
        }
        public async Task Construct_CreatesGauges()
        {
            const long gaugeIntervalMillis = 50;

            var mockStats = new Mock <IStats>();
            var pool      = new StpIsolationThreadPool(
                GroupKey.Named("Test"),
                new TransientConfigurableValue <int>(10),
                new TransientConfigurableValue <int>(20),
                mockStats.Object,
                new TransientConfigurableValue <long>(gaugeIntervalMillis));

            await Task.Delay(TimeSpan.FromMilliseconds(gaugeIntervalMillis + 50));

            mockStats.Verify(m => m.Gauge("mjolnir pool Test activeThreads", null, It.IsAny <long>()), Times.AtLeastOnce);
            mockStats.Verify(m => m.Gauge("mjolnir pool Test inUseThreads", null, It.IsAny <long>()), Times.AtLeastOnce);
            mockStats.Verify(m => m.Gauge("mjolnir pool Test pendingCompletion", null, It.IsAny <long>()), Times.AtLeastOnce);
        }
Пример #19
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);
        }
Пример #20
0
        public void Construct_InitializesConfigGauge_GaugeFiresForOneBulkhead()
        {
            // Arrange

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

            var expectedMinimumOperations     = AnyPositiveInt;
            var expectedWindowMillis          = AnyPositiveInt;
            var expectedThresholdPercent      = AnyPositiveInt;
            var expectedTrippedDurationMillis = AnyPositiveInt;
            var expectedForceTripped          = false;
            var expectedForceFixed            = false;

            var mockMetricEvents = new Mock <IMetricEvents>();

            var mockBreakerConfig = new Mock <IFailurePercentageCircuitBreakerConfig>(MockBehavior.Strict);

            mockBreakerConfig.Setup(m => m.GetMinimumOperations(groupKey)).Returns(expectedMinimumOperations);
            mockBreakerConfig.Setup(m => m.GetWindowMillis(groupKey)).Returns(expectedWindowMillis);
            mockBreakerConfig.Setup(m => m.GetThresholdPercentage(groupKey)).Returns(expectedThresholdPercent);
            mockBreakerConfig.Setup(m => m.GetTrippedDurationMillis(groupKey)).Returns(expectedTrippedDurationMillis);
            mockBreakerConfig.Setup(m => m.GetForceTripped(groupKey)).Returns(expectedForceTripped);
            mockBreakerConfig.Setup(m => m.GetForceFixed(groupKey)).Returns(expectedForceFixed);

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

            mockLogFactory.Setup(m => m.CreateLog <FailurePercentageCircuitBreaker>()).Returns(new DefaultMjolnirLog <FailurePercentageCircuitBreaker>());
            mockLogFactory.Setup(m => m.CreateLog <ResettingNumbersBucket>()).Returns(new DefaultMjolnirLog <ResettingNumbersBucket>());
            // Act + Assert

            var factory = new CircuitBreakerFactory(mockMetricEvents.Object, mockBreakerConfig.Object, mockLogFactory.Object);

            // Gauge won't fire immediately, it'll start one second after construction
            mockMetricEvents.Verify(m => m.BreakerGauge(It.IsAny <string>(), It.IsAny <long>(), It.IsAny <long>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <bool>(), It.IsAny <bool>(), It.IsAny <bool>(), It.IsAny <long>(), It.IsAny <long>()), Times.Never);

            // Add two breakers
            var firstBreaker = factory.GetCircuitBreaker(groupKey);

            Thread.Sleep(TimeSpan.FromMilliseconds(1500));

            mockMetricEvents.Verify(m => m.BreakerGauge(key, expectedMinimumOperations, expectedWindowMillis, expectedThresholdPercent, expectedTrippedDurationMillis, expectedForceTripped, expectedForceFixed, false, 0, 0));
        }
Пример #21
0
        private MetricsSnapshot SnapshotFor(int success, int failure)
        {
            var metrics = new StandardCommandMetrics(
                GroupKey.Named("Test"),
                new TransientConfigurableValue <long>(10000),
                new TransientConfigurableValue <long>(0),
                new IgnoringStats()); // Don't cache snapshots.

            for (var i = 0; i < success; i++)
            {
                metrics.MarkCommandSuccess();
            }

            for (var i = 0; i < failure; i++)
            {
                metrics.MarkCommandFailure();
            }
            return(metrics.GetSnapshot());
        }
            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);
            }
Пример #23
0
        public void GetSnapshot_WithinCachePeriod_ReturnsPreviousSnapshot()
        {
            var clock = new ManualTestClock();

            // Within the metrics, the last snapshot timestamp will probably be zero.
            // Let's start our clock with something far away from zero.
            clock.AddMilliseconds(new SystemClock().GetMillisecondTimestamp());
            var metrics = new StandardCommandMetrics(
                GroupKey.Named("Test"),
                new TransientConfigurableValue <long>(10000),
                new TransientConfigurableValue <long>(1000),
                clock,
                new IgnoringStats());

            metrics.MarkCommandSuccess();
            metrics.GetSnapshot();       // Take the first snapshot to cache it.
            metrics.MarkCommandSuccess();
            clock.AddMilliseconds(500);  // Still within the snapshot TTL (1000).
            Assert.Equal(1, metrics.GetSnapshot().Total);
            clock.AddMilliseconds(1000); // Push time past the TTL.
            Assert.Equal(2, metrics.GetSnapshot().Total);
        }
Пример #24
0
        internal BaseCommand(string group, string name, string breakerKey, string bulkheadKey, TimeSpan?defaultTimeout = null)
        {
            if (string.IsNullOrWhiteSpace(group))
            {
                throw new ArgumentNullException(nameof(group));
            }

            if (string.IsNullOrWhiteSpace(breakerKey))
            {
                throw new ArgumentNullException(nameof(breakerKey));
            }

            if (string.IsNullOrWhiteSpace(bulkheadKey))
            {
                throw new ArgumentNullException(nameof(bulkheadKey));
            }

            if (breakerKey == "default")
            {
                throw new ArgumentException($"Cannot use 'default' as {nameof(breakerKey)}, it is a reserved name");
            }

            if (bulkheadKey == "default")
            {
                throw new ArgumentException($"Cannot use 'default' as {nameof(bulkheadKey)}, it is a reserved name");
            }

            if (defaultTimeout != null && defaultTimeout.Value.TotalMilliseconds <= 0)
            {
                throw new ArgumentException($"Positive default timeout is required if passed (received invalid timeout of {defaultTimeout.Value.TotalMilliseconds}ms)", nameof(defaultTimeout));
            }

            _group              = GroupKey.Named(group);
            _name               = string.IsNullOrWhiteSpace(name) ? GenerateAndCacheName(Group) : CacheProvidedName(Group, name);
            _breakerKey         = GroupKey.Named(breakerKey);
            _bulkheadKey        = GroupKey.Named(bulkheadKey);
            _constructorTimeout = defaultTimeout ?? DefaultTimeout;
        }
        public FailurePercentageCircuitBreaker Create()
        {
            var config = FailurePercentageCircuitBreakerTests.CreateMockBreakerConfig(_minimumOperations, _failurePercent, _waitMillis);

            return(new FailurePercentageCircuitBreaker(GroupKey.Named(_key), _clock, _mockMetrics.Object, _metricEvents, config.Object, new DefaultMjolnirLogFactory()));
        }
Пример #26
0
 private static void AssertValidName(string name)
 {
     GroupKey.Named(name);
     Assert.True(true); // Didn't throw exception.
 }
Пример #27
0
 public void ToString_ReturnsName()
 {
     Assert.Equal("Foo", GroupKey.Named("Foo").ToString());
 }
Пример #28
0
 public void GetHashCode_ReturnsNameHashCode()
 {
     Assert.Equal("Foo".GetHashCode(), GroupKey.Named("Foo").GetHashCode());
 }
Пример #29
0
 public void Equals_WhenNamesDiffer_ReturnsFalse()
 {
     Assert.NotEqual(GroupKey.Named("Foo"), GroupKey.Named("Bar"));
 }
Пример #30
0
 public void Equals_WhenNamesEqual_ReturnsTrue()
 {
     Assert.Equal(GroupKey.Named("Foo"), GroupKey.Named("Foo"));
 }