-
-
Notifications
You must be signed in to change notification settings - Fork 244
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
Lazy queue creation, don't recreate on delete #38
Conversation
…eleteQueueAsync() This enables us to move network requests out of the constructor (where they don't belong) and to use the async methods where applicable.
e43318d
to
ce843dd
Compare
I think it would be a good idea if
public abstract Task<string> EnqueueAsync(T data); becomes... public Task<string> EnqueueAsync(T data) {
await EnsureQueueCreatedAsync().AnyContext();
await EnqueueAsyncImpl(data).AnyContext();
}
protected abstract Task<string> EnqueueAsyncImpl(T data); |
Would it be worth accounting for the scenario where a queue might be deleted by another instance of the implementation? I didn't do that for the current implementation primarily because I don't want to be making an additional network request (in the case of Azure Storage / Service Bus) for almost every API call. Consider the below, or in a scale out scenario... var a = new AzureServiceBusQueue("myqueue");
var b = new AzureServiceBusQueue("myqueue");
a.EnqueueAsync(new Thing());
var bEntry = b.DequeueAsync();
a.DeleteQueueAsync();
b.EnqueueAsync(new Thing()); // Throws as queue is deleted
a.EnqueueAsync(new Thing()); // Works as the class knows the queue is deleted and recreates We could handle this by adding something like: public abstract class QueueBase<T> {
private bool _queueCreated;
protected abstract Task EnsureQueueCreatedImplAsync(CancellationToken cancellationToken);
protected Task EnsureQueueCreatedAsync(CancellationToken cancellationToken = default(CancellationToken)) {
if (!_queueCreated) {
await EnsureQueueCreatedImplAsync(cancellationToken).AnyContext();
_queueCreated = true;
}
}
protected async Task<TResult> WrapQueueCall<TResult>(Func<Task<TResult>> action, CancellationToken cancellationToken) where TResult : Task {
await EnsureQueueCreatedAsync(cancellationToken);
try {
return await action();
}
catch (Exception ex) when (IsQueueDeletedException(ex)) {
_queueCreated = false;
await EnsureQueueCreatedAsync(cancellationToken);
return await action();
}
}
protected abstract bool IsQueueDeletedException(Exception ex);
protected abstract Task<string> EnqueueImplAsync(T data);
public async Task<string> EnqueueAsync(T data) {
return await WrapQueueCall(() => EnqueueImplAsync(data));
await EnsureQueueCreatedAsync().AnyContext();
return await .AnyContext();
}
}
public class AzureStorageQueue<T> : QueueBase<T> {
protected override bool IsQueueDeletedException(Exception ex) => ex is StorageException && ex.Message.Contains("404");
} |
I think if you delete it you should have to create it again |
Not sure I agree although not a common scenario. If that is the case then we need a |
@@ -133,40 +149,50 @@ public class AzureServiceBusQueue<T> : QueueBase<T> where T : class { | |||
if (!queueEntry.IsAbandoned && !queueEntry.IsCompleted) | |||
await queueEntry.AbandonAsync().AnyContext(); | |||
} | |||
}, new OnMessageOptions { | |||
AutoComplete = false | |||
}); |
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.
What does this control? Shouldn't the auto complete setting be set from the constructor?
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.
This will tell Service Bus not to autocomplete... Autocompletion is controlled by queueEntry.CompleteAsync()
and queueEntry.AbandonAsync()
instead in order to trigger the events.
Everyone happy to merge? 😄 |
As soon as the build passes :) |
I think @ejsmith was going to look into that Redis test 😉 |
_queueDescription.MaxDeliveryCount = newMaxDeliveryCount; | ||
changes = true; | ||
if (!await _namespaceManager.QueueExistsAsync(_queueName).AnyContext()) { | ||
queueDescription = await _namespaceManager.CreateQueueAsync(new QueueDescription(_queueName) { |
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.
You should catch the exception thrown when CreateQueueAsync
fails because the queue already exists.
We've seen this in our current project using Foundatio: in a multi-process environment, QueueExistsAsync
can return false
for multiple processes, and then they each call CreateQueueAsync
but only one will succeed. The rest will fail with either Microsoft.ServiceBus.Messaging.MessagingException
or Microsoft.ServiceBus.Messaging.MessagingEntityAlreadyExistsException
.
The correct solution is to catch those exceptions, and then fall back on calling GetQueueAsync
. If that also fails, then you can bail. :)
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.
Hadn't noticed that, I'll get that sorted and see if i can add a test to cover this also.
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 could add a distributed lock around this or bail out.
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.
@moswald feel free to join our public slack channel: https://slack.exceptionless.com
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.
Yeah I did think that, but that makes queue coupled to a distributed locking provider. Rather not do that!
Conflicts: src/Azure/Queues/AzureServiceBusQueue.cs
As dicussed in #25, #36, #37 and on Gitter.
Need to do AzureStorageQueue too - RedisQueue already permenently deletes and doesn't have network requests in the ctor, so that should be fine.