-
Notifications
You must be signed in to change notification settings - Fork 14
Add functionality to browse queues #412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| using System; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace ActiveMQ.Artemis.Client | ||
| { | ||
| internal class Browser : IBrowser | ||
| { | ||
|
|
||
| private IConsumer _consumer; | ||
| private Message _current; | ||
|
|
||
| public Browser(IConsumer consumer) | ||
| { | ||
| _consumer = consumer; | ||
| } | ||
|
|
||
| public Message Current => _current; | ||
|
|
||
| object IEnumerator.Current => _current; | ||
|
|
||
| public void Dispose() | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| public async ValueTask DisposeAsync() | ||
| { | ||
| if (_consumer != null) | ||
| await _consumer.DisposeAsync(); | ||
| } | ||
|
|
||
|
|
||
| public IEnumerator<Message> GetEnumerator() | ||
| { | ||
| return this; | ||
| } | ||
|
|
||
| public bool MoveNext() | ||
| { | ||
| _current = Next(); | ||
|
|
||
| return _current != null; | ||
| } | ||
|
|
||
| private Message Next() | ||
| { | ||
| var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); | ||
| var token = cts.Token; | ||
|
|
||
| try | ||
| { | ||
| _current = null; | ||
| _current = _consumer.ReceiveAsync(token).Result; | ||
|
|
||
| return _current; | ||
| } | ||
| catch(OperationCanceledException ex) | ||
| { | ||
| var error = ex.Message; | ||
| return null; | ||
| } | ||
| catch(Exception) | ||
| { | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| public void Reset() | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| IEnumerator IEnumerable.GetEnumerator() | ||
| { | ||
| return this; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace ActiveMQ.Artemis.Client | ||
| { | ||
| public interface IBrowser : IEnumerator<Message>, IEnumerable<Message>, IAsyncDisposable | ||
| { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather expect this interface to expose a method that returns IAsyncEnumerable: IAsyncEnumerable<Message> ReceiveAllAsync(CancellationToken cancellationToken);Than implement IEnumerable itself. As a side note, you cannot implement IEnumerable without blocking. A naive implementation might look as follows: public async IAsyncEnumerable<Message> ReceiveAllAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
yield return await _consumer.ReceiveAsync(cancellationToken).ConfigureAwait(false);
// we probably need to ack message, so the browser won't block after we run out of the consumer credit
}
}But I'm not sure that reusing |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,11 +25,12 @@ public interface IConnection : IAsyncDisposable | |
| /// </summary> | ||
| bool IsOpened { get; } | ||
| Task<ITopologyManager> CreateTopologyManagerAsync(CancellationToken cancellationToken = default); | ||
| Task<IConsumer> CreateConsumerAsync(ConsumerConfiguration configuration, CancellationToken cancellationToken = default); | ||
| Task<IConsumer> CreateConsumerAsync(ConsumerConfiguration configuration, CancellationToken cancellationToken = default, bool isBrowser = false); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IConsumer != IBrowser. They have different characteristics. I wouldn't pretend they represent the same thing and try to shoehorn them under a single interface umbrella. |
||
| Task<IProducer> CreateProducerAsync(ProducerConfiguration configuration, CancellationToken cancellationToken = default); | ||
| Task<IAnonymousProducer> CreateAnonymousProducerAsync(AnonymousProducerConfiguration configuration, CancellationToken cancellationToken = default); | ||
| Task<IRequestReplyClient> CreateRequestReplyClientAsync(RequestReplyClientConfiguration configuration, CancellationToken cancellationToken = default); | ||
|
|
||
| Task<IBrowser> CreateBrowserAsync(ConsumerConfiguration configuration, CancellationToken cancellationToken = default); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably need a special A big chunk of properties from I think we don't need I'm not sure about |
||
|
|
||
| /// <summary> | ||
| /// Raised when the connection is closed. | ||
| /// </summary> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| using System; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace ActiveMQ.Artemis.Client.IntegrationTests | ||
| { | ||
| public class MessageBrowseSpec : ActiveMQNetIntegrationSpec | ||
| { | ||
| public MessageBrowseSpec(ITestOutputHelper output) : base(output) | ||
| { | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Should_find_message_by_browsing_for_id_property_with_existing_id_and_single_message() | ||
| { | ||
| await using var connection = await CreateConnection(); | ||
| var address = Guid.NewGuid().ToString(); | ||
| await using var producer = await connection.CreateProducerAsync(address, RoutingType.Anycast); | ||
|
|
||
| var messageId = Guid.NewGuid().ToString(); | ||
| var message = new Message("foo"); | ||
| message.ApplicationProperties["id"] = messageId; | ||
|
|
||
| await producer.SendAsync(message); | ||
|
|
||
| var configuration = new ConsumerConfiguration | ||
| { | ||
| Address = address, | ||
| RoutingType = RoutingType.Anycast, | ||
| FilterExpression = $"id = '{messageId}'", | ||
| }; | ||
|
|
||
| await using var browser = await connection.CreateBrowserAsync(configuration); | ||
|
|
||
|
|
||
| Message existingMessage = null; | ||
| if (browser.MoveNext()) | ||
| { | ||
| existingMessage = browser.Current; | ||
| } | ||
|
|
||
| Assert.Equal("foo", existingMessage.GetBody<string>()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Should_find_message_by_browsing_for_id_property_with_existing_id_and_multiple_messages() | ||
| { | ||
| await using var connection = await CreateConnection(); | ||
| var address = Guid.NewGuid().ToString(); | ||
| await using var producer = await connection.CreateProducerAsync(address, RoutingType.Anycast); | ||
|
|
||
| var messageId = Guid.NewGuid().ToString(); | ||
| var message = new Message("foo"); | ||
| message.ApplicationProperties["id"] = messageId; | ||
|
|
||
| await producer.SendAsync(new Message(Guid.NewGuid().ToString())); | ||
| await producer.SendAsync(new Message(Guid.NewGuid().ToString())); | ||
|
|
||
| await producer.SendAsync(message); | ||
|
|
||
| await producer.SendAsync(new Message(Guid.NewGuid().ToString())); | ||
| await producer.SendAsync(new Message(Guid.NewGuid().ToString())); | ||
|
|
||
| var configuration = new ConsumerConfiguration | ||
| { | ||
| Address = address, | ||
| RoutingType = RoutingType.Anycast, | ||
| FilterExpression = $"id = '{messageId}'", | ||
| }; | ||
|
|
||
| await using var browser = await connection.CreateBrowserAsync(configuration); | ||
|
|
||
|
|
||
| Message existingMessage = null; | ||
| if (browser.MoveNext()) | ||
| { | ||
| existingMessage = browser.Current; | ||
| } | ||
|
|
||
| Assert.Equal("foo", existingMessage.GetBody<string>()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Should_not_find_message_by_browsing_for_id_property_with_inexisting_id() | ||
| { | ||
| await using var connection = await CreateConnection(); | ||
| var address = Guid.NewGuid().ToString(); | ||
| await using var producer = await connection.CreateProducerAsync(address, RoutingType.Anycast); | ||
|
|
||
| var message = new Message("foo"); | ||
| message.ApplicationProperties["id"] = Guid.NewGuid().ToString(); | ||
|
|
||
| await producer.SendAsync(message); | ||
|
|
||
| var configuration = new ConsumerConfiguration | ||
| { | ||
| Address = address, | ||
| RoutingType = RoutingType.Anycast, | ||
| FilterExpression = $"id = '{Guid.NewGuid()}'", // inexistent id | ||
| }; | ||
|
|
||
| await using var browser = await connection.CreateBrowserAsync(configuration); | ||
|
|
||
| Message maybeMessage = null; | ||
| if (browser.MoveNext()) | ||
| { | ||
| maybeMessage = browser.Current; | ||
| } | ||
|
|
||
| Assert.Null(maybeMessage); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Should_not_find_message_by_browsing_for_id_property_with_empty_id() | ||
| { | ||
| await using var connection = await CreateConnection(); | ||
| var address = Guid.NewGuid().ToString(); | ||
| await using var producer = await connection.CreateProducerAsync(address, RoutingType.Anycast); | ||
|
|
||
| var message = new Message("foo"); | ||
| message.ApplicationProperties["id"] = Guid.NewGuid().ToString(); | ||
|
|
||
| await producer.SendAsync(message); | ||
|
|
||
| var configuration = new ConsumerConfiguration | ||
| { | ||
| Address = address, | ||
| RoutingType = RoutingType.Anycast, | ||
| FilterExpression = $"id = ''", // empty id | ||
| }; | ||
|
|
||
| await using var browser = await connection.CreateBrowserAsync(configuration); | ||
|
|
||
| Message maybeMessage = null; | ||
| if (browser.MoveNext()) | ||
| { | ||
| maybeMessage = browser.Current; | ||
| } | ||
|
|
||
| Assert.Null(maybeMessage); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want blocking on async call.