-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Host.RabbitMq] Multiple consumers on same queue with varying concurr…
…ency separated by routing key Signed-off-by: Tomasz Maruszak <[email protected]>
- Loading branch information
Showing
3 changed files
with
141 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 0 additions & 74 deletions
74
...s/SlimMessageBus.Host.Test/Consumer/ConcurrencyIncreasingMessageProcessorDecoratorTest.cs
This file was deleted.
Oops, something went wrong.
139 changes: 139 additions & 0 deletions
139
src/Tests/SlimMessageBus.Host.Test/Consumer/ConcurrentMessageProcessorDecoratorTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
namespace SlimMessageBus.Host.Test.Consumer; | ||
|
||
public class ConcurrentMessageProcessorDecoratorTest | ||
{ | ||
private readonly MessageBusMock _busMock; | ||
private readonly Mock<IMessageProcessor<SomeMessage>> _messageProcessorMock; | ||
|
||
public ConcurrentMessageProcessorDecoratorTest() | ||
{ | ||
_busMock = new MessageBusMock(); | ||
_messageProcessorMock = new Mock<IMessageProcessor<SomeMessage>>(); | ||
} | ||
|
||
[Fact] | ||
public void When_Dispose_Then_CallsDisposeOnTarget() | ||
{ | ||
// arrange | ||
var targetDisposableMock = _messageProcessorMock.As<IDisposable>(); | ||
var subject = new ConcurrentMessageProcessorDecorator<SomeMessage>(1, NullLoggerFactory.Instance, _messageProcessorMock.Object); | ||
|
||
// act | ||
subject.Dispose(); | ||
|
||
// assert | ||
targetDisposableMock.Verify(x => x.Dispose(), Times.Once); | ||
} | ||
|
||
[Theory] | ||
[InlineData(false)] | ||
[InlineData(true)] | ||
public async Task When_WaitAll_Then_WaitsOnAllPendingMessageProcessToFinish(bool cancelAwait) | ||
{ | ||
// arrange | ||
_messageProcessorMock | ||
.Setup(x => x.ProcessMessage( | ||
It.IsAny<SomeMessage>(), | ||
It.IsAny<IReadOnlyDictionary<string, object>>(), | ||
It.IsAny<IDictionary<string, object>>(), | ||
It.IsAny<IServiceProvider>(), | ||
It.IsAny<CancellationToken>())) | ||
.Returns(async () => | ||
{ | ||
await Task.Delay(TimeSpan.FromSeconds(1)); | ||
return new(null, null, null); | ||
}); | ||
|
||
var subject = new ConcurrentMessageProcessorDecorator<SomeMessage>(1, NullLoggerFactory.Instance, _messageProcessorMock.Object); | ||
|
||
await subject.ProcessMessage(new SomeMessage(), new Dictionary<string, object>(), default); | ||
|
||
using var cts = new CancellationTokenSource(); | ||
|
||
if (cancelAwait) | ||
{ | ||
cts.CancelAfter(TimeSpan.FromMilliseconds(100)); | ||
} | ||
|
||
// act | ||
var waitAll = () => subject.WaitAll(cts.Token); | ||
|
||
// assert | ||
if (cancelAwait) | ||
{ | ||
await waitAll.Should().ThrowAsync<TaskCanceledException>(); | ||
} | ||
else | ||
{ | ||
await waitAll.Should().NotThrowAsync(); | ||
} | ||
_messageProcessorMock | ||
.Verify(x => x.ProcessMessage( | ||
It.IsAny<SomeMessage>(), | ||
It.IsAny<IReadOnlyDictionary<string, object>>(), | ||
It.IsAny<IDictionary<string, object>>(), | ||
It.IsAny<IServiceProvider>(), | ||
It.IsAny<CancellationToken>()), | ||
cancelAwait ? Times.Once : Times.Once); | ||
} | ||
|
||
[Theory] | ||
[InlineData(10, 40)] | ||
[InlineData(2, 40)] | ||
public async Task When_ProcessMessage_Given_NMessagesAndConcurrencySetToC_Then_NMethodInvocationsHappenOnTargetWithCConcurrently(int concurrency, int expectedMessageCount) | ||
{ | ||
// arrange | ||
var subject = new ConcurrentMessageProcessorDecorator<SomeMessage>(concurrency, NullLoggerFactory.Instance, _messageProcessorMock.Object); | ||
|
||
var currentSectionCount = 0; | ||
var maxSectionCount = 0; | ||
var maxSectionCountLock = new object(); | ||
var messageCount = 0; | ||
|
||
_messageProcessorMock | ||
.Setup(x => x.ProcessMessage(It.IsAny<SomeMessage>(), It.IsAny<IReadOnlyDictionary<string, object>>(), It.IsAny<IDictionary<string, object>>(), It.IsAny<IServiceProvider>(), It.IsAny<CancellationToken>())) | ||
.Returns(async () => | ||
{ | ||
// Entering critical section | ||
Interlocked.Increment(ref currentSectionCount); | ||
|
||
// Simulate work | ||
await Task.Delay(50); | ||
|
||
Interlocked.Increment(ref messageCount); | ||
|
||
lock (maxSectionCountLock) | ||
{ | ||
if (currentSectionCount > maxSectionCount) | ||
{ | ||
maxSectionCount = currentSectionCount; | ||
} | ||
} | ||
|
||
// Simulate work | ||
await Task.Delay(50); | ||
|
||
// Leaving critical section | ||
Interlocked.Decrement(ref currentSectionCount); | ||
return new(null, null, null); | ||
}); | ||
|
||
// act | ||
var msg = new SomeMessage(); | ||
var msgHeaders = new Dictionary<string, object>(); | ||
for (var i = 0; i < expectedMessageCount; i++) | ||
{ | ||
// executed in sequence | ||
await subject.ProcessMessage(msg, msgHeaders, default); | ||
} | ||
|
||
// assert | ||
while (subject.PendingCount > 0) | ||
{ | ||
await Task.Delay(100); | ||
} | ||
|
||
messageCount.Should().Be(expectedMessageCount); | ||
maxSectionCount.Should().Be(concurrency); | ||
} | ||
} |