diff --git a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj index e91a7b2ee6..7fcee1c782 100755 --- a/projects/RabbitMQ.Client/RabbitMQ.Client.csproj +++ b/projects/RabbitMQ.Client/RabbitMQ.Client.csproj @@ -57,12 +57,15 @@ + + + + - diff --git a/projects/RabbitMQ.Client/client/FrameworkExtension/Interlocked.cs b/projects/RabbitMQ.Client/client/FrameworkExtension/Interlocked.cs new file mode 100644 index 0000000000..e635c23af1 --- /dev/null +++ b/projects/RabbitMQ.Client/client/FrameworkExtension/Interlocked.cs @@ -0,0 +1,19 @@ +using System.Runtime.CompilerServices; + +namespace RabbitMQ.Client +{ +#if NETCOREAPP3_1 || NETSTANDARD + internal static class Interlocked + { + public static ulong CompareExchange(ref ulong location1, ulong value, ulong comparand) + { + return (ulong)System.Threading.Interlocked.CompareExchange(ref Unsafe.As(ref location1), (long)value, (long)comparand); + } + + public static ulong Increment(ref ulong location1) + { + return (ulong)System.Threading.Interlocked.Add(ref Unsafe.As(ref location1), 1L); + } + } +#endif +} diff --git a/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs index be2d522d23..7ee4dba62e 100644 --- a/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs +++ b/projects/RabbitMQ.Client/client/api/AsyncDefaultBasicConsumer.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client @@ -12,29 +12,29 @@ public class AsyncDefaultBasicConsumer : IBasicConsumer, IAsyncBasicConsumer private readonly HashSet _consumerTags = new HashSet(); /// - /// Creates a new instance of an . + /// Creates a new instance of an . /// public AsyncDefaultBasicConsumer() { ShutdownReason = null; - Model = null; + Channel = null; IsRunning = false; } /// - /// Constructor which sets the Model property to the given value. + /// Constructor which sets the property to the given value. /// - /// Common AMQP model. - public AsyncDefaultBasicConsumer(IModel model) + /// The channel. + public AsyncDefaultBasicConsumer(IChannel channel) { ShutdownReason = null; IsRunning = false; - Model = model; + Channel = channel; } /// /// Retrieve the consumer tags this consumer is registered as; to be used when discussing this consumer - /// with the server, for instance with . + /// with the server, for instance with . /// public string[] ConsumerTags { @@ -50,7 +50,7 @@ public string[] ConsumerTags public bool IsRunning { get; protected set; } /// - /// If our shuts down, this property will contain a description of the reason for the + /// If our shuts down, this property will contain a description of the reason for the /// shutdown. Otherwise it will contain null. See . /// public ShutdownEventArgs ShutdownReason { get; protected set; } @@ -61,10 +61,10 @@ public string[] ConsumerTags public event AsyncEventHandler ConsumerCancelled; /// - /// Retrieve the this consumer is associated with, + /// Retrieve the this consumer is associated with, /// for use in acknowledging received messages, for instance. /// - public IModel Model { get; set; } + public IChannel Channel { get; set; } /// /// Called when the consumer is cancelled for reasons other than by a basicCancel: @@ -101,7 +101,7 @@ public virtual Task HandleBasicConsumeOk(string consumerTag) /// Called each time a message is delivered for this consumer. /// /// - /// This is a no-op implementation. It will not acknowledge deliveries via + /// This is a no-op implementation. It will not acknowledge deliveries via /// if consuming in automatic acknowledgement mode. /// Subclasses must copy or fully use delivery body before returning. /// Accessing the body at a later point is unsafe as its memory can @@ -120,7 +120,7 @@ public virtual Task HandleBasicDeliver(string consumerTag, } /// - /// Called when the model (channel) this consumer was registered on terminates. + /// Called when the channel this consumer was registered on terminates. /// /// A channel this consumer was registered on. /// Shutdown context. diff --git a/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs b/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs index 6339494d08..e8cff5449f 100644 --- a/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs +++ b/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs @@ -59,11 +59,11 @@ namespace RabbitMQ.Client /// // /// IConnection conn = factory.CreateConnection(); /// // - /// IModel ch = conn.CreateModel(); + /// IChannel ch = await conn.CreateChannelAsync().ConfigureAwait(false); /// // - /// // ... use ch's IModel methods ... + /// // ... use ch's IChannel methods ... /// // - /// ch.Close(Constants.ReplySuccess, "Closing the channel"); + /// await ch.CloseAsync().ConfigureAwait(false); /// conn.Close(Constants.ReplySuccess, "Closing the connection"); /// /// @@ -492,7 +492,7 @@ public IConnection CreateConnection(IEndpointResolver endpointResolver, string c else { var protocol = new RabbitMQ.Client.Framing.Protocol(); - conn = protocol.CreateConnection(this, false, endpointResolver.SelectOne(CreateFrameHandler), clientProvidedName); + conn = protocol.CreateConnection(this, endpointResolver.SelectOne(CreateFrameHandler), clientProvidedName); } } catch (Exception e) diff --git a/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs index 00f25b0b7d..f5f24a844e 100644 --- a/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs +++ b/projects/RabbitMQ.Client/client/api/DefaultBasicConsumer.cs @@ -32,7 +32,7 @@ using System; using System.Collections.Generic; using System.Linq; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client @@ -56,24 +56,24 @@ public class DefaultBasicConsumer : IBasicConsumer public DefaultBasicConsumer() { ShutdownReason = null; - Model = null; + Channel = null; IsRunning = false; } /// - /// Constructor which sets the Model property to the given value. + /// Constructor which sets the Channel property to the given value. /// - /// Common AMQP model. - public DefaultBasicConsumer(IModel model) + /// The channel. + public DefaultBasicConsumer(IChannel channel) { ShutdownReason = null; IsRunning = false; - Model = model; + Channel = channel; } /// /// Retrieve the consumer tags this consumer is registered as; to be used to identify - /// this consumer, for example, when cancelling it with . + /// this consumer, for example, when cancelling it with . /// This value is an array because a single consumer instance can be reused to consume on /// multiple channels. /// @@ -91,7 +91,7 @@ public string[] ConsumerTags public bool IsRunning { get; protected set; } /// - /// If our shuts down, this property will contain a description of the reason for the + /// If our shuts down, this property will contain a description of the reason for the /// shutdown. Otherwise it will contain null. See . /// public ShutdownEventArgs ShutdownReason { get; protected set; } @@ -102,10 +102,10 @@ public string[] ConsumerTags public event EventHandler ConsumerCancelled; /// - /// Retrieve the this consumer is associated with, + /// Retrieve the this consumer is associated with, /// for use in acknowledging received messages, for instance. /// - public IModel Model { get; set; } + public IChannel Channel { get; set; } /// /// Called when the consumer is cancelled for reasons other than by a basicCancel: @@ -141,7 +141,7 @@ public virtual void HandleBasicConsumeOk(string consumerTag) /// Called each time a message is delivered for this consumer. /// /// - /// This is a no-op implementation. It will not acknowledge deliveries via + /// This is a no-op implementation. It will not acknowledge deliveries via /// if consuming in automatic acknowledgement mode. /// Subclasses must copy or fully use delivery body before returning. /// Accessing the body at a later point is unsafe as its memory can @@ -159,7 +159,7 @@ public virtual void HandleBasicDeliver(string consumerTag, } /// - /// Called when the model (channel) this consumer was registered on terminates. + /// Called when the channel this consumer was registered on terminates. /// /// A channel this consumer was registered on. /// Shutdown context. diff --git a/projects/RabbitMQ.Client/client/api/ExchangeType.cs b/projects/RabbitMQ.Client/client/api/ExchangeType.cs index 545d5383a0..149fbd5bee 100644 --- a/projects/RabbitMQ.Client/client/api/ExchangeType.cs +++ b/projects/RabbitMQ.Client/client/api/ExchangeType.cs @@ -38,7 +38,7 @@ namespace RabbitMQ.Client /// /// /// Use the static members of this class as values for the - /// "exchangeType" arguments for IModel methods such as + /// "exchangeType" arguments for IChannel methods such as /// ExchangeDeclare. The broker may be extended with additional /// exchange types that do not appear in this class. /// diff --git a/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs index 10c0db9a15..ce6c514cdd 100644 --- a/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs +++ b/projects/RabbitMQ.Client/client/api/IAsyncBasicConsumer.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client @@ -8,10 +8,10 @@ namespace RabbitMQ.Client public interface IAsyncBasicConsumer { /// - /// Retrieve the this consumer is associated with, + /// Retrieve the this consumer is associated with, /// for use in acknowledging received messages, for instance. /// - IModel Model { get; } + IChannel Channel { get; } /// /// Signalled when the consumer gets cancelled. @@ -43,7 +43,7 @@ public interface IAsyncBasicConsumer /// /// /// Does nothing with the passed in information. - /// Note that in particular, some delivered messages may require acknowledgement via . + /// Note that in particular, some delivered messages may require acknowledgement via . /// The implementation of this method in this class does NOT acknowledge such messages. /// Task HandleBasicDeliver(string consumerTag, @@ -55,10 +55,10 @@ Task HandleBasicDeliver(string consumerTag, ReadOnlyMemory body); /// - /// Called when the model shuts down. + /// Called when the channel shuts down. /// - /// Common AMQP model. - /// Information about the reason why a particular model, session, or connection was destroyed. + /// The channel. + /// Information about the reason why a particular channel, session, or connection was destroyed. Task HandleModelShutdown(object model, ShutdownEventArgs reason); } } diff --git a/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs b/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs index 7b46667d82..5da0194dfe 100644 --- a/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs +++ b/projects/RabbitMQ.Client/client/api/IBasicConsumer.cs @@ -30,7 +30,7 @@ //--------------------------------------------------------------------------- using System; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client @@ -39,9 +39,6 @@ namespace RabbitMQ.Client ///receive messages from a queue by subscription. /// /// - /// See IModel.BasicConsume, IModel.BasicCancel. - /// - /// /// Note that the "Handle*" methods run in the connection's /// thread! Consider using , which uses a /// SharedQueue instance to safely pass received messages across @@ -51,10 +48,10 @@ namespace RabbitMQ.Client public interface IBasicConsumer { /// - /// Retrieve the this consumer is associated with, + /// Retrieve the this consumer is associated with, /// for use in acknowledging received messages, for instance. /// - IModel Model { get; } + IChannel Channel { get; } /// /// Signalled when the consumer gets cancelled. @@ -86,7 +83,7 @@ public interface IBasicConsumer /// /// /// Does nothing with the passed in information. - /// Note that in particular, some delivered messages may require acknowledgement via . + /// Note that in particular, some delivered messages may require acknowledgement via . /// The implementation of this method in this class does NOT acknowledge such messages. /// void HandleBasicDeliver(string consumerTag, @@ -98,10 +95,10 @@ void HandleBasicDeliver(string consumerTag, ReadOnlyMemory body); /// - /// Called when the model shuts down. + /// Called when the channel shuts down. /// - /// Common AMQP model. - /// Information about the reason why a particular model, session, or connection was destroyed. + /// The channel. + /// Information about the reason why a particular channel, session, or connection was destroyed. void HandleModelShutdown(object model, ShutdownEventArgs reason); } } diff --git a/projects/RabbitMQ.Client/client/api/IBasicProperties.cs b/projects/RabbitMQ.Client/client/api/IBasicProperties.cs index bf777de5b8..0608421126 100644 --- a/projects/RabbitMQ.Client/client/api/IBasicProperties.cs +++ b/projects/RabbitMQ.Client/client/api/IBasicProperties.cs @@ -38,12 +38,6 @@ namespace RabbitMQ.Client /// 0-8, 0-8qpid, 0-9 and 0-9-1 of AMQP. /// /// - /// The specification code generator provides - /// protocol-version-specific implementations of this interface. To - /// obtain an implementation of this interface in a - /// protocol-version-neutral way, use . - /// - /// /// Each property is readable, writable and clearable: a cleared /// property will not be transmitted over the wire. Properties on a /// fresh instance are clear by default. diff --git a/projects/RabbitMQ.Client/client/api/IConnection.cs b/projects/RabbitMQ.Client/client/api/IConnection.cs index 5da59a801f..da41c191e5 100644 --- a/projects/RabbitMQ.Client/client/api/IConnection.cs +++ b/projects/RabbitMQ.Client/client/api/IConnection.cs @@ -33,10 +33,10 @@ using System.Collections.Generic; using System.IO; using System.Threading; - +using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; -using RabbitMQ.Client.Impl; namespace RabbitMQ.Client { @@ -218,7 +218,7 @@ public interface IConnection : INetworkConnection, IDisposable /// Abort this connection and all its channels. /// /// - /// Note that all active channels, sessions, and models will be closed if this method is called. + /// Note that all active channels, sessions, and consumers will be closed if this method is called. /// In comparison to normal method, will not throw /// during closing connection. ///This method waits infinitely for the in-progress close operation to complete. @@ -275,7 +275,7 @@ public interface IConnection : INetworkConnection, IDisposable /// Close this connection and all its channels. /// /// - /// Note that all active channels, sessions, and models will be + /// Note that all active channels, sessions, and consumers will be /// closed if this method is called. It will wait for the in-progress /// close operation to complete. This method will not return to the caller /// until the shutdown is complete. If the connection is already closed @@ -304,7 +304,7 @@ public interface IConnection : INetworkConnection, IDisposable /// and wait with a timeout for all the in-progress close operations to complete. /// /// - /// Note that all active channels, sessions, and models will be + /// Note that all active channels, sessions, and consumers will be /// closed if this method is called. It will wait for the in-progress /// close operation to complete with a timeout. If the connection is /// already closed (or closing), then this method will do nothing. @@ -336,9 +336,9 @@ public interface IConnection : INetworkConnection, IDisposable void Close(ushort reasonCode, string reasonText, TimeSpan timeout); /// - /// Create and return a fresh channel, session, and model. + /// Create and return a fresh channel, session. /// - IModel CreateModel(); + ValueTask CreateChannelAsync(); /// /// Handle incoming Connection.Blocked methods. diff --git a/projects/RabbitMQ.Client/client/api/IModel.cs b/projects/RabbitMQ.Client/client/api/IModel.cs deleted file mode 100644 index a1a297fd2b..0000000000 --- a/projects/RabbitMQ.Client/client/api/IModel.cs +++ /dev/null @@ -1,533 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; - -using RabbitMQ.Client.Events; - -namespace RabbitMQ.Client -{ - /// - /// Common AMQP model, spanning the union of the - /// functionality offered by versions 0-8, 0-8qpid, 0-9 and 0-9-1 of AMQP. - /// - /// - /// Extends the interface, so that the "using" - /// statement can be used to scope the lifetime of a channel when appropriate. - /// - public interface IModel : IDisposable - { - /// - /// Channel number, unique per connections. - /// - int ChannelNumber { get; } - - /// - /// Returns null if the session is still in a state where it can be used, - /// or the cause of its closure otherwise. - /// - ShutdownEventArgs CloseReason { get; } - - /// Signalled when an unexpected message is delivered - /// - /// Under certain circumstances it is possible for a channel to receive a - /// message delivery which does not match any consumer which is currently - /// set up via basicConsume(). This will occur after the following sequence - /// of events: - /// - /// ctag = basicConsume(queue, consumer); // i.e. with explicit acks - /// // some deliveries take place but are not acked - /// basicCancel(ctag); - /// basicRecover(false); - /// - /// Since requeue is specified to be false in the basicRecover, the spec - /// states that the message must be redelivered to "the original recipient" - /// - i.e. the same channel / consumer-tag. But the consumer is no longer - /// active. - /// - /// In these circumstances, you can register a default consumer to handle - /// such deliveries. If no default consumer is registered an - /// InvalidOperationException will be thrown when such a delivery arrives. - /// - /// Most people will not need to use this. - IBasicConsumer DefaultConsumer { get; set; } - - /// - /// Returns true if the model is no longer in a state where it can be used. - /// - bool IsClosed { get; } - - /// - /// Returns true if the model is still in a state where it can be used. - /// Identical to checking if equals null. - bool IsOpen { get; } - - /// - /// When in confirm mode, return the sequence number of the next message to be published. - /// - ulong NextPublishSeqNo { get; } - - /// - /// Signalled when a Basic.Ack command arrives from the broker. - /// - event EventHandler BasicAcks; - - /// - /// Signalled when a Basic.Nack command arrives from the broker. - /// - event EventHandler BasicNacks; - - /// - /// All messages received before this fires that haven't been ack'ed will be redelivered. - /// All messages received afterwards won't be. - /// - /// - /// Handlers for this event are invoked by the connection thread. - /// It is sometimes useful to allow that thread to know that a recover-ok - /// has been received, rather than the thread that invoked . - /// - event EventHandler BasicRecoverOk; - - /// - /// Signalled when a Basic.Return command arrives from the broker. - /// - event EventHandler BasicReturn; - - /// - /// Signalled when an exception occurs in a callback invoked by the model. - /// - /// Examples of cases where this event will be signalled - /// include exceptions thrown in methods, or - /// exceptions thrown in delegates etc. - /// - event EventHandler CallbackException; - - event EventHandler FlowControl; - - /// - /// Notifies the destruction of the model. - /// - /// - /// If the model is already destroyed at the time an event - /// handler is added to this event, the event handler will be fired immediately. - /// - event EventHandler ModelShutdown; - - /// - /// Abort this session. - /// - /// - /// If the session is already closed (or closing), then this - /// method does nothing but wait for the in-progress close - /// operation to complete. This method will not return to the - /// caller until the shutdown is complete. - /// In comparison to normal method, will not throw - /// or or any other during closing model. - /// - void Abort(); - - /// - /// Abort this session. - /// - /// - /// The method behaves in the same way as , with the only - /// difference that the model is closed with the given model close code and message. - /// - /// The close code (See under "Reply Codes" in the AMQP specification) - /// - /// - /// A message indicating the reason for closing the model - /// - /// - void Abort(ushort replyCode, string replyText); - - /// - /// Acknowledge one or more delivered message(s). - /// - void BasicAck(ulong deliveryTag, bool multiple); - - /// - /// Delete a Basic content-class consumer. - /// - void BasicCancel(string consumerTag); - - /// - /// Same as BasicCancel but sets nowait to true and returns void (as there - /// will be no response from the server). - /// - void BasicCancelNoWait(string consumerTag); - - /// Start a Basic content-class consumer. - string BasicConsume( - string queue, - bool autoAck, - string consumerTag, - bool noLocal, - bool exclusive, - IDictionary arguments, - IBasicConsumer consumer); - - /// - /// Retrieve an individual message, if - /// one is available; returns null if the server answers that - /// no messages are currently available. See also . - /// - BasicGetResult BasicGet(string queue, bool autoAck); - - /// Reject one or more delivered message(s). - void BasicNack(ulong deliveryTag, bool multiple, bool requeue); - - /// - /// Publishes a message. - /// - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - /// - void BasicPublish(string exchange, string routingKey, bool mandatory, IBasicProperties basicProperties, ReadOnlyMemory body); - - /// - /// Configures QoS parameters of the Basic content-class. - /// - void BasicQos(uint prefetchSize, ushort prefetchCount, bool global); - - /// - /// Indicates that a consumer has recovered. - /// Deprecated. Should not be used. - /// - void BasicRecover(bool requeue); - - /// - /// Indicates that a consumer has recovered. - /// Deprecated. Should not be used. - /// - void BasicRecoverAsync(bool requeue); - - /// Reject a delivered message. - void BasicReject(ulong deliveryTag, bool requeue); - - /// Close this session. - /// - /// If the session is already closed (or closing), then this - /// method does nothing but wait for the in-progress close - /// operation to complete. This method will not return to the - /// caller until the shutdown is complete. - /// - void Close(); - - /// Close this session. - /// - /// The method behaves in the same way as Close(), with the only - /// difference that the model is closed with the given model - /// close code and message. - /// - /// The close code (See under "Reply Codes" in the AMQP specification) - /// - /// - /// A message indicating the reason for closing the model - /// - /// - void Close(ushort replyCode, string replyText); - - /// - /// Enable publisher acknowledgements. - /// - void ConfirmSelect(); - - /// - /// Creates a BasicPublishBatch instance - /// - IBasicPublishBatch CreateBasicPublishBatch(); - - /// - /// Creates a BasicPublishBatch instance - /// - IBasicPublishBatch CreateBasicPublishBatch(int sizeHint); - - /// - /// Construct a completely empty content header for use with the Basic content class. - /// - IBasicProperties CreateBasicProperties(); - - /// - /// Bind an exchange to an exchange. - /// - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - /// - void ExchangeBind(string destination, string source, string routingKey, IDictionary arguments); - - /// - /// Like ExchangeBind but sets nowait to true. - /// - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - /// - void ExchangeBindNoWait(string destination, string source, string routingKey, IDictionary arguments); - - /// Declare an exchange. - /// - /// The exchange is declared non-passive and non-internal. - /// The "nowait" option is not exercised. - /// - void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments); - - /// - /// Same as ExchangeDeclare but sets nowait to true and returns void (as there - /// will be no response from the server). - /// - void ExchangeDeclareNoWait(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments); - - /// - /// Do a passive exchange declaration. - /// - /// - /// This method performs a "passive declare" on an exchange, - /// which verifies whether . - /// It will do nothing if the exchange already exists and result - /// in a channel-level protocol exception (channel closure) if not. - /// - void ExchangeDeclarePassive(string exchange); - - /// - /// Delete an exchange. - /// - void ExchangeDelete(string exchange, bool ifUnused); - - /// - /// Like ExchangeDelete but sets nowait to true. - /// - void ExchangeDeleteNoWait(string exchange, bool ifUnused); - - /// - /// Unbind an exchange from an exchange. - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - void ExchangeUnbind(string destination, string source, string routingKey, IDictionary arguments); - - /// - /// Like ExchangeUnbind but sets nowait to true. - /// - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - /// - void ExchangeUnbindNoWait(string destination, string source, string routingKey, IDictionary arguments); - - /// - /// Bind a queue to an exchange. - /// - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - /// - void QueueBind(string queue, string exchange, string routingKey, IDictionary arguments); - - /// Same as QueueBind but sets nowait parameter to true. - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - /// - void QueueBindNoWait(string queue, string exchange, string routingKey, IDictionary arguments); - - /// - /// Declares a queue. See the Queues guide to learn more. - /// - /// The name of the queue. Pass an empty string to make the server generate a name. - /// Should this queue will survive a broker restart? - /// Should this queue use be limited to its declaring connection? Such a queue will be deleted when its declaring connection closes. - /// Should this queue be auto-deleted when its last consumer (if any) unsubscribes? - /// Optional; additional queue arguments, e.g. "x-queue-type" - QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments); - - /// - /// Declares a queue. See the Queues guide to learn more. - /// - /// The name of the queue. Pass an empty string to make the server generate a name. - /// Should this queue will survive a broker restart? - /// Should this queue use be limited to its declaring connection? Such a queue will be deleted when its declaring connection closes. - /// Should this queue be auto-deleted when its last consumer (if any) unsubscribes? - /// Optional; additional queue arguments, e.g. "x-queue-type" - void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary arguments); - - /// Declare a queue passively. - /// - ///The queue is declared passive, non-durable, - ///non-exclusive, and non-autodelete, with no arguments. - ///The queue is declared passively; i.e. only check if it exists. - /// - QueueDeclareOk QueueDeclarePassive(string queue); - - /// - /// Returns the number of messages in a queue ready to be delivered - /// to consumers. This method assumes the queue exists. If it doesn't, - /// an exception will be closed with an exception. - /// - /// The name of the queue - uint MessageCount(string queue); - - /// - /// Returns the number of consumers on a queue. - /// This method assumes the queue exists. If it doesn't, - /// an exception will be closed with an exception. - /// - /// The name of the queue - uint ConsumerCount(string queue); - - /// - /// Delete a queue. - /// - /// - ///Returns the number of messages purged during queue deletion. - /// uint.MaxValue. - /// - uint QueueDelete(string queue, bool ifUnused, bool ifEmpty); - - /// - ///Same as QueueDelete but sets nowait parameter to true - ///and returns void (as there will be no response from the server) - /// - void QueueDeleteNoWait(string queue, bool ifUnused, bool ifEmpty); - - /// - /// Purge a queue of messages. - /// - /// - /// Returns the number of messages purged. - /// - uint QueuePurge(string queue); - - /// - /// Unbind a queue from an exchange. - /// - /// - /// - /// Routing key must be shorter than 255 bytes. - /// - /// - void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments); - - /// - /// Commit this session's active TX transaction. - /// - void TxCommit(); - - /// - /// Roll back this session's active TX transaction. - /// - void TxRollback(); - - /// - /// Enable TX mode for this session. - /// - void TxSelect(); - - /// Wait until all published messages have been confirmed. - /// - /// - /// Waits until all messages published since the last call have - /// been either ack'd or nack'd by the broker. Returns whether - /// all the messages were ack'd (and none were nack'd). Note, - /// throws an exception when called on a non-Confirm channel. - /// - bool WaitForConfirms(); - - /// - /// Wait until all published messages have been confirmed. - /// - /// True if no nacks were received within the timeout, otherwise false. - /// How long to wait (at most) before returning - ///whether or not any nacks were returned. - /// - /// - /// Waits until all messages published since the last call have - /// been either ack'd or nack'd by the broker. Returns whether - /// all the messages were ack'd (and none were nack'd). Note, - /// throws an exception when called on a non-Confirm channel. - /// - bool WaitForConfirms(TimeSpan timeout); - - /// - /// Wait until all published messages have been confirmed. - /// - /// True if no nacks were received within the timeout, otherwise false. - /// How long to wait (at most) before returning - /// whether or not any nacks were returned. - /// - /// True if the method returned because - /// the timeout elapsed, not because all messages were ack'd or at least one nack'd. - /// - /// - /// Waits until all messages published since the last call have - /// been either ack'd or nack'd by the broker. Returns whether - /// all the messages were ack'd (and none were nack'd). Note, - /// throws an exception when called on a non-Confirm channel. - /// - bool WaitForConfirms(TimeSpan timeout, out bool timedOut); - - /// - /// Wait until all published messages have been confirmed. - /// - /// - /// Waits until all messages published since the last call have - /// been ack'd by the broker. If a nack is received, throws an - /// OperationInterrupedException exception immediately. - /// - void WaitForConfirmsOrDie(); - - /// - /// Wait until all published messages have been confirmed. - /// - /// - /// Waits until all messages published since the last call have - /// been ack'd by the broker. If a nack is received or the timeout - /// elapses, throws an OperationInterrupedException exception immediately. - /// - void WaitForConfirmsOrDie(TimeSpan timeout); - - /// - /// Amount of time protocol operations (e.g. queue.declare) are allowed to take before - /// timing out. - /// - TimeSpan ContinuationTimeout { get; set; } - } -} diff --git a/projects/RabbitMQ.Client/client/api/IModelExtensions.cs b/projects/RabbitMQ.Client/client/api/IModelExtensions.cs deleted file mode 100644 index d5bdab0678..0000000000 --- a/projects/RabbitMQ.Client/client/api/IModelExtensions.cs +++ /dev/null @@ -1,209 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; - -namespace RabbitMQ.Client -{ - public static class IModelExtensions - { - /// Start a Basic content-class consumer. - public static string BasicConsume(this IModel model, - IBasicConsumer consumer, - string queue, - bool autoAck = false, - string consumerTag = "", - bool noLocal = false, - bool exclusive = false, - IDictionary arguments = null) - { - return model.BasicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumer); - } - - /// Start a Basic content-class consumer. - public static string BasicConsume(this IModel model, string queue, bool autoAck, IBasicConsumer consumer) - { - return model.BasicConsume(queue, autoAck, "", false, false, null, consumer); - } - - /// Start a Basic content-class consumer. - public static string BasicConsume(this IModel model, string queue, - bool autoAck, - string consumerTag, - IBasicConsumer consumer) - { - return model.BasicConsume(queue, autoAck, consumerTag, false, false, null, consumer); - } - - /// Start a Basic content-class consumer. - public static string BasicConsume(this IModel model, string queue, - bool autoAck, - string consumerTag, - IDictionary arguments, - IBasicConsumer consumer) - { - return model.BasicConsume(queue, autoAck, consumerTag, false, false, arguments, consumer); - } - - /// - /// (Extension method) Convenience overload of BasicPublish. - /// - /// - /// The publication occurs with mandatory=false and immediate=false. - /// - public static void BasicPublish(this IModel model, PublicationAddress addr, IBasicProperties basicProperties, ReadOnlyMemory body) - { - model.BasicPublish(addr.ExchangeName, addr.RoutingKey, basicProperties: basicProperties, body: body); - } - - /// - /// (Extension method) Convenience overload of BasicPublish. - /// - /// - /// The publication occurs with mandatory=false - /// - public static void BasicPublish(this IModel model, string exchange, string routingKey, IBasicProperties basicProperties, ReadOnlyMemory body) - { - model.BasicPublish(exchange, routingKey, false, basicProperties, body); - } - - /// - /// (Spec method) Convenience overload of BasicPublish. - /// - public static void BasicPublish(this IModel model, string exchange, string routingKey, bool mandatory = false, IBasicProperties basicProperties = null, ReadOnlyMemory body = default) - { - model.BasicPublish(exchange, routingKey, mandatory, basicProperties, body); - } - - /// - /// (Spec method) Declare a queue. - /// - public static QueueDeclareOk QueueDeclare(this IModel model, string queue = "", bool durable = false, bool exclusive = true, - bool autoDelete = true, IDictionary arguments = null) - { - return model.QueueDeclare(queue, durable, exclusive, autoDelete, arguments); - } - - /// - /// (Extension method) Bind an exchange to an exchange. - /// - public static void ExchangeBind(this IModel model, string destination, string source, string routingKey, IDictionary arguments = null) - { - model.ExchangeBind(destination, source, routingKey, arguments); - } - - /// - /// (Extension method) Like exchange bind but sets nowait to true. - /// - public static void ExchangeBindNoWait(this IModel model, string destination, string source, string routingKey, IDictionary arguments = null) - { - model.ExchangeBindNoWait(destination, source, routingKey, arguments); - } - - /// - /// (Spec method) Declare an exchange. - /// - public static void ExchangeDeclare(this IModel model, string exchange, string type, bool durable = false, bool autoDelete = false, - IDictionary arguments = null) - { - model.ExchangeDeclare(exchange, type, durable, autoDelete, arguments); - } - - /// - /// (Extension method) Like ExchangeDeclare but sets nowait to true. - /// - public static void ExchangeDeclareNoWait(this IModel model, string exchange, string type, bool durable = false, bool autoDelete = false, - IDictionary arguments = null) - { - model.ExchangeDeclareNoWait(exchange, type, durable, autoDelete, arguments); - } - - /// - /// (Spec method) Unbinds an exchange. - /// - public static void ExchangeUnbind(this IModel model, string destination, - string source, - string routingKey, - IDictionary arguments = null) - { - model.ExchangeUnbind(destination, source, routingKey, arguments); - } - - /// - /// (Spec method) Deletes an exchange. - /// - public static void ExchangeDelete(this IModel model, string exchange, bool ifUnused = false) - { - model.ExchangeDelete(exchange, ifUnused); - } - - /// - /// (Extension method) Like ExchangeDelete but sets nowait to true. - /// - public static void ExchangeDeleteNoWait(this IModel model, string exchange, bool ifUnused = false) - { - model.ExchangeDeleteNoWait(exchange, ifUnused); - } - - /// - /// (Spec method) Binds a queue. - /// - public static void QueueBind(this IModel model, string queue, string exchange, string routingKey, IDictionary arguments = null) - { - model.QueueBind(queue, exchange, routingKey, arguments); - } - - /// - /// (Spec method) Deletes a queue. - /// - public static uint QueueDelete(this IModel model, string queue, bool ifUnused = false, bool ifEmpty = false) - { - return model.QueueDelete(queue, ifUnused, ifEmpty); - } - - /// - /// (Extension method) Like QueueDelete but sets nowait to true. - /// - public static void QueueDeleteNoWait(this IModel model, string queue, bool ifUnused = false, bool ifEmpty = false) - { - model.QueueDeleteNoWait(queue, ifUnused, ifEmpty); - } - - /// - /// (Spec method) Unbinds a queue. - /// - public static void QueueUnbind(this IModel model, string queue, string exchange, string routingKey, IDictionary arguments = null) - { - model.QueueUnbind(queue, exchange, routingKey, arguments); - } - } -} diff --git a/projects/RabbitMQ.Client/client/api/IRecoverable.cs b/projects/RabbitMQ.Client/client/api/IRecoverable.cs deleted file mode 100644 index 86f086812c..0000000000 --- a/projects/RabbitMQ.Client/client/api/IRecoverable.cs +++ /dev/null @@ -1,43 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; - -namespace RabbitMQ.Client -{ - /// - /// A marker interface for entities that are recoverable (currently connection or channel). - /// - public interface IRecoverable - { - event EventHandler Recovery; - } -} diff --git a/projects/RabbitMQ.Client/client/api/IStreamProperties.cs b/projects/RabbitMQ.Client/client/api/IStreamProperties.cs index 89686802f3..6b7f08cb01 100644 --- a/projects/RabbitMQ.Client/client/api/IStreamProperties.cs +++ b/projects/RabbitMQ.Client/client/api/IStreamProperties.cs @@ -39,12 +39,6 @@ namespace RabbitMQ.Client /// /// /// - /// The specification code generator provides - /// protocol-version-specific implementations of this interface. To - /// obtain an implementation of this interface in a - /// protocol-version-neutral way, use IModel.CreateStreamProperties(). - /// - /// /// Each property is readable, writable and clearable: a cleared /// property will not be transmitted over the wire. Properties on a fresh instance are clear by default. /// diff --git a/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs b/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs index 1ff0caafc1..c18d8612b7 100644 --- a/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs +++ b/projects/RabbitMQ.Client/client/api/ShutdownEventArgs.cs @@ -34,7 +34,7 @@ namespace RabbitMQ.Client { /// - /// Information about the reason why a particular model, session, or connection was destroyed. + /// Information about the reason why a particular channel, session, or connection was destroyed. /// /// /// The and properties should be used to determine the originator of the shutdown event. diff --git a/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs b/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs index cd0f311cfa..4d0651aab6 100644 --- a/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs +++ b/projects/RabbitMQ.Client/client/events/AsyncEventingBasicConsumer.cs @@ -1,13 +1,13 @@ using System; using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Events { public class AsyncEventingBasicConsumer : AsyncDefaultBasicConsumer { - ///Constructor which sets the Model property to the - ///given value. - public AsyncEventingBasicConsumer(IModel model) : base(model) + ///Constructor which sets the Channel property to the given value. + public AsyncEventingBasicConsumer(IChannel channel) : base(channel) { } @@ -24,7 +24,7 @@ public AsyncEventingBasicConsumer(IModel model) : base(model) ///Fires when the server confirms successful consumer cancelation. public event AsyncEventHandler Registered; - ///Fires on model (channel) shutdown, both client and server initiated. + ///Fires on channel shutdown, both client and server initiated. public event AsyncEventHandler Shutdown; ///Fires when the server confirms successful consumer cancelation. diff --git a/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs b/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs index ffd057f084..25451cbfdc 100644 --- a/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs +++ b/projects/RabbitMQ.Client/client/events/CallbackExceptionEventArgs.cs @@ -69,7 +69,7 @@ public IDictionary UpdateDetails(IDictionary oth /// When an exception is thrown from a callback registered with /// part of the RabbitMQ .NET client library, it is caught, /// packaged into a CallbackExceptionEventArgs, and passed through - /// the appropriate IModel's or IConnection's CallbackException + /// the appropriate IChannel's or IConnection's CallbackException /// event handlers. If an exception is thrown in a /// CallbackException handler, it is silently swallowed, as /// CallbackException is the last chance to handle these kinds of @@ -87,18 +87,7 @@ public CallbackExceptionEventArgs(Exception e) : base(e) { } - public static CallbackExceptionEventArgs Build(Exception e, - string context) - { - var details = new Dictionary - { - {"context", context} - }; - return Build(e, details); - } - - public static CallbackExceptionEventArgs Build(Exception e, - IDictionary details) + public static CallbackExceptionEventArgs Build(Exception e, IDictionary details) { var exnArgs = new CallbackExceptionEventArgs(e); exnArgs.UpdateDetails(details); diff --git a/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs b/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs index 9184a40784..edb804951a 100644 --- a/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs +++ b/projects/RabbitMQ.Client/client/events/EventingBasicConsumer.cs @@ -30,6 +30,7 @@ //--------------------------------------------------------------------------- using System; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Events { @@ -37,9 +38,8 @@ namespace RabbitMQ.Client.Events ///methods as separate events. public class EventingBasicConsumer : DefaultBasicConsumer { - ///Constructor which sets the Model property to the - ///given value. - public EventingBasicConsumer(IModel model) : base(model) + ///Constructor which sets the channel property to the given value. + public EventingBasicConsumer(IChannel channel) : base(channel) { } @@ -56,7 +56,7 @@ public EventingBasicConsumer(IModel model) : base(model) ///Fires when the server confirms successful consumer cancelation. public event EventHandler Registered; - ///Fires on model (channel) shutdown, both client and server initiated. + ///Fires on channel shutdown, both client and server initiated. public event EventHandler Shutdown; ///Fires when the server confirms successful consumer cancelation. diff --git a/projects/RabbitMQ.Client/client/exceptions/OperationInterruptedException.cs b/projects/RabbitMQ.Client/client/exceptions/OperationInterruptedException.cs index 36b22a5713..b97f1dfdde 100644 --- a/projects/RabbitMQ.Client/client/exceptions/OperationInterruptedException.cs +++ b/projects/RabbitMQ.Client/client/exceptions/OperationInterruptedException.cs @@ -38,11 +38,9 @@ namespace RabbitMQ.Client.Exceptions /// broker. For example, if a TCP connection dropping causes the /// destruction of a session in the middle of a QueueDeclare /// operation, an OperationInterruptedException will be thrown to - /// the caller of IModel.QueueDeclare. + /// the caller of IChannel.DeclareQueueAsync. /// -#if !NETSTANDARD1_5 [Serializable] -#endif public class OperationInterruptedException // TODO: inherit from OperationCanceledException : RabbitMQClientException diff --git a/projects/RabbitMQ.Client/client/exceptions/UnexpectedMethodException.cs b/projects/RabbitMQ.Client/client/exceptions/UnexpectedMethodException.cs index 590a8be199..f77f7f7f42 100644 --- a/projects/RabbitMQ.Client/client/exceptions/UnexpectedMethodException.cs +++ b/projects/RabbitMQ.Client/client/exceptions/UnexpectedMethodException.cs @@ -34,11 +34,9 @@ namespace RabbitMQ.Client.Exceptions { /// - /// Thrown when the model receives an RPC reply that it wasn't expecting. + /// Thrown when the channel receives an RPC reply that it wasn't expecting. /// -#if !NETSTANDARD1_5 [Serializable] -#endif public class UnexpectedMethodException : ProtocolViolationException { public UnexpectedMethodException(ushort classId, ushort methodId, string methodName) diff --git a/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodException.cs b/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodException.cs index 15b7cea0ae..1b614d227e 100644 --- a/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodException.cs +++ b/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodException.cs @@ -34,11 +34,9 @@ namespace RabbitMQ.Client.Exceptions { /// - /// Thrown when the model receives an RPC request it cannot satisfy. + /// Thrown when the channel receives an RPC request it cannot satisfy. /// -#if !NETSTANDARD1_5 [Serializable] -#endif public class UnsupportedMethodException : NotSupportedException { public UnsupportedMethodException(string methodName) diff --git a/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodFieldException.cs b/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodFieldException.cs index 706fb9d86e..c3c823005b 100644 --- a/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodFieldException.cs +++ b/projects/RabbitMQ.Client/client/exceptions/UnsupportedMethodFieldException.cs @@ -33,13 +33,11 @@ namespace RabbitMQ.Client.Exceptions { - /// Thrown when the model cannot transmit a method field - /// because the version of the protocol the model is implementing + /// Thrown when the channel cannot transmit a method field + /// because the version of the protocol the channel is implementing /// does not contain a definition for the field in /// question. -#if !NETSTANDARD1_5 [Serializable] -#endif public class UnsupportedMethodFieldException : NotSupportedException { public UnsupportedMethodFieldException(string methodName, string fieldName) diff --git a/projects/RabbitMQ.Client/client/framing/BasicProperties.cs b/projects/RabbitMQ.Client/client/framing/BasicProperties.cs index 362ced0f13..290fe2c5e4 100644 --- a/projects/RabbitMQ.Client/client/framing/BasicProperties.cs +++ b/projects/RabbitMQ.Client/client/framing/BasicProperties.cs @@ -37,7 +37,7 @@ namespace RabbitMQ.Client.Framing { /// Autogenerated type. AMQP specification content header properties for content class "basic" - internal sealed class BasicProperties : RabbitMQ.Client.Impl.BasicProperties + public sealed class BasicProperties : RabbitMQ.Client.Impl.BasicProperties { private string _contentType; private string _contentEncoding; diff --git a/projects/RabbitMQ.Client/client/framing/Model.cs b/projects/RabbitMQ.Client/client/framing/Model.cs deleted file mode 100644 index 10b7b3180d..0000000000 --- a/projects/RabbitMQ.Client/client/framing/Model.cs +++ /dev/null @@ -1,426 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using RabbitMQ.Client.client.framing; -using RabbitMQ.Client.Impl; - -namespace RabbitMQ.Client.Framing.Impl -{ - internal class Model: ModelBase - { - public Model(ISession session) - : base(session) - { - } - - public Model(ISession session, ConsumerWorkService workService) - : base(session, workService) - { - } - - public override void ConnectionTuneOk(ushort channelMax, uint frameMax, ushort heartbeat) - { - ModelSend(new ConnectionTuneOk(channelMax, frameMax, heartbeat)); - } - - public override void _Private_BasicCancel(string consumerTag, bool nowait) - { - ModelSend(new BasicCancel(consumerTag, nowait)); - } - - public override void _Private_BasicConsume(string queue, string consumerTag, bool noLocal, bool autoAck, bool exclusive, bool nowait, IDictionary arguments) - { - ModelSend(new BasicConsume(default, queue, consumerTag, noLocal, autoAck, exclusive, nowait, arguments)); - } - - public override void _Private_BasicGet(string queue, bool autoAck) - { - ModelSend(new BasicGet(default, queue, autoAck)); - } - - public override void _Private_BasicPublish(string exchange, string routingKey, bool mandatory, IBasicProperties basicProperties, ReadOnlyMemory body) - { - ModelSend(new BasicPublish(default, exchange, routingKey, mandatory, default), (BasicProperties) basicProperties, body); - } - - public override void _Private_BasicRecover(bool requeue) - { - ModelSend(new BasicRecover(requeue)); - } - - public override void _Private_ChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId) - { - ModelSend(new ChannelClose(replyCode, replyText, classId, methodId)); - } - - public override void _Private_ChannelCloseOk() - { - ModelSend(new ChannelCloseOk()); - } - - public override void _Private_ChannelFlowOk(bool active) - { - ModelSend(new ChannelFlowOk(active)); - } - - public override void _Private_ChannelOpen(string outOfBand) - { - ModelRpc(new ChannelOpen(outOfBand)); - } - - public override void _Private_ConfirmSelect(bool nowait) - { - var method = new ConfirmSelect(nowait); - if (nowait) - { - ModelSend(method); - } - else - { - ModelRpc(method); - } - } - - public override void _Private_ConnectionClose(ushort replyCode, string replyText, ushort classId, ushort methodId) - { - ModelRpc(new ConnectionClose(replyCode, replyText, classId, methodId)); - } - - public override void _Private_ConnectionCloseOk() - { - ModelSend(new ConnectionCloseOk()); - } - - public override void _Private_ConnectionOpen(string virtualHost, string capabilities, bool insist) - { - ModelSend(new ConnectionOpen(virtualHost, capabilities, insist)); - } - - public override void _Private_ConnectionSecureOk(byte[] response) - { - ModelSend(new ConnectionSecureOk(response)); - } - - public override void _Private_ConnectionStartOk(IDictionary clientProperties, string mechanism, byte[] response, string locale) - { - ModelSend(new ConnectionStartOk(clientProperties, mechanism, response, locale)); - } - - public override void _Private_UpdateSecret(byte[] newSecret, string reason) - { - ModelRpc(new ConnectionUpdateSecret(newSecret, reason)); - } - - public override void _Private_ExchangeBind(string destination, string source, string routingKey, bool nowait, IDictionary arguments) - { - ExchangeBind method = new ExchangeBind(default, destination, source, routingKey, nowait, arguments); - if (nowait) - { - ModelSend(method); - } - else - { - ModelRpc(method); - } - } - - public override void _Private_ExchangeDeclare(string exchange, string type, bool passive, bool durable, bool autoDelete, bool @internal, bool nowait, IDictionary arguments) - { - ExchangeDeclare method = new ExchangeDeclare(default, exchange, type, passive, durable, autoDelete, @internal, nowait, arguments); - if (nowait) - { - ModelSend(method); - } - else - { - ModelRpc(method); - } - } - - public override void _Private_ExchangeDelete(string exchange, bool ifUnused, bool nowait) - { - ExchangeDelete method = new ExchangeDelete(default, exchange, ifUnused, nowait); - if (nowait) - { - ModelSend(method); - } - else - { - ModelRpc(method); - } - } - - public override void _Private_ExchangeUnbind(string destination, string source, string routingKey, bool nowait, IDictionary arguments) - { - ExchangeUnbind method = new ExchangeUnbind(default, destination, source, routingKey, nowait, arguments); - if (nowait) - { - ModelSend(method); - } - else - { - ModelRpc(method); - } - } - - public override void _Private_QueueBind(string queue, string exchange, string routingKey, bool nowait, IDictionary arguments) - { - QueueBind method = new QueueBind(default, queue, exchange, routingKey, nowait, arguments); - if (nowait) - { - ModelSend(method); - } - else - { - ModelRpc(method); - } - } - - public override void _Private_QueueDeclare(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, bool nowait, IDictionary arguments) - { - QueueDeclare method = new QueueDeclare(default, queue, passive, durable, exclusive, autoDelete, nowait, arguments); - if (nowait) - { - ModelSend(method); - } - else - { - ModelSend(method); - } - } - - public override uint _Private_QueueDelete(string queue, bool ifUnused, bool ifEmpty, bool nowait) - { - QueueDelete method = new QueueDelete(default, queue, ifUnused, ifEmpty, nowait); - if (nowait) - { - ModelSend(method); - return 0xFFFFFFFF; - } - - return ModelRpc(method)._messageCount; - } - - public override uint _Private_QueuePurge(string queue, bool nowait) - { - QueuePurge method = new QueuePurge(default, queue, nowait); - if (nowait) - { - ModelSend(method); - return 0xFFFFFFFF; - } - - return ModelRpc(method)._messageCount; - } - - public override void BasicAck(ulong deliveryTag, bool multiple) - { - ModelSend(new BasicAck(deliveryTag, multiple)); - } - - public override void BasicNack(ulong deliveryTag, bool multiple, bool requeue) - { - ModelSend(new BasicNack(deliveryTag, multiple, requeue)); - } - - public override void BasicQos(uint prefetchSize, ushort prefetchCount, bool global) - { - ModelRpc(new BasicQos(prefetchSize, prefetchCount, global)); - } - - public override void BasicRecoverAsync(bool requeue) - { - ModelSend(new BasicRecoverAsync(requeue)); - } - - public override void BasicReject(ulong deliveryTag, bool requeue) - { - ModelSend(new BasicReject(deliveryTag, requeue)); - } - - public override IBasicProperties CreateBasicProperties() - { - return new BasicProperties(); - } - - public override void QueueUnbind(string queue, string exchange, string routingKey, IDictionary arguments) - { - ModelRpc(new QueueUnbind(default, queue, exchange, routingKey, arguments)); - } - - public override void TxCommit() - { - ModelRpc(new TxCommit()); - } - - public override void TxRollback() - { - ModelRpc(new TxRollback()); - } - - public override void TxSelect() - { - ModelRpc(new TxSelect()); - } - - public override bool DispatchAsynchronous(in IncomingCommand cmd) - { - switch (cmd.Method.ProtocolCommandId) - { - case ProtocolCommandId.BasicDeliver: - { - var __impl = (BasicDeliver)cmd.Method; - HandleBasicDeliver(__impl._consumerTag, __impl._deliveryTag, __impl._redelivered, __impl._exchange, __impl._routingKey, (IBasicProperties) cmd.Header, cmd.Body, cmd.TakeoverPayload()); - return true; - } - case ProtocolCommandId.BasicAck: - { - var __impl = (BasicAck)cmd.Method; - HandleBasicAck(__impl._deliveryTag, __impl._multiple); - return true; - } - case ProtocolCommandId.BasicCancel: - { - var __impl = (BasicCancel)cmd.Method; - HandleBasicCancel(__impl._consumerTag, __impl._nowait); - return true; - } - case ProtocolCommandId.BasicCancelOk: - { - var __impl = (BasicCancelOk)cmd.Method; - HandleBasicCancelOk(__impl._consumerTag); - return true; - } - case ProtocolCommandId.BasicConsumeOk: - { - var __impl = (BasicConsumeOk)cmd.Method; - HandleBasicConsumeOk(__impl._consumerTag); - return true; - } - case ProtocolCommandId.BasicGetEmpty: - { - HandleBasicGetEmpty(); - return true; - } - case ProtocolCommandId.BasicGetOk: - { - var __impl = (BasicGetOk)cmd.Method; - HandleBasicGetOk(__impl._deliveryTag, __impl._redelivered, __impl._exchange, __impl._routingKey, __impl._messageCount, (IBasicProperties) cmd.Header, cmd.Body, cmd.TakeoverPayload()); - return true; - } - case ProtocolCommandId.BasicNack: - { - var __impl = (BasicNack)cmd.Method; - HandleBasicNack(__impl._deliveryTag, __impl._multiple, __impl._requeue); - return true; - } - case ProtocolCommandId.BasicRecoverOk: - { - HandleBasicRecoverOk(); - return true; - } - case ProtocolCommandId.BasicReturn: - { - var __impl = (BasicReturn)cmd.Method; - HandleBasicReturn(__impl._replyCode, __impl._replyText, __impl._exchange, __impl._routingKey, (IBasicProperties) cmd.Header, cmd.Body, cmd.TakeoverPayload()); - return true; - } - case ProtocolCommandId.ChannelClose: - { - var __impl = (ChannelClose)cmd.Method; - HandleChannelClose(__impl._replyCode, __impl._replyText, __impl._classId, __impl._methodId); - return true; - } - case ProtocolCommandId.ChannelCloseOk: - { - HandleChannelCloseOk(); - return true; - } - case ProtocolCommandId.ChannelFlow: - { - var __impl = (ChannelFlow)cmd.Method; - HandleChannelFlow(__impl._active); - return true; - } - case ProtocolCommandId.ConnectionBlocked: - { - var __impl = (ConnectionBlocked)cmd.Method; - HandleConnectionBlocked(__impl._reason); - return true; - } - case ProtocolCommandId.ConnectionClose: - { - var __impl = (ConnectionClose)cmd.Method; - HandleConnectionClose(__impl._replyCode, __impl._replyText, __impl._classId, __impl._methodId); - return true; - } - case ProtocolCommandId.ConnectionOpenOk: - { - var __impl = (ConnectionOpenOk)cmd.Method; - HandleConnectionOpenOk(__impl._reserved1); - return true; - } - case ProtocolCommandId.ConnectionSecure: - { - var __impl = (ConnectionSecure)cmd.Method; - HandleConnectionSecure(__impl._challenge); - return true; - } - case ProtocolCommandId.ConnectionStart: - { - var __impl = (ConnectionStart)cmd.Method; - HandleConnectionStart(__impl._versionMajor, __impl._versionMinor, __impl._serverProperties, __impl._mechanisms, __impl._locales); - return true; - } - case ProtocolCommandId.ConnectionTune: - { - var __impl = (ConnectionTune)cmd.Method; - HandleConnectionTune(__impl._channelMax, __impl._frameMax, __impl._heartbeat); - return true; - } - case ProtocolCommandId.ConnectionUnblocked: - { - HandleConnectionUnblocked(); - return true; - } - case ProtocolCommandId.QueueDeclareOk: - { - var __impl = (QueueDeclareOk)cmd.Method; - HandleQueueDeclareOk(__impl._queue, __impl._messageCount, __impl._consumerCount); - return true; - } - default: return false; - } - } - } -} diff --git a/projects/RabbitMQ.Client/client/impl/AsyncConsumerDispatcher.cs b/projects/RabbitMQ.Client/client/impl/AsyncConsumerDispatcher.cs index 624f5bc910..be7cf5ea77 100644 --- a/projects/RabbitMQ.Client/client/impl/AsyncConsumerDispatcher.cs +++ b/projects/RabbitMQ.Client/client/impl/AsyncConsumerDispatcher.cs @@ -1,17 +1,17 @@ using System; -using System.Buffers; using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Impl { internal sealed class AsyncConsumerDispatcher : IConsumerDispatcher { - private readonly ModelBase _model; + private readonly ChannelBase _channelBase; private readonly AsyncConsumerWorkService _workService; - public AsyncConsumerDispatcher(ModelBase model, AsyncConsumerWorkService ws) + public AsyncConsumerDispatcher(ChannelBase channelBase, AsyncConsumerWorkService ws) { - _model = model; + _channelBase = channelBase; _workService = ws; IsShutdown = false; } @@ -21,9 +21,9 @@ public void Quiesce() IsShutdown = true; } - public Task Shutdown(IModel model) + public Task Shutdown() { - return _workService.Stop(model); + return _workService.Stop(_channelBase); } public bool IsShutdown @@ -64,7 +64,7 @@ public void HandleBasicCancel(IBasicConsumer consumer, string consumerTag) public void HandleModelShutdown(IBasicConsumer consumer, ShutdownEventArgs reason) { // the only case where we ignore the shutdown flag. - Schedule(new ModelShutdown(consumer, reason, _model)); + Schedule(new ModelShutdown(consumer, reason, _channelBase)); } private void ScheduleUnlessShuttingDown(Work work) @@ -77,7 +77,7 @@ private void ScheduleUnlessShuttingDown(Work work) private void Schedule(Work work) { - _workService.Schedule(_model, work); + _workService.Schedule(_channelBase, work); } } } diff --git a/projects/RabbitMQ.Client/client/impl/AsyncConsumerWorkService.cs b/projects/RabbitMQ.Client/client/impl/AsyncConsumerWorkService.cs index 2352f93f4c..58752dae64 100644 --- a/projects/RabbitMQ.Client/client/impl/AsyncConsumerWorkService.cs +++ b/projects/RabbitMQ.Client/client/impl/AsyncConsumerWorkService.cs @@ -1,44 +1,44 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -using RabbitMQ.Client.Events; +using RabbitMQ.Client.client.impl.Channel; +using Channel = System.Threading.Channels.Channel; namespace RabbitMQ.Client.Impl { internal sealed class AsyncConsumerWorkService : ConsumerWorkService { - private readonly ConcurrentDictionary _workPools = new ConcurrentDictionary(); - private readonly Func _startNewWorkPoolFunc; + private readonly ConcurrentDictionary _workPools = new ConcurrentDictionary(); + private readonly Func _startNewWorkPoolFunc; public AsyncConsumerWorkService(int concurrency) : base(concurrency) { - _startNewWorkPoolFunc = model => StartNewWorkPool(model); + _startNewWorkPoolFunc = channelBase => StartNewWorkPool(channelBase); } - public void Schedule(IModel model, Work work) + public void Schedule(ChannelBase channelBase, Work work) { /* * rabbitmq/rabbitmq-dotnet-client#841 * https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd * Note that the value delegate is not atomic but the Schedule method will not be called concurrently. */ - WorkPool workPool = _workPools.GetOrAdd(model, _startNewWorkPoolFunc); + WorkPool workPool = _workPools.GetOrAdd(channelBase, _startNewWorkPoolFunc); workPool.Enqueue(work); } - private WorkPool StartNewWorkPool(IModel model) + private WorkPool StartNewWorkPool(ChannelBase channelBase) { - var newWorkPool = new WorkPool(model, _concurrency); + var newWorkPool = new WorkPool(channelBase, _concurrency); newWorkPool.Start(); return newWorkPool; } - public Task Stop(IModel model) + public Task Stop(ChannelBase channelBase) { - if (_workPools.TryRemove(model, out WorkPool workPool)) + if (_workPools.TryRemove(channelBase, out WorkPool workPool)) { return workPool.Stop(); } @@ -49,16 +49,16 @@ public Task Stop(IModel model) private class WorkPool { private readonly Channel _channel; - private readonly IModel _model; + private readonly ChannelBase _channelBase; private Task _worker; private readonly int _concurrency; private SemaphoreSlim _limiter; private CancellationTokenSource _tokenSource; - public WorkPool(IModel model, int concurrency) + public WorkPool(ChannelBase channelBase, int concurrency) { _concurrency = concurrency; - _model = model; + _channelBase = channelBase; _channel = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true, SingleWriter = false, AllowSynchronousContinuations = false }); } @@ -102,17 +102,7 @@ private async Task Loop() } catch (Exception e) { - if (!(_model is ModelBase modelBase)) - { - return; - } - - var details = new Dictionary - { - { "consumer", work.Consumer }, - { "context", work.Consumer } - }; - modelBase.OnCallbackException(CallbackExceptionEventArgs.Build(e, details)); + _channelBase.OnUnhandledExceptionOccurred(e, work.Context, work.Consumer); } finally { @@ -136,7 +126,7 @@ private async Task LoopWithConcurrency(CancellationToken cancellationToken) await _limiter.WaitAsync(cancellationToken).ConfigureAwait(false); } - _ = HandleConcurrent(work, _model, _limiter); + _ = HandleConcurrent(work, _channelBase, _limiter); } } } @@ -146,7 +136,7 @@ private async Task LoopWithConcurrency(CancellationToken cancellationToken) } } - private static async Task HandleConcurrent(Work work, IModel model, SemaphoreSlim limiter) + private static async Task HandleConcurrent(Work work, ChannelBase channelBase, SemaphoreSlim limiter) { try { @@ -163,17 +153,7 @@ private static async Task HandleConcurrent(Work work, IModel model, SemaphoreSli } catch (Exception e) { - if (!(model is ModelBase modelBase)) - { - return; - } - - var details = new Dictionary - { - { "consumer", work.Consumer }, - { "context", work.Consumer } - }; - modelBase.OnCallbackException(CallbackExceptionEventArgs.Build(e, details)); + channelBase.OnUnhandledExceptionOccurred(e, work.Context, work.Consumer); } finally { diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs index ff39ba4a95..298b26e040 100644 --- a/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs +++ b/projects/RabbitMQ.Client/client/impl/AutorecoveringConnection.cs @@ -30,26 +30,24 @@ //--------------------------------------------------------------------------- using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Channels; using System.Threading.Tasks; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; using RabbitMQ.Client.Impl; using RabbitMQ.Client.Logging; +using Channel = System.Threading.Channels.Channel; namespace RabbitMQ.Client.Framing.Impl { internal sealed class AutorecoveringConnection : IConnection { - private bool _disposed = false; - private readonly object _eventLock = new object(); - private Connection _delegate; + private readonly object _eventLock = new object(); private readonly ConnectionFactory _factory; // list of endpoints provided on initial connection. @@ -60,19 +58,17 @@ internal sealed class AutorecoveringConnection : IConnection private readonly object _recordedEntitiesLock = new object(); private readonly Dictionary _recordedExchanges = new Dictionary(); - private readonly Dictionary _recordedQueues = new Dictionary(); - private readonly Dictionary _recordedBindings = new Dictionary(); - private readonly Dictionary _recordedConsumers = new Dictionary(); - - private readonly List _models = new List(); + private readonly List _channels = new List(); private EventHandler _recordedBlockedEventHandlers; private EventHandler _recordedShutdownEventHandlers; private EventHandler _recordedUnblockedEventHandlers; + private bool _disposed; + public AutorecoveringConnection(ConnectionFactory factory, string clientProvidedName = null) { _factory = factory; @@ -200,10 +196,6 @@ public AmqpTcpEndpoint[] KnownHosts public ProtocolBase Protocol => Delegate.Protocol; - public IDictionary RecordedExchanges { get; } = new ConcurrentDictionary(); - - public IDictionary RecordedQueues { get; } = new ConcurrentDictionary(); - public int RemotePort => Delegate.RemotePort; public IDictionary ServerProperties => Delegate.ServerProperties; @@ -224,11 +216,11 @@ private bool TryPerformAutomaticRecovery() RecoverConnectionBlockedHandlers(); RecoverConnectionUnblockedHandlers(); - RecoverModels(); + RecoverChannelsAsync().GetAwaiter().GetResult(); if (_factory.TopologyRecoveryEnabled) { - RecoverEntities(); - RecoverConsumers(); + RecoverEntitiesAsync().GetAwaiter().GetResult(); + RecoverConsumersAsync().GetAwaiter().GetResult(); } ESLog.Info("Connection recovery completed"); @@ -251,12 +243,11 @@ private bool TryPerformAutomaticRecovery() public void Close(ShutdownEventArgs reason) => Delegate.Close(reason); - public RecoveryAwareModel CreateNonRecoveringModel() + internal async ValueTask CreateNonRecoveringChannelAsync() { - ISession session = Delegate.CreateSession(); - var result = new RecoveryAwareModel(session) { ContinuationTimeout = _factory.ContinuationTimeout }; - result._Private_ChannelOpen(""); - return result; + var channel = new RecoveryAwareChannel(Delegate.CreateSession(), ConsumerWorkService) { ContinuationTimeout = _factory.ContinuationTimeout }; + await channel.OpenChannelAsync().ConfigureAwait(false); + return channel; } public void DeleteRecordedBinding(RecordedBinding rb) @@ -336,7 +327,7 @@ public void MaybeDeleteRecordedAutoDeleteExchange(string exchange) // last binding where this exchange is the source is gone, // remove recorded exchange // if it is auto-deleted. See bug 26364. - if ((rx != null) && rx.IsAutoDelete) + if (rx != null && rx.IsAutoDelete) { _recordedExchanges.Remove(exchange); } @@ -353,7 +344,7 @@ public void MaybeDeleteRecordedAutoDeleteQueue(string queue) _recordedQueues.TryGetValue(queue, out RecordedQueue rq); // last consumer on this connection is gone, remove recorded queue // if it is auto-deleted. See bug 26364. - if ((rq != null) && rq.IsAutoDelete) + if (rq != null && rq.IsAutoDelete) { _recordedQueues.Remove(queue); } @@ -401,11 +392,11 @@ public void RecordQueue(string name, RecordedQueue q) public override string ToString() => $"AutorecoveringConnection({Delegate.Id},{Endpoint},{GetHashCode()})"; - public void UnregisterModel(AutorecoveringModel model) + public void UnregisterChannel(AutorecoveringChannel channel) { - lock (_models) + lock (_channels) { - _models.Remove(model); + _channels.Remove(channel); } } @@ -421,8 +412,7 @@ public void Init(IEndpointResolver endpoints) private void Init(IFrameHandler fh) { ThrowIfDisposed(); - _delegate = new Connection(_factory, false, - fh, ClientProvidedName); + _delegate = new Connection(_factory, fh, ClientProvidedName); _recoveryTask = Task.Run(MainRecoveryLoop); @@ -440,8 +430,6 @@ private void Init(IFrameHandler fh) } } - - ///API-side invocation of updating the secret. public void UpdateSecret(string newSecret, string reason) { @@ -539,15 +527,15 @@ public void Close(ushort reasonCode, string reasonText, TimeSpan timeout) } } - public IModel CreateModel() + public async ValueTask CreateChannelAsync() { EnsureIsOpen(); - AutorecoveringModel m = new AutorecoveringModel(this, CreateNonRecoveringModel()); - lock (_models) + AutorecoveringChannel channel = new AutorecoveringChannel(this, await CreateNonRecoveringChannelAsync().ConfigureAwait(false)); + lock (_channels) { - _models.Add(m); + _channels.Add(channel); } - return m; + return channel; } void IDisposable.Dispose() => Dispose(true); @@ -597,7 +585,7 @@ private void Dispose(bool disposing) } finally { - _models.Clear(); + _channels.Clear(); _delegate = null; _recordedBlockedEventHandlers = null; _recordedShutdownEventHandlers = null; @@ -640,7 +628,7 @@ private void PropagateQueueNameChangeToConsumers(string oldName, string newName) } } - private void RecoverBindings() + private async Task RecoverBindingsAsync() { Dictionary recordedBindingsCopy; lock (_recordedBindings) @@ -652,12 +640,11 @@ private void RecoverBindings() { try { - b.Recover(); + await b.RecoverAsync().ConfigureAwait(false); } catch (Exception cause) { - string s = string.Format("Caught an exception while recovering binding between {0} and {1}: {2}", - b.Source, b.Destination, cause.Message); + string s = $"Caught an exception while recovering binding between {b.Source} and {b.Destination}: {cause.Message}"; HandleTopologyRecoveryException(new TopologyRecoveryException(s, cause)); } } @@ -678,7 +665,7 @@ private bool TryRecoverConnectionDelegate() try { IFrameHandler fh = _endpoints.SelectOne(_factory.CreateFrameHandler); - _delegate = new Connection(_factory, false, fh, ClientProvidedName); + _delegate = new Connection(_factory, fh, ClientProvidedName); return true; } catch (Exception e) @@ -707,7 +694,7 @@ private bool TryRecoverConnectionDelegate() private void RecoverConnectionUnblockedHandlers() => Delegate.ConnectionUnblocked += _recordedUnblockedEventHandlers; - private void RecoverConsumers() + private async Task RecoverConsumersAsync() { ThrowIfDisposed(); Dictionary recordedConsumersCopy; @@ -723,7 +710,8 @@ private void RecoverConsumers() try { - string newTag = cons.Recover(); + await cons.RecoverAsync().ConfigureAwait(false); + string newTag = cons.ConsumerTag; lock (_recordedConsumers) { // make sure server-generated tags are re-added @@ -748,14 +736,13 @@ private void RecoverConsumers() } catch (Exception cause) { - string s = string.Format("Caught an exception while recovering consumer {0} on queue {1}: {2}", - tag, cons.Queue, cause.Message); + string s = $"Caught an exception while recovering consumer {tag} on queue {cons.Queue}: {cause.Message}"; HandleTopologyRecoveryException(new TopologyRecoveryException(s, cause)); } } } - private void RecoverEntities() + private async Task RecoverEntitiesAsync() { // The recovery sequence is the following: // @@ -763,12 +750,12 @@ private void RecoverEntities() // 2. Recover queues // 3. Recover bindings // 4. Recover consumers - RecoverExchanges(); - RecoverQueues(); - RecoverBindings(); + await RecoverExchangesAsync().ConfigureAwait(false); + await RecoverQueuesAsync().ConfigureAwait(false); + await RecoverBindingsAsync().ConfigureAwait(false); } - private void RecoverExchanges() + private async Task RecoverExchangesAsync() { Dictionary recordedExchangesCopy; lock (_recordedEntitiesLock) @@ -780,7 +767,7 @@ private void RecoverExchanges() { try { - rx.Recover(); + await rx.RecoverAsync().ConfigureAwait(false); } catch (Exception cause) { @@ -791,18 +778,21 @@ private void RecoverExchanges() } } - private void RecoverModels() + private async Task RecoverChannelsAsync() { - lock (_models) + AutorecoveringChannel[] copy; + lock (_channels) { - foreach (AutorecoveringModel m in _models) - { - m.AutomaticallyRecover(this); - } + copy = _channels.ToArray(); + } + + foreach (AutorecoveringChannel m in copy) + { + await m.AutomaticallyRecoverAsync(this).ConfigureAwait(false); } } - private void RecoverQueues() + private async Task RecoverQueuesAsync() { Dictionary recordedQueuesCopy; lock (_recordedEntitiesLock) @@ -817,7 +807,7 @@ private void RecoverQueues() try { - rq.Recover(); + await rq.RecoverAsync().ConfigureAwait(false); string newName = rq.Name; if (!oldName.Equals(newName)) diff --git a/projects/RabbitMQ.Client/client/impl/AutorecoveringModel.cs b/projects/RabbitMQ.Client/client/impl/AutorecoveringModel.cs deleted file mode 100644 index deaa18ce64..0000000000 --- a/projects/RabbitMQ.Client/client/impl/AutorecoveringModel.cs +++ /dev/null @@ -1,1128 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Xml.Schema; - -using RabbitMQ.Client.Events; -using RabbitMQ.Client.Framing.Impl; - -namespace RabbitMQ.Client.Impl -{ - internal sealed class AutorecoveringModel : IFullModel, IRecoverable - { - private bool _disposed = false; - private readonly object _eventLock = new object(); - private AutorecoveringConnection _connection; - private RecoveryAwareModel _delegate; - - private EventHandler _recordedBasicAckEventHandlers; - private EventHandler _recordedBasicNackEventHandlers; - private EventHandler _recordedBasicReturnEventHandlers; - private EventHandler _recordedCallbackExceptionEventHandlers; - private EventHandler _recordedShutdownEventHandlers; - - private ushort _prefetchCountConsumer = 0; - private ushort _prefetchCountGlobal = 0; - private bool _usesPublisherConfirms = false; - private bool _usesTransactions = false; - - public IConsumerDispatcher ConsumerDispatcher => !_disposed ? _delegate.ConsumerDispatcher : throw new ObjectDisposedException(GetType().FullName); - - public TimeSpan ContinuationTimeout - { - get => Delegate.ContinuationTimeout; - set => Delegate.ContinuationTimeout = value; - } - - public AutorecoveringModel(AutorecoveringConnection conn, RecoveryAwareModel _delegate) - { - _connection = conn; - this._delegate = _delegate; - } - - public event EventHandler BasicAcks - { - add - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedBasicAckEventHandlers += value; - _delegate.BasicAcks += value; - } - } - remove - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedBasicAckEventHandlers -= value; - _delegate.BasicAcks -= value; - } - } - } - - public event EventHandler BasicNacks - { - add - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedBasicNackEventHandlers += value; - _delegate.BasicNacks += value; - } - } - remove - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedBasicNackEventHandlers -= value; - _delegate.BasicNacks -= value; - } - } - } - - public event EventHandler BasicRecoverOk - { - add - { - // TODO: record and re-add handlers - Delegate.BasicRecoverOk += value; - } - remove - { - Delegate.BasicRecoverOk -= value; - } - } - - public event EventHandler BasicReturn - { - add - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedBasicReturnEventHandlers += value; - _delegate.BasicReturn += value; - } - } - remove - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedBasicReturnEventHandlers -= value; - _delegate.BasicReturn -= value; - } - } - } - - public event EventHandler CallbackException - { - add - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedCallbackExceptionEventHandlers += value; - _delegate.CallbackException += value; - } - } - remove - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedCallbackExceptionEventHandlers -= value; - _delegate.CallbackException -= value; - } - } - } - - public event EventHandler FlowControl - { - add { Delegate.FlowControl += value; } - remove { Delegate.FlowControl -= value; } - } - - public event EventHandler ModelShutdown - { - add - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedShutdownEventHandlers += value; - _delegate.ModelShutdown += value; - } - } - remove - { - ThrowIfDisposed(); - lock (_eventLock) - { - _recordedShutdownEventHandlers -= value; - _delegate.ModelShutdown -= value; - } - } - } - - public event EventHandler Recovery; - - public int ChannelNumber => Delegate.ChannelNumber; - - public ShutdownEventArgs CloseReason => Delegate.CloseReason; - - public IBasicConsumer DefaultConsumer - { - get => Delegate.DefaultConsumer; - set => Delegate.DefaultConsumer = value; - } - - public IModel Delegate => RecoveryAwareDelegate; - private RecoveryAwareModel RecoveryAwareDelegate => !_disposed ? _delegate : throw new ObjectDisposedException(GetType().FullName); - - public bool IsClosed => _delegate is object && _delegate.IsClosed; - - public bool IsOpen => _delegate is object && _delegate.IsOpen; - - public ulong NextPublishSeqNo => Delegate.NextPublishSeqNo; - - public void AutomaticallyRecover(AutorecoveringConnection conn) - { - ThrowIfDisposed(); - _connection = conn; - RecoveryAwareModel defunctModel = _delegate; - - _delegate = conn.CreateNonRecoveringModel(); - _delegate.InheritOffsetFrom(defunctModel); - - RecoverModelShutdownHandlers(); - RecoverState(); - - RecoverBasicReturnHandlers(); - RecoverBasicAckHandlers(); - RecoverBasicNackHandlers(); - RecoverCallbackExceptionHandlers(); - - RunRecoveryEventHandlers(); - } - - public void BasicQos(ushort prefetchCount, - bool global) => Delegate.BasicQos(0, prefetchCount, global); - - public void Close(ushort replyCode, string replyText, bool abort) - { - ThrowIfDisposed(); - try - { - _delegate.Close(replyCode, replyText, abort); - } - finally - { - _connection.UnregisterModel(this); - } - } - - public void Close(ShutdownEventArgs reason, bool abort) - { - ThrowIfDisposed(); - try - { - _delegate.Close(reason, abort).GetAwaiter().GetResult();; - } - finally - { - _connection.UnregisterModel(this); - } - } - - public override string ToString() => Delegate.ToString(); - - void IDisposable.Dispose() => Dispose(true); - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - Abort(); - - _connection = null; - _delegate = null; - _recordedBasicAckEventHandlers = null; - _recordedBasicNackEventHandlers = null; - _recordedBasicReturnEventHandlers = null; - _recordedCallbackExceptionEventHandlers = null; - _recordedShutdownEventHandlers = null; - - _disposed = true; - } - } - - public void ConnectionTuneOk(ushort channelMax, - uint frameMax, - ushort heartbeat) - { - ThrowIfDisposed(); - _delegate.ConnectionTuneOk(channelMax, frameMax, heartbeat); - } - - public void HandleBasicAck(ulong deliveryTag, bool multiple) - { - ThrowIfDisposed(); - _delegate.HandleBasicAck(deliveryTag, multiple); - } - - public void HandleBasicCancel(string consumerTag, bool nowait) - { - ThrowIfDisposed(); - _delegate.HandleBasicCancel(consumerTag, nowait); - } - - public void HandleBasicCancelOk(string consumerTag) - { - ThrowIfDisposed(); - _delegate.HandleBasicCancelOk(consumerTag); - } - - public void HandleBasicConsumeOk(string consumerTag) - { - ThrowIfDisposed(); - _delegate.HandleBasicConsumeOk(consumerTag); - } - - public void HandleBasicDeliver(string consumerTag, - ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - ThrowIfDisposed(); - _delegate.HandleBasicDeliver(consumerTag, deliveryTag, redelivered, exchange, routingKey, basicProperties, body, rentedArray); - } - - public void HandleBasicGetEmpty() - { - ThrowIfDisposed(); - _delegate.HandleBasicGetEmpty(); - } - - public void HandleBasicGetOk(ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - uint messageCount, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - ThrowIfDisposed(); - _delegate.HandleBasicGetOk(deliveryTag, redelivered, exchange, routingKey, messageCount, basicProperties, body, rentedArray); - } - - public void HandleBasicNack(ulong deliveryTag, - bool multiple, - bool requeue) - { - ThrowIfDisposed(); - _delegate.HandleBasicNack(deliveryTag, multiple, requeue); - } - - public void HandleBasicRecoverOk() - { - ThrowIfDisposed(); - _delegate.HandleBasicRecoverOk(); - } - - public void HandleBasicReturn(ushort replyCode, - string replyText, - string exchange, - string routingKey, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - ThrowIfDisposed(); - _delegate.HandleBasicReturn(replyCode, replyText, exchange, routingKey, basicProperties, body, rentedArray); - } - - public void HandleChannelClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId) - { - ThrowIfDisposed(); - _delegate.HandleChannelClose(replyCode, replyText, classId, methodId); - } - - public void HandleChannelCloseOk() - { - ThrowIfDisposed(); - _delegate.HandleChannelCloseOk(); - } - - public void HandleChannelFlow(bool active) - { - ThrowIfDisposed(); - _delegate.HandleChannelFlow(active); - } - - public void HandleConnectionBlocked(string reason) - { - ThrowIfDisposed(); - _delegate.HandleConnectionBlocked(reason); - } - - public void HandleConnectionClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId) - { - ThrowIfDisposed(); - _delegate.HandleConnectionClose(replyCode, replyText, classId, methodId); - } - - public void HandleConnectionOpenOk(string knownHosts) - { - ThrowIfDisposed(); - _delegate.HandleConnectionOpenOk(knownHosts); - } - - public void HandleConnectionSecure(byte[] challenge) - { - ThrowIfDisposed(); - _delegate.HandleConnectionSecure(challenge); - } - - public void HandleConnectionStart(byte versionMajor, - byte versionMinor, - IDictionary serverProperties, - byte[] mechanisms, - byte[] locales) - { - ThrowIfDisposed(); - _delegate.HandleConnectionStart(versionMajor, versionMinor, serverProperties, - mechanisms, locales); - } - - public void HandleConnectionTune(ushort channelMax, - uint frameMax, - ushort heartbeat) - { - ThrowIfDisposed(); - _delegate.HandleConnectionTune(channelMax, frameMax, heartbeat); - } - - public void HandleConnectionUnblocked() - { - ThrowIfDisposed(); - _delegate.HandleConnectionUnblocked(); - } - - public void HandleQueueDeclareOk(string queue, - uint messageCount, - uint consumerCount) - { - ThrowIfDisposed(); - _delegate.HandleQueueDeclareOk(queue, messageCount, consumerCount); - } - - public void _Private_BasicCancel(string consumerTag, - bool nowait) - { - ThrowIfDisposed(); - _delegate._Private_BasicCancel(consumerTag, nowait); - } - - public void _Private_BasicConsume(string queue, - string consumerTag, - bool noLocal, - bool autoAck, - bool exclusive, - bool nowait, - IDictionary arguments) - { - ThrowIfDisposed(); - _delegate._Private_BasicConsume(queue, - consumerTag, - noLocal, - autoAck, - exclusive, - nowait, - arguments); - } - - public void _Private_BasicGet(string queue, bool autoAck) - { - ThrowIfDisposed(); - _delegate._Private_BasicGet(queue, autoAck); - } - - public void _Private_BasicPublish(string exchange, - string routingKey, - bool mandatory, - IBasicProperties basicProperties, - ReadOnlyMemory body) - { - if (routingKey is null) - { - throw new ArgumentNullException(nameof(routingKey)); - } - - ThrowIfDisposed(); - _delegate._Private_BasicPublish(exchange, routingKey, mandatory, - basicProperties, body); - } - - public void _Private_BasicRecover(bool requeue) - { - ThrowIfDisposed(); - _delegate._Private_BasicRecover(requeue); - } - - public void _Private_ChannelClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId) - { - ThrowIfDisposed(); - _delegate._Private_ChannelClose(replyCode, replyText, - classId, methodId); - } - - public void _Private_ChannelCloseOk() - { - ThrowIfDisposed(); - _delegate._Private_ChannelCloseOk(); - } - - public void _Private_ChannelFlowOk(bool active) - { - ThrowIfDisposed(); - _delegate._Private_ChannelFlowOk(active); - } - - public void _Private_ChannelOpen(string outOfBand) - { - ThrowIfDisposed(); - _delegate._Private_ChannelOpen(outOfBand); - } - - public void _Private_ConfirmSelect(bool nowait) - { - ThrowIfDisposed(); - _delegate._Private_ConfirmSelect(nowait); - } - - public void _Private_ConnectionClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId) - { - ThrowIfDisposed(); - _delegate._Private_ConnectionClose(replyCode, replyText, - classId, methodId); - } - - public void _Private_ConnectionCloseOk() - { - ThrowIfDisposed(); - _delegate._Private_ConnectionCloseOk(); - } - - public void _Private_ConnectionOpen(string virtualHost, - string capabilities, - bool insist) - { - ThrowIfDisposed(); - _delegate._Private_ConnectionOpen(virtualHost, capabilities, insist); - } - - public void _Private_ConnectionSecureOk(byte[] response) - { - ThrowIfDisposed(); - _delegate._Private_ConnectionSecureOk(response); - } - - public void _Private_ConnectionStartOk(IDictionary clientProperties, - string mechanism, byte[] response, string locale) - { - ThrowIfDisposed(); - _delegate._Private_ConnectionStartOk(clientProperties, mechanism, - response, locale); - } - - public void _Private_UpdateSecret(byte[] newSecret, string reason) - { - ThrowIfDisposed(); - _delegate._Private_UpdateSecret(newSecret, reason); - } - - public void _Private_ExchangeBind(string destination, - string source, - string routingKey, - bool nowait, - IDictionary arguments) - { - ThrowIfDisposed(); - _delegate._Private_ExchangeBind(destination, source, routingKey, - nowait, arguments); - } - - public void _Private_ExchangeDeclare(string exchange, - string type, - bool passive, - bool durable, - bool autoDelete, - bool @internal, - bool nowait, - IDictionary arguments) - { - ThrowIfDisposed(); - _delegate._Private_ExchangeDeclare(exchange, type, passive, - durable, autoDelete, @internal, - nowait, arguments); - } - - public void _Private_ExchangeDelete(string exchange, - bool ifUnused, - bool nowait) - { - ThrowIfDisposed(); - _delegate._Private_ExchangeDelete(exchange, ifUnused, nowait); - } - - public void _Private_ExchangeUnbind(string destination, - string source, - string routingKey, - bool nowait, - IDictionary arguments) - { - ThrowIfDisposed(); - _delegate._Private_ExchangeUnbind(destination, source, routingKey, - nowait, arguments); - } - - public void _Private_QueueBind(string queue, - string exchange, - string routingKey, - bool nowait, - IDictionary arguments) - { - ThrowIfDisposed(); - _delegate._Private_QueueBind(queue, exchange, routingKey, - nowait, arguments); - } - - public void _Private_QueueDeclare(string queue, - bool passive, - bool durable, - bool exclusive, - bool autoDelete, - bool nowait, - IDictionary arguments) => RecoveryAwareDelegate._Private_QueueDeclare(queue, passive, - durable, exclusive, autoDelete, - nowait, arguments); - - public uint _Private_QueueDelete(string queue, bool ifUnused, bool ifEmpty, bool nowait) => RecoveryAwareDelegate._Private_QueueDelete(queue, ifUnused, ifEmpty, nowait); - - public uint _Private_QueuePurge(string queue, bool nowait) => RecoveryAwareDelegate._Private_QueuePurge(queue, nowait); - - public void Abort() - { - ThrowIfDisposed(); - try - { - _delegate.Abort(); - } - finally - { - _connection.UnregisterModel(this); - } - } - - public void Abort(ushort replyCode, string replyText) - { - ThrowIfDisposed(); - try - { - _delegate.Abort(replyCode, replyText); - } - finally - { - _connection.UnregisterModel(this); - } - } - - public void BasicAck(ulong deliveryTag, - bool multiple) => Delegate.BasicAck(deliveryTag, multiple); - - public void BasicCancel(string consumerTag) - { - ThrowIfDisposed(); - RecordedConsumer cons = _connection.DeleteRecordedConsumer(consumerTag); - if (cons != null) - { - _connection.MaybeDeleteRecordedAutoDeleteQueue(cons.Queue); - } - _delegate.BasicCancel(consumerTag); - } - - public void BasicCancelNoWait(string consumerTag) - { - ThrowIfDisposed(); - RecordedConsumer cons = _connection.DeleteRecordedConsumer(consumerTag); - if (cons != null) - { - _connection.MaybeDeleteRecordedAutoDeleteQueue(cons.Queue); - } - _delegate.BasicCancelNoWait(consumerTag); - } - - public string BasicConsume( - string queue, - bool autoAck, - string consumerTag, - bool noLocal, - bool exclusive, - IDictionary arguments, - IBasicConsumer consumer) - { - string result = Delegate.BasicConsume(queue, autoAck, consumerTag, noLocal, - exclusive, arguments, consumer); - RecordedConsumer rc = new RecordedConsumer(this, queue). - WithConsumerTag(result). - WithConsumer(consumer). - WithExclusive(exclusive). - WithAutoAck(autoAck). - WithArguments(arguments); - _connection.RecordConsumer(result, rc); - return result; - } - - public BasicGetResult BasicGet(string queue, - bool autoAck) => Delegate.BasicGet(queue, autoAck); - - public void BasicNack(ulong deliveryTag, - bool multiple, - bool requeue) => Delegate.BasicNack(deliveryTag, multiple, requeue); - - public void BasicPublish(string exchange, - string routingKey, - bool mandatory, - IBasicProperties basicProperties, - ReadOnlyMemory body) - { - if (routingKey is null) - { - throw new ArgumentNullException(nameof(routingKey)); - } - - Delegate.BasicPublish(exchange, - routingKey, - mandatory, - basicProperties, - body); - } - - public void BasicQos(uint prefetchSize, - ushort prefetchCount, - bool global) - { - ThrowIfDisposed(); - if (global) - { - _prefetchCountGlobal = prefetchCount; - } - else - { - _prefetchCountConsumer = prefetchCount; - } - _delegate.BasicQos(prefetchSize, prefetchCount, global); - } - - public void BasicRecover(bool requeue) => Delegate.BasicRecover(requeue); - - public void BasicRecoverAsync(bool requeue) => Delegate.BasicRecoverAsync(requeue); - - public void BasicReject(ulong deliveryTag, - bool requeue) => Delegate.BasicReject(deliveryTag, requeue); - - public void Close() - { - ThrowIfDisposed(); - try - { - _delegate.Close(); - } - finally - { - _connection.UnregisterModel(this); - } - } - - public void Close(ushort replyCode, string replyText) - { - ThrowIfDisposed(); - try - { - _delegate.Close(replyCode, replyText); - } - finally - { - _connection.UnregisterModel(this); - } - } - - public void ConfirmSelect() - { - Delegate.ConfirmSelect(); - _usesPublisherConfirms = true; - } - - public IBasicProperties CreateBasicProperties() => Delegate.CreateBasicProperties(); - - public void ExchangeBind(string destination, - string source, - string routingKey, - IDictionary arguments) - { - ThrowIfDisposed(); - RecordedBinding eb = new RecordedExchangeBinding(this). - WithSource(source). - WithDestination(destination). - WithRoutingKey(routingKey). - WithArguments(arguments); - _connection.RecordBinding(eb); - _delegate.ExchangeBind(destination, source, routingKey, arguments); - } - - public void ExchangeBindNoWait(string destination, - string source, - string routingKey, - IDictionary arguments) => Delegate.ExchangeBindNoWait(destination, source, routingKey, arguments); - - public void ExchangeDeclare(string exchange, string type, bool durable, - bool autoDelete, IDictionary arguments) - { - ThrowIfDisposed(); - RecordedExchange rx = new RecordedExchange(this, exchange). - WithType(type). - WithDurable(durable). - WithAutoDelete(autoDelete). - WithArguments(arguments); - _delegate.ExchangeDeclare(exchange, type, durable, - autoDelete, arguments); - _connection.RecordExchange(exchange, rx); - } - - public void ExchangeDeclareNoWait(string exchange, - string type, - bool durable, - bool autoDelete, - IDictionary arguments) - { - ThrowIfDisposed(); - RecordedExchange rx = new RecordedExchange(this, exchange). - WithType(type). - WithDurable(durable). - WithAutoDelete(autoDelete). - WithArguments(arguments); - _delegate.ExchangeDeclareNoWait(exchange, type, durable, - autoDelete, arguments); - _connection.RecordExchange(exchange, rx); - } - - public void ExchangeDeclarePassive(string exchange) => Delegate.ExchangeDeclarePassive(exchange); - - public void ExchangeDelete(string exchange, - bool ifUnused) - { - Delegate.ExchangeDelete(exchange, ifUnused); - _connection.DeleteRecordedExchange(exchange); - } - - public void ExchangeDeleteNoWait(string exchange, - bool ifUnused) - { - Delegate.ExchangeDeleteNoWait(exchange, ifUnused); - _connection.DeleteRecordedExchange(exchange); - } - - public void ExchangeUnbind(string destination, - string source, - string routingKey, - IDictionary arguments) - { - ThrowIfDisposed(); - RecordedBinding eb = new RecordedExchangeBinding(this). - WithSource(source). - WithDestination(destination). - WithRoutingKey(routingKey). - WithArguments(arguments); - _connection.DeleteRecordedBinding(eb); - _delegate.ExchangeUnbind(destination, source, routingKey, arguments); - _connection.MaybeDeleteRecordedAutoDeleteExchange(source); - } - - public void ExchangeUnbindNoWait(string destination, - string source, - string routingKey, - IDictionary arguments) => Delegate.ExchangeUnbind(destination, source, routingKey, arguments); - - public void QueueBind(string queue, - string exchange, - string routingKey, - IDictionary arguments) - { - ThrowIfDisposed(); - RecordedBinding qb = new RecordedQueueBinding(this). - WithSource(exchange). - WithDestination(queue). - WithRoutingKey(routingKey). - WithArguments(arguments); - _connection.RecordBinding(qb); - _delegate.QueueBind(queue, exchange, routingKey, arguments); - } - - public void QueueBindNoWait(string queue, - string exchange, - string routingKey, - IDictionary arguments) => Delegate.QueueBind(queue, exchange, routingKey, arguments); - - public QueueDeclareOk QueueDeclare(string queue, bool durable, - bool exclusive, bool autoDelete, - IDictionary arguments) - { - ThrowIfDisposed(); - QueueDeclareOk result = _delegate.QueueDeclare(queue, durable, exclusive, - autoDelete, arguments); - RecordedQueue rq = new RecordedQueue(this, result.QueueName). - Durable(durable). - Exclusive(exclusive). - AutoDelete(autoDelete). - Arguments(arguments). - ServerNamed(string.Empty.Equals(queue)); - _connection.RecordQueue(result.QueueName, rq); - return result; - } - - public void QueueDeclareNoWait(string queue, bool durable, - bool exclusive, bool autoDelete, - IDictionary arguments) - { - ThrowIfDisposed(); - _delegate.QueueDeclareNoWait(queue, durable, exclusive, - autoDelete, arguments); - RecordedQueue rq = new RecordedQueue(this, queue). - Durable(durable). - Exclusive(exclusive). - AutoDelete(autoDelete). - Arguments(arguments). - ServerNamed(string.Empty.Equals(queue)); - _connection.RecordQueue(queue, rq); - } - - public QueueDeclareOk QueueDeclarePassive(string queue) => Delegate.QueueDeclarePassive(queue); - - public uint MessageCount(string queue) => Delegate.MessageCount(queue); - - public uint ConsumerCount(string queue) => Delegate.ConsumerCount(queue); - - public uint QueueDelete(string queue, - bool ifUnused, - bool ifEmpty) - { - ThrowIfDisposed(); - uint result = _delegate.QueueDelete(queue, ifUnused, ifEmpty); - _connection.DeleteRecordedQueue(queue); - return result; - } - - public void QueueDeleteNoWait(string queue, - bool ifUnused, - bool ifEmpty) - { - Delegate.QueueDeleteNoWait(queue, ifUnused, ifEmpty); - _connection.DeleteRecordedQueue(queue); - } - - public uint QueuePurge(string queue) => Delegate.QueuePurge(queue); - - public void QueueUnbind(string queue, - string exchange, - string routingKey, - IDictionary arguments) - { - ThrowIfDisposed(); - RecordedBinding qb = new RecordedQueueBinding(this). - WithSource(exchange). - WithDestination(queue). - WithRoutingKey(routingKey). - WithArguments(arguments); - _connection.DeleteRecordedBinding(qb); - _delegate.QueueUnbind(queue, exchange, routingKey, arguments); - _connection.MaybeDeleteRecordedAutoDeleteExchange(exchange); - } - - public void TxCommit() => Delegate.TxCommit(); - - public void TxRollback() => Delegate.TxRollback(); - - public void TxSelect() - { - Delegate.TxSelect(); - _usesTransactions = true; - } - - public bool WaitForConfirms(TimeSpan timeout, out bool timedOut) => Delegate.WaitForConfirms(timeout, out timedOut); - - public bool WaitForConfirms(TimeSpan timeout) => Delegate.WaitForConfirms(timeout); - - public bool WaitForConfirms() => Delegate.WaitForConfirms(); - - public void WaitForConfirmsOrDie() => Delegate.WaitForConfirmsOrDie(); - - public void WaitForConfirmsOrDie(TimeSpan timeout) => Delegate.WaitForConfirmsOrDie(timeout); - - private void RecoverBasicAckHandlers() - { - ThrowIfDisposed(); - lock (_eventLock) - { - _delegate.BasicAcks += _recordedBasicAckEventHandlers; - } - } - - private void RecoverBasicNackHandlers() - { - ThrowIfDisposed(); - lock (_eventLock) - { - _delegate.BasicNacks += _recordedBasicNackEventHandlers; - } - } - - private void RecoverBasicReturnHandlers() - { - ThrowIfDisposed(); - lock (_eventLock) - { - _delegate.BasicReturn += _recordedBasicReturnEventHandlers; - } - } - - private void RecoverCallbackExceptionHandlers() - { - ThrowIfDisposed(); - lock (_eventLock) - { - _delegate.CallbackException += _recordedCallbackExceptionEventHandlers; - } - } - - private void RecoverModelShutdownHandlers() - { - ThrowIfDisposed(); - lock (_eventLock) - { - _delegate.ModelShutdown += _recordedShutdownEventHandlers; - } - } - - private void RecoverState() - { - if (_prefetchCountConsumer != 0) - { - BasicQos(_prefetchCountConsumer, false); - } - - if (_prefetchCountGlobal != 0) - { - BasicQos(_prefetchCountGlobal, true); - } - - if (_usesPublisherConfirms) - { - ConfirmSelect(); - } - - if (_usesTransactions) - { - TxSelect(); - } - } - - private void RunRecoveryEventHandlers() - { - ThrowIfDisposed(); - foreach (EventHandler reh in Recovery?.GetInvocationList() ?? Array.Empty()) - { - try - { - reh(this, EventArgs.Empty); - } - catch (Exception e) - { - var args = new CallbackExceptionEventArgs(e); - args.Detail["context"] = "OnModelRecovery"; - _delegate.OnCallbackException(args); - } - } - } - - public IBasicPublishBatch CreateBasicPublishBatch() => Delegate.CreateBasicPublishBatch(); - - public IBasicPublishBatch CreateBasicPublishBatch(int sizeHint) => Delegate.CreateBasicPublishBatch(sizeHint); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ThrowIfDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - } - } -} diff --git a/projects/RabbitMQ.Client/client/impl/BasicProperties.cs b/projects/RabbitMQ.Client/client/impl/BasicProperties.cs index 581c64913a..304af9d573 100644 --- a/projects/RabbitMQ.Client/client/impl/BasicProperties.cs +++ b/projects/RabbitMQ.Client/client/impl/BasicProperties.cs @@ -33,7 +33,7 @@ namespace RabbitMQ.Client.Impl { - internal abstract class BasicProperties : ContentHeaderBase, IBasicProperties + public abstract class BasicProperties : ContentHeaderBase, IBasicProperties { /// /// Application Id. diff --git a/projects/RabbitMQ.Client/client/impl/BasicPublishBatch.cs b/projects/RabbitMQ.Client/client/impl/BasicPublishBatch.cs deleted file mode 100644 index f524bc561d..0000000000 --- a/projects/RabbitMQ.Client/client/impl/BasicPublishBatch.cs +++ /dev/null @@ -1,73 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using System.Collections.Generic; - -using RabbitMQ.Client.Framing.Impl; - -namespace RabbitMQ.Client.Impl -{ - internal sealed class BasicPublishBatch : IBasicPublishBatch - { - private readonly List _commands; - private readonly ModelBase _model; - - internal BasicPublishBatch (ModelBase model) - { - _model = model; - _commands = new List(); - } - - internal BasicPublishBatch (ModelBase model, int sizeHint) - { - _model = model; - _commands = new List(sizeHint); - } - - public void Add(string exchange, string routingKey, bool mandatory, IBasicProperties basicProperties, ReadOnlyMemory body) - { - var method = new BasicPublish - { - _exchange = exchange, - _routingKey = routingKey, - _mandatory = mandatory - }; - - _commands.Add(new OutgoingCommand(method, (ContentHeaderBase)(basicProperties ?? _model._emptyBasicProperties), body)); - } - - public void Publish() - { - _model.SendCommands(_commands); - } - } -} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/AutorecoveringChannel.cs b/projects/RabbitMQ.Client/client/impl/Channel/AutorecoveringChannel.cs new file mode 100644 index 0000000000..2f5e0089b6 --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/AutorecoveringChannel.cs @@ -0,0 +1,364 @@ +// This source code is dual-licensed under the Apache License, version +// 2.0, and the Mozilla Public License, version 2.0. +// +// The APL v2.0: +// +//--------------------------------------------------------------------------- +// Copyright (c) 2007-2020 VMware, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//--------------------------------------------------------------------------- +// +// The MPL v2.0: +// +//--------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. +//--------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using RabbitMQ.Client.Framing.Impl; +using RabbitMQ.Client.Impl; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + internal sealed class AutorecoveringChannel : IChannel + { + private AutorecoveringConnection _connection; + private RecoveryAwareChannel _delegate; + private bool _disposed; + + private ushort _prefetchCountConsumer; + private ushort _prefetchCountGlobal; + private bool _usesPublisherConfirms; + private bool _usesTransactions; + + public TimeSpan ContinuationTimeout + { + get => NonDisposedDelegate.ContinuationTimeout; + set => NonDisposedDelegate.ContinuationTimeout = value; + } + + // only exist due to tests + internal IConsumerDispatcher ConsumerDispatcher => NonDisposedDelegate.ConsumerDispatcher; + + public AutorecoveringChannel(AutorecoveringConnection conn, RecoveryAwareChannel @delegate) + { + _connection = conn; + _delegate = @delegate; + } + + public event Action? PublishTagAcknowledged + { + add => NonDisposedDelegate.PublishTagAcknowledged += value; + remove => NonDisposedDelegate.PublishTagAcknowledged -= value; + } + + public event Action? NewPublishTagUsed + { + add => NonDisposedDelegate.NewPublishTagUsed += value; + remove => NonDisposedDelegate.NewPublishTagUsed -= value; + } + + public event MessageDeliveryFailedDelegate? MessageDeliveryFailed + { + add => NonDisposedDelegate.MessageDeliveryFailed += value; + remove => NonDisposedDelegate.MessageDeliveryFailed -= value; + } + + public event Action?>? UnhandledExceptionOccurred + { + add => NonDisposedDelegate.UnhandledExceptionOccurred += value; + remove => NonDisposedDelegate.UnhandledExceptionOccurred -= value; + } + + public event Action? FlowControlChanged + { + add { NonDisposedDelegate.FlowControlChanged += value; } + remove { NonDisposedDelegate.FlowControlChanged -= value; } + } + + public event Action? Shutdown + { + add => NonDisposedDelegate.Shutdown += value; + remove => NonDisposedDelegate.Shutdown -= value; + } + + public event EventHandler? Recovery; + + public int ChannelNumber => NonDisposedDelegate.ChannelNumber; + public bool IsOpen => NonDisposedDelegate.IsOpen; + public ShutdownEventArgs? CloseReason => NonDisposedDelegate.CloseReason; + + internal RecoveryAwareChannel NonDisposedDelegate => !_disposed ? _delegate : ThrowDisposed(); + + private RecoveryAwareChannel ThrowDisposed() + { + throw new ObjectDisposedException(GetType().FullName); + } + + public async ValueTask AutomaticallyRecoverAsync(AutorecoveringConnection conn) + { + ThrowIfDisposed(); + _connection = conn; + var defunctChannel = _delegate; + + _delegate = await conn.CreateNonRecoveringChannelAsync().ConfigureAwait(false); + _delegate.TakeOverChannel(defunctChannel!); + await RecoverStateAsync().ConfigureAwait(false); + RunRecoveryEventHandlers(); + } + + public ValueTask SetQosAsync(uint prefetchSize, ushort prefetchCount, bool global) + { + // TODO 8.0 - Missing Tests + ThrowIfDisposed(); + if (global) + { + _prefetchCountGlobal = prefetchCount; + } + else + { + _prefetchCountConsumer = prefetchCount; + } + + return _delegate.SetQosAsync(prefetchSize, prefetchCount, global); + } + + public ValueTask AckMessageAsync(ulong deliveryTag, bool multiple) + => NonDisposedDelegate.AckMessageAsync(deliveryTag, multiple); + + public ValueTask NackMessageAsync(ulong deliveryTag, bool multiple, bool requeue) + => NonDisposedDelegate.NackMessageAsync(deliveryTag, multiple, requeue); + + public async ValueTask ActivateConsumerAsync(IBasicConsumer consumer, string queue, bool autoAck, string consumerTag = "", bool noLocal = false, bool exclusive = false, IDictionary? arguments = null) + { + string result = await NonDisposedDelegate.ActivateConsumerAsync(consumer, queue, autoAck, consumerTag, noLocal, exclusive, arguments).ConfigureAwait(false); + RecordedConsumer rc = new RecordedConsumer(this, consumer, queue, autoAck, result, exclusive, arguments); + _connection.RecordConsumer(result, rc); + return result; + } + + public ValueTask CancelConsumerAsync(string consumerTag, bool waitForConfirmation = true) + { + ThrowIfDisposed(); + RecordedConsumer cons = _connection.DeleteRecordedConsumer(consumerTag); + if (cons != null) + { + _connection.MaybeDeleteRecordedAutoDeleteQueue(cons.Queue); + } + + return _delegate.CancelConsumerAsync(consumerTag, waitForConfirmation); + } + + public ValueTask RetrieveSingleMessageAsync(string queue, bool autoAck) + => NonDisposedDelegate.RetrieveSingleMessageAsync(queue, autoAck); + + public ValueTask PublishMessageAsync(string exchange, string routingKey, IBasicProperties? basicProperties, ReadOnlyMemory body, bool mandatory = false) + => NonDisposedDelegate.PublishMessageAsync(exchange, routingKey, basicProperties, body, mandatory); + + public ValueTask PublishBatchAsync(MessageBatch batch) + => NonDisposedDelegate.PublishBatchAsync(batch); + + public async ValueTask ActivatePublishTagsAsync() + { + await NonDisposedDelegate.ActivatePublishTagsAsync().ConfigureAwait(false); + _usesPublisherConfirms = true; + } + + public async ValueTask DeclareExchangeAsync(string exchange, string type, bool durable, bool autoDelete, bool throwOnMismatch = true, IDictionary? arguments = null, bool waitForConfirmation = true) + { + await NonDisposedDelegate.DeclareExchangeAsync(exchange, type, durable, autoDelete, throwOnMismatch, arguments, waitForConfirmation).ConfigureAwait(false); + RecordedExchange rx = new RecordedExchange(this, exchange, type, durable, autoDelete, arguments); + _connection.RecordExchange(exchange, rx); + } + + public ValueTask DeleteExchangeAsync(string exchange, bool ifUnused = false, bool waitForConfirmation = true) + { + ThrowIfDisposed(); + _connection.DeleteRecordedExchange(exchange); + return _delegate.DeleteExchangeAsync(exchange, ifUnused, waitForConfirmation); + } + + public async ValueTask BindExchangeAsync(string destination, string source, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true) + { + await NonDisposedDelegate.BindExchangeAsync(destination, source, routingKey, arguments, waitForConfirmation).ConfigureAwait(false); + RecordedBinding eb = new RecordedExchangeBinding(this, destination, source, routingKey, arguments); + _connection.RecordBinding(eb); + } + + public ValueTask UnbindExchangeAsync(string destination, string source, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true) + { + ThrowIfDisposed(); + RecordedBinding eb = new RecordedExchangeBinding(this, destination, source, routingKey, arguments); + _connection.DeleteRecordedBinding(eb); + _connection.MaybeDeleteRecordedAutoDeleteExchange(source); + return _delegate.UnbindExchangeAsync(destination, source, routingKey, arguments, waitForConfirmation); + } + + public async ValueTask<(string QueueName, uint MessageCount, uint ConsumerCount)> DeclareQueueAsync(string queue, + bool durable, bool exclusive, bool autoDelete, bool throwOnMismatch = true, IDictionary? arguments = null) + { + var result = await NonDisposedDelegate.DeclareQueueAsync(queue, durable, exclusive, autoDelete, throwOnMismatch, arguments).ConfigureAwait(false); + RecordedQueue rq = new RecordedQueue(this, result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments); + _connection.RecordQueue(result.QueueName, rq); + return result; + } + + public async ValueTask DeclareQueueWithoutConfirmationAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary? arguments = null) + { + await NonDisposedDelegate.DeclareQueueWithoutConfirmationAsync(queue, durable, exclusive, autoDelete, arguments).ConfigureAwait(false); + RecordedQueue rq = new RecordedQueue(this, queue, queue.Length == 0, durable, exclusive, autoDelete, arguments); + _connection.RecordQueue(queue, rq); + } + + public ValueTask DeleteQueueAsync(string queue, bool ifUnused = false, bool ifEmpty = false) + { + _connection.DeleteRecordedQueue(queue); + return NonDisposedDelegate.DeleteQueueAsync(queue, ifUnused, ifEmpty); + } + + public ValueTask DeleteQueueWithoutConfirmationAsync(string queue, bool ifUnused = false, bool ifEmpty = false) + { + _connection.DeleteRecordedQueue(queue); + return NonDisposedDelegate.DeleteQueueWithoutConfirmationAsync(queue, ifUnused, ifEmpty); + } + + public async ValueTask BindQueueAsync(string queue, string exchange, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true) + { + await NonDisposedDelegate.BindQueueAsync(queue, exchange, routingKey, arguments, waitForConfirmation).ConfigureAwait(false); + RecordedBinding qb = new RecordedQueueBinding(this, queue, exchange, routingKey, arguments); + _connection.RecordBinding(qb); + } + + public ValueTask UnbindQueueAsync(string queue, string exchange, string routingKey, IDictionary? arguments = null) + { + ThrowIfDisposed(); + RecordedBinding qb = new RecordedQueueBinding(this, queue, exchange, routingKey, arguments); + _connection.DeleteRecordedBinding(qb); + _connection.MaybeDeleteRecordedAutoDeleteExchange(exchange); + return _delegate.UnbindQueueAsync(queue, exchange, routingKey, arguments); + } + + public ValueTask PurgeQueueAsync(string queue) + => NonDisposedDelegate.PurgeQueueAsync(queue); + + public async ValueTask ActivateTransactionsAsync() + { + await NonDisposedDelegate.ActivateTransactionsAsync().ConfigureAwait(false); + _usesTransactions = true; + } + + public ValueTask CommitTransactionAsync() + => NonDisposedDelegate.CommitTransactionAsync(); + + public ValueTask RollbackTransactionAsync() + => NonDisposedDelegate.RollbackTransactionAsync(); + + public ValueTask ResendUnackedMessages(bool requeue) + => NonDisposedDelegate.ResendUnackedMessages(requeue); + + public ValueTask CloseAsync(ushort replyCode, string replyText) + { + _connection.UnregisterChannel(this); + return NonDisposedDelegate.CloseAsync(replyCode, replyText); + } + + public ValueTask AbortAsync(ushort replyCode, string replyText) + { + _connection.UnregisterChannel(this); + return NonDisposedDelegate.AbortAsync(replyCode, replyText); + } + + public bool WaitForConfirms(TimeSpan timeout) => NonDisposedDelegate.WaitForConfirms(timeout); + + public bool WaitForConfirms() => NonDisposedDelegate.WaitForConfirms(); + + public void WaitForConfirmsOrDie() => NonDisposedDelegate.WaitForConfirmsOrDie(); + + public void WaitForConfirmsOrDie(TimeSpan timeout) => NonDisposedDelegate.WaitForConfirmsOrDie(timeout); + + private async ValueTask RecoverStateAsync() + { + if (_prefetchCountConsumer != 0) + { + await SetQosAsync(0, _prefetchCountConsumer, false).ConfigureAwait(false); + } + + if (_prefetchCountGlobal != 0) + { + await SetQosAsync(0, _prefetchCountGlobal, true).ConfigureAwait(false); + } + + if (_usesPublisherConfirms) + { + await ActivatePublishTagsAsync().ConfigureAwait(false); + } + + if (_usesTransactions) + { + await ActivateTransactionsAsync().ConfigureAwait(false); + } + } + + private void RunRecoveryEventHandlers() + { + ThrowIfDisposed(); + foreach (EventHandler reh in Recovery?.GetInvocationList() ?? Array.Empty()) + { + try + { + reh(this, EventArgs.Empty); + } + catch (Exception e) + { + _delegate.OnUnhandledExceptionOccurred(e, "OnModelRecovery"); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ThrowIfDisposed() + { + if (_disposed) + { + ThrowDisposed(); + } + } + + public override string? ToString() => NonDisposedDelegate.ToString(); + + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + await NonDisposedDelegate.DisposeAsync().ConfigureAwait(false); + _disposed = true; + } + + void IDisposable.Dispose() + { + DisposeAsync().GetAwaiter().GetResult(); + } + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/Channel.cs b/projects/RabbitMQ.Client/client/impl/Channel/Channel.cs new file mode 100644 index 0000000000..61f31035c1 --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/Channel.cs @@ -0,0 +1,976 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client.client.framing; +using RabbitMQ.Client.Exceptions; +using RabbitMQ.Client.Framing.Impl; +using RabbitMQ.Client.Impl; +using BasicCancel = RabbitMQ.Client.Framing.Impl.BasicCancel; +using BasicCancelOk = RabbitMQ.Client.Framing.Impl.BasicCancelOk; +using BasicConsumeOk = RabbitMQ.Client.Framing.Impl.BasicConsumeOk; +using BasicDeliver = RabbitMQ.Client.Framing.Impl.BasicDeliver; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + /// + /// A channel of a . + /// + public class Channel : ChannelBase, IChannel + { + internal static readonly BasicProperties EmptyBasicProperties = new Framing.BasicProperties(); + + private readonly Dictionary _consumers = new Dictionary(); + + private ulong _nextPublishTag; + + private bool IsPublishAcksEnabled => _nextPublishTag != ulong.MaxValue; + + // only exist due to tests + internal IConsumerDispatcher ConsumerDispatcher { get; } + + internal Channel(ISession session, ConsumerWorkService workService) + : base(session) + { + _nextPublishTag = ulong.MaxValue; + if (workService is AsyncConsumerWorkService asyncConsumerWorkService) + { + ConsumerDispatcher = new AsyncConsumerDispatcher(this, asyncConsumerWorkService); + } + else + { + ConsumerDispatcher = new ConcurrentConsumerDispatcher(this, workService); + } + } + + //**************************** Various methods **************************** + internal ValueTask OpenChannelAsync() + { + ModelRpc(new ChannelOpen(string.Empty)); + return default; + } + + /// + public ValueTask SetQosAsync(uint prefetchSize, ushort prefetchCount, bool global) + { + ModelRpc(new BasicQos(prefetchSize, prefetchCount, global)); + return default; + } + + // TODO 8.0 - Missing Tests + /// + public event Action? FlowControlChanged; + + private void HandleChannelFlow(bool active) + { + if (active) + { + _flowControlBlock.Set(); + } + else + { + _flowControlBlock.Reset(); + } + ModelSend(new ChannelFlowOk(active)); + + var handler = FlowControlChanged; + if (handler != null) + { + foreach (Action action in handler.GetInvocationList()) + { + try + { + action(active); + } + catch (Exception e) + { + OnUnhandledExceptionOccurred(e, "OnFlowControl"); + } + } + } + } + + //**************************** Message retrieval methods **************************** + /// + public virtual ValueTask AckMessageAsync(ulong deliveryTag, bool multiple) + { + ModelSend(new BasicAck(deliveryTag, multiple)); + return default; + } + + /// + public virtual ValueTask NackMessageAsync(ulong deliveryTag, bool multiple, bool requeue) + { + ModelSend(new BasicNack(deliveryTag, multiple, requeue)); + return default; + } + + /// + public ValueTask ActivateConsumerAsync(IBasicConsumer consumer, string queue, bool autoAck, string consumerTag = "", bool noLocal = false, bool exclusive = false, IDictionary? arguments = null) + { + // TODO: Replace with flag + if (ConsumerDispatcher is AsyncConsumerDispatcher) + { + if (!(consumer is IAsyncBasicConsumer)) + { + // TODO: Friendly message + throw new InvalidOperationException("In the async mode you have to use an async consumer"); + } + } + + // TODO 8.0 - Call ModelRpc + var k = new ModelBase.BasicConsumerRpcContinuation { m_consumer = consumer }; + + lock (_rpcLock) + { + Enqueue(k); + // Non-nowait. We have an unconventional means of getting the RPC response, but a response is still expected. + ModelSend(new BasicConsume(default, queue, consumerTag, noLocal, autoAck, exclusive, false, arguments)); + k.GetReply(ContinuationTimeout); + } + + return new ValueTask(k.m_consumerTag); + } + + private void HandleBasicConsumeOk(string consumerTag) + { + var k = (ModelBase.BasicConsumerRpcContinuation)GetRpcContinuation(); + k.m_consumerTag = consumerTag; + lock (_consumers) + { + _consumers[consumerTag] = k.m_consumer; + } + ConsumerDispatcher.HandleBasicConsumeOk(k.m_consumer, consumerTag); + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + /// + public ValueTask CancelConsumerAsync(string consumerTag, bool waitForConfirmation = true) + { + if (waitForConfirmation) + { + // TODO 8.0 - Call ModelRpc + var k = new ModelBase.BasicConsumerRpcContinuation { m_consumerTag = consumerTag }; + + lock (_rpcLock) + { + Enqueue(k); + ModelSend(new BasicCancel(consumerTag, false)); + k.GetReply(ContinuationTimeout); + } + } + else + { + ModelSend(new BasicCancel(consumerTag, true)); + + lock (_consumers) + { + _consumers.Remove(consumerTag); + } + // TODO 8.0 - ConsumerDispatcher on no wait - Check if dispatcher shall be called + } + + return default; + } + + private void HandleBasicCancelOk(string consumerTag) + { + var k = (ModelBase.BasicConsumerRpcContinuation)GetRpcContinuation(); + lock (_consumers) + { + k.m_consumer = _consumers[consumerTag]; + _consumers.Remove(consumerTag); + } + ConsumerDispatcher.HandleBasicCancelOk(k.m_consumer, consumerTag); + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + private void HandleBasicCancel(string consumerTag) + { + IBasicConsumer consumer; + lock (_consumers) + { + consumer = _consumers[consumerTag]; + _consumers.Remove(consumerTag); + } + ConsumerDispatcher.HandleBasicCancel(consumer, consumerTag); + } + + protected virtual void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, IBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray) + { + IBasicConsumer consumer; + lock (_consumers) + { + consumer = _consumers[consumerTag]; + } + + ConsumerDispatcher.HandleBasicDeliver(consumer, consumerTag, deliveryTag, redelivered, exchange, routingKey, basicProperties, body, rentedArray); + } + + /// + public ValueTask RetrieveSingleMessageAsync(string queue, bool autoAck) + { + // TODO 8.0 - Call ModelRpc + var k = new ModelBase.BasicGetRpcContinuation(); + lock (_rpcLock) + { + Enqueue(k); + ModelSend(new BasicGet(default, queue, autoAck)); + k.GetReply(ContinuationTimeout); + } + + return new ValueTask(k.m_result); + } + + protected virtual void HandleBasicGetOk(ulong deliveryTag, bool redelivered, string exchange, string routingKey, uint messageCount, IBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray) + { + var k = (ModelBase.BasicGetRpcContinuation)GetRpcContinuation(); + k.m_result = new SingleMessageRetrieval(rentedArray, basicProperties, body, exchange, routingKey, deliveryTag, messageCount, redelivered); + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + private void HandleBasicGetEmpty() + { + var k = (ModelBase.BasicGetRpcContinuation)GetRpcContinuation(); + k.m_result = new SingleMessageRetrieval(EmptyBasicProperties); + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + //**************************** Message publication methods **************************** + // TODO 8.0 - API extension: waitForConfirmation + /// + public ValueTask ActivatePublishTagsAsync() + { + if (Interlocked.CompareExchange(ref _nextPublishTag, 0UL, ulong.MaxValue) == ulong.MaxValue) + { + ModelRpc(new ConfirmSelect(false)); + } + + return default; + } + + /// + public event Action? PublishTagAcknowledged; + + private void HandleBasicAck(ulong deliveryTag, bool multiple) + { + OnPublishAckReceived(deliveryTag, multiple, true); + } + + private void HandleBasicNack(ulong deliveryTag, bool multiple) + { + OnPublishAckReceived(deliveryTag, multiple, false); + } + + private void OnPublishAckReceived(ulong deliveryTag, bool multiple, bool isAck) + { + var handler = PublishTagAcknowledged; + if (handler != null) + { + foreach (Action action in handler.GetInvocationList()) + { + try + { + action(deliveryTag, multiple, isAck); + } + catch (Exception e) + { + OnUnhandledExceptionOccurred(e, "OnPublishAckReceived"); + } + } + } + + // TODO 8.0 - Wait for confirms - Remove when moved + if (IsPublishAcksEnabled) + { + // let's take a lock so we can assume that deliveryTags are unique, never duplicated and always sorted + lock (_confirmLock) + { + // No need to do anything if there are no delivery tags in the list + if (_pendingDeliveryTags.Count > 0) + { + if (multiple) + { + int count = 0; + while (_pendingDeliveryTags.First!.Value < deliveryTag) + { + _pendingDeliveryTags.RemoveFirst(); + count++; + } + + if (_pendingDeliveryTags.First!.Value == deliveryTag) + { + _pendingDeliveryTags.RemoveFirst(); + count++; + } + + if (count > 0) + { + _deliveryTagsCountdown.Signal(count); + } + } + else + { + if (_pendingDeliveryTags.Remove(deliveryTag)) + { + _deliveryTagsCountdown.Signal(); + } + } + } + + _onlyAcksReceived = _onlyAcksReceived && isAck; + } + } + } + + /// + public event Action? NewPublishTagUsed; + + /// + public ValueTask PublishMessageAsync(string exchange, string routingKey, IBasicProperties? basicProperties, ReadOnlyMemory body, bool mandatory = false) + { + if (IsPublishAcksEnabled) + { + AllocatePublishTagUsed(); + } + + basicProperties ??= EmptyBasicProperties; + ModelSend(new BasicPublish(default, exchange, routingKey, mandatory, default), (BasicProperties) basicProperties, body); + return default; + } + + /// + public ValueTask PublishBatchAsync(MessageBatch batch) + { + if (IsPublishAcksEnabled) + { + AllocatePublishTagUsed(batch.Commands.Count); + } + + SendCommands(batch.Commands); + + return default; + } + + private void AllocatePublishTagUsed(int count) + { + // TODO 8.0 - Wait for confirms - Remove when moved + ulong endTag; + lock (_confirmLock) + { + if (_deliveryTagsCountdown.IsSet) + { + _deliveryTagsCountdown.Reset(count); + } + else + { + _deliveryTagsCountdown.AddCount(count); + } + + for (int i = 0; i < count; i++) + { + ulong tag = Interlocked.Increment(ref _nextPublishTag); + _pendingDeliveryTags.AddLast(tag); + } + endTag = _nextPublishTag; + } + + var handler = NewPublishTagUsed; + if (handler != null) + { + foreach (Action action in handler.GetInvocationList()) + { + try + { + for (ulong tag = endTag - (uint)count; tag <= endTag; tag++) + { + action(tag); + } + } + catch (Exception e) + { + OnUnhandledExceptionOccurred(e, "AllocatePublishTagUsed"); + } + } + } + } + + private ulong AllocatePublishTagUsed() + { + ulong tag; + + // TODO 8.0 - Wait for confirms - Remove when moved + lock (_confirmLock) + { + if (_deliveryTagsCountdown.IsSet) + { + _deliveryTagsCountdown.Reset(1); + } + else + { + _deliveryTagsCountdown.AddCount(); + } + + tag = Interlocked.Increment(ref _nextPublishTag); + _pendingDeliveryTags.AddLast(tag); + } + + var handler = NewPublishTagUsed; + if (handler != null) + { + foreach (Action action in handler.GetInvocationList()) + { + try + { + action(tag); + } + catch (Exception e) + { + OnUnhandledExceptionOccurred(e, "AllocatePublishTagUsed"); + } + } + } + + return tag; + } + + /// + public event MessageDeliveryFailedDelegate? MessageDeliveryFailed; + + private void HandleBasicReturn(ushort replyCode, string replyText, string exchange, string routingKey, IBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray) + { + var eventHandler = MessageDeliveryFailed; + if (eventHandler != null) + { + var message = new Message(exchange, routingKey, basicProperties, body); + foreach (Action action in eventHandler.GetInvocationList()) + { + try + { + action(message, replyText, replyCode); + } + catch (Exception exception) + { + OnUnhandledExceptionOccurred(exception, "OnBasicReturn"); + } + } + } + ArrayPool.Shared.Return(rentedArray); + } + + //**************************** Exchange methods **************************** + /// + public ValueTask DeclareExchangeAsync(string exchange, string type, bool durable, bool autoDelete, bool throwOnMismatch = true, IDictionary? arguments = null, bool waitForConfirmation = true) + { + ExchangeDeclare method = new ExchangeDeclare(default, exchange, type, !throwOnMismatch, durable, autoDelete, false, !waitForConfirmation, arguments); + if (waitForConfirmation) + { + ModelRpc(method); + } + else + { + ModelSend(method); + } + + return default; + } + + /// + public ValueTask DeleteExchangeAsync(string exchange, bool ifUnused = false, bool waitForConfirmation = true) + { + ExchangeDelete method = new ExchangeDelete(default, exchange, ifUnused, !waitForConfirmation); + if (waitForConfirmation) + { + ModelRpc(method); + } + else + { + ModelSend(method); + } + + return default; + } + + /// + public ValueTask BindExchangeAsync(string destination, string source, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true) + { + ExchangeBind method = new ExchangeBind(default, destination, source, routingKey, !waitForConfirmation, arguments); + if (waitForConfirmation) + { + ModelRpc(method); + } + else + { + ModelSend(method); + } + + return default; + } + + /// + public ValueTask UnbindExchangeAsync(string destination, string source, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true) + { + ExchangeUnbind method = new ExchangeUnbind(default, destination, source, routingKey, !waitForConfirmation, arguments); + if (waitForConfirmation) + { + ModelRpc(method); + } + else + { + ModelSend(method); + } + + return default; + } + + //**************************** Queue methods **************************** + /// + public ValueTask<(string QueueName, uint MessageCount, uint ConsumerCount)> DeclareQueueAsync(string queue, + bool durable, bool exclusive, bool autoDelete, bool throwOnMismatch = true, IDictionary? arguments = null) + { + // TODO 8.0 - Call ModelRpc + var k = new ModelBase.QueueDeclareRpcContinuation(); + lock (_rpcLock) + { + Enqueue(k); + ModelSend(new QueueDeclare(default, queue, !throwOnMismatch, durable, exclusive, autoDelete, false, arguments)); + k.GetReply(ContinuationTimeout); + } + return new ValueTask<(string, uint, uint)>((k.m_result.QueueName, k.m_result.MessageCount, k.m_result.ConsumerCount)); + } + + /// + public ValueTask DeclareQueueWithoutConfirmationAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary? arguments = null) + { + ModelSend(new QueueDeclare(default, queue, false, durable, exclusive, autoDelete, true, arguments)); + return default; + } + + /// + public ValueTask DeleteQueueAsync(string queue, bool ifUnused = false, bool ifEmpty = false) + { + var result = ModelRpc(new QueueDelete(default, queue, ifUnused, ifEmpty, false)); + return new ValueTask(result._messageCount); + } + + private void HandleQueueDeclareOk(string queue, uint messageCount, uint consumerCount) + { + var k = (ModelBase.QueueDeclareRpcContinuation)GetRpcContinuation(); + k.m_result = new QueueDeclareOk(queue, messageCount, consumerCount); + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + /// + public ValueTask DeleteQueueWithoutConfirmationAsync(string queue, bool ifUnused = false, bool ifEmpty = false) + { + QueueDelete method = new QueueDelete(default, queue, ifUnused, ifEmpty, true); + ModelSend(method); + return default; + } + + /// + public ValueTask BindQueueAsync(string queue, string exchange, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true) + { + QueueBind method = new QueueBind(default, queue, exchange, routingKey, !waitForConfirmation, arguments); + if (waitForConfirmation) + { + ModelRpc(method); + } + else + { + ModelSend(method); + } + + return default; + } + + /// + public ValueTask UnbindQueueAsync(string queue, string exchange, string routingKey, IDictionary? arguments = null) + { + ModelRpc(new QueueUnbind(default, queue, exchange, routingKey, arguments)); + return default; + } + + // TODO 8.0 - API extension: waitForConfirmation + /// + public ValueTask PurgeQueueAsync(string queue) + { + QueuePurgeOk result = ModelRpc(new QueuePurge(default, queue, false)); + return new ValueTask(result._messageCount); + } + + //**************************** Transaction methods **************************** + /// + public ValueTask ActivateTransactionsAsync() + { + ModelRpc(new TxSelect()); + return default; + } + + /// + public ValueTask CommitTransactionAsync() + { + ModelRpc(new TxCommit()); + return default; + } + + /// + public ValueTask RollbackTransactionAsync() + { + ModelRpc(new TxRollback()); + return default; + } + + //**************************** Recover methods **************************** + /// + public ValueTask ResendUnackedMessages(bool requeue) + { + // TODO 8.0 - Call ModelRpc + var k = new SimpleBlockingRpcContinuation(); + + lock (_rpcLock) + { + Enqueue(k); + ModelSend(new BasicRecover(requeue)); + k.GetReply(ContinuationTimeout); + } + + return default; + } + + private void HandleBasicRecoverOk() + { + var k = (SimpleBlockingRpcContinuation)GetRpcContinuation(); + k.HandleCommand(IncomingCommand.Empty); + } + + //**************************** Close methods **************************** + /// + public ValueTask AbortAsync(ushort replyCode, string replyText) + { + return CloseAsync(replyCode, replyText, true); + } + + /// + public ValueTask CloseAsync(ushort replyCode, string replyText) + { + return CloseAsync(replyCode, replyText, false); + } + + private ValueTask CloseAsync(ushort replyCode, string replyText, bool abort) + { + var k = new ShutdownContinuation(); + Shutdown += k.OnConnectionShutdown; + + try + { + ConsumerDispatcher.Quiesce(); + if (SetCloseReason(new ShutdownEventArgs(ShutdownInitiator.Application, replyCode, replyText))) + { + ModelSend(new ChannelClose(replyCode, replyText, 0, 0)); + } + k.Wait(TimeSpan.FromMilliseconds(10000)); + } + catch (Exception) + { + if (!abort) + { + throw; + } + } + + return new ValueTask(ConsumerDispatcher.Shutdown()); + } + + private void HandleChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId) + { + SetCloseReason(new ShutdownEventArgs(ShutdownInitiator.Peer, replyCode, replyText, classId, methodId)); + Session.Close(CloseReason, false); + try + { + ModelSend(new ChannelCloseOk()); + } + finally + { + Session.Notify(); + } + } + + private Action? _shutdownHandler; + /// + public event Action? Shutdown + { + add + { + if (CloseReason is null) + { + Action? handler2; + Action? tmpHandler = _shutdownHandler; + do + { + handler2 = tmpHandler; + var combinedHandler = (Action?)Delegate.Combine(handler2, value); + tmpHandler = System.Threading.Interlocked.CompareExchange(ref _shutdownHandler, combinedHandler, handler2); + } + while (tmpHandler != handler2); + } + else + { + // Call immediately if it already closed + value?.Invoke(CloseReason); + } + } + remove + { + Action? handler2; + Action? tmpHandler = _shutdownHandler; + do + { + handler2 = tmpHandler; + var combinedHandler = (Action?)Delegate.Remove(handler2, value); + tmpHandler = System.Threading.Interlocked.CompareExchange(ref _shutdownHandler, combinedHandler, handler2); + } + while (tmpHandler != handler2); + } + } + + protected override void HandleSessionShutdown(object? sender, ShutdownEventArgs reason) + { + ConsumerDispatcher.Quiesce(); + + base.HandleSessionShutdown(sender, reason); + + var handler = System.Threading.Interlocked.Exchange(ref _shutdownHandler, null); + if (handler != null) + { + foreach (Action h in handler.GetInvocationList()) + { + try + { + h(reason); + } + catch (Exception e) + { + OnUnhandledExceptionOccurred(e, "OnModelShutdown"); + } + } + } + + foreach (KeyValuePair c in _consumers) + { + ConsumerDispatcher.HandleModelShutdown(c.Value, reason); + } + + // TODO 8.0 - Wait for confirms - Remove when moved + _deliveryTagsCountdown.Reset(0); + + ConsumerDispatcher.Shutdown().GetAwaiter().GetResult(); + } + + private void HandleChannelCloseOk() + { + FinishClose(); + } + + internal void FinishClose() + { + if (CloseReason != null) + { + Session.Close(CloseReason); + } + } + + protected void TakeOverChannel(Channel channel) + { + base.TakeOverChannel(channel); + + PublishTagAcknowledged = channel.PublishTagAcknowledged; + NewPublishTagUsed = channel.NewPublishTagUsed; + MessageDeliveryFailed = channel.MessageDeliveryFailed; + _shutdownHandler = channel._shutdownHandler; + } + + //**************************** HandleCommands **************************** + private protected override bool DispatchAsynchronous(in IncomingCommand cmd) + { + switch (cmd.Method.ProtocolCommandId) + { + case ProtocolCommandId.BasicDeliver: + { + var __impl = (BasicDeliver)cmd.Method; + HandleBasicDeliver(__impl._consumerTag, __impl._deliveryTag, __impl._redelivered, __impl._exchange, + __impl._routingKey, (IBasicProperties)cmd.Header, cmd.Body, cmd.TakeoverPayload()); + return true; + } + case ProtocolCommandId.BasicAck: + { + var __impl = (BasicAck)cmd.Method; + HandleBasicAck(__impl._deliveryTag, __impl._multiple); + return true; + } + case ProtocolCommandId.BasicCancel: + { + var __impl = (BasicCancel)cmd.Method; + HandleBasicCancel(__impl._consumerTag); + return true; + } + case ProtocolCommandId.BasicCancelOk: + { + var __impl = (BasicCancelOk)cmd.Method; + HandleBasicCancelOk(__impl._consumerTag); + return true; + } + case ProtocolCommandId.BasicConsumeOk: + { + var __impl = (BasicConsumeOk)cmd.Method; + HandleBasicConsumeOk(__impl._consumerTag); + return true; + } + case ProtocolCommandId.BasicGetEmpty: + { + HandleBasicGetEmpty(); + return true; + } + case ProtocolCommandId.BasicGetOk: + { + var __impl = (BasicGetOk)cmd.Method; + HandleBasicGetOk(__impl._deliveryTag, __impl._redelivered, __impl._exchange, __impl._routingKey, + __impl._messageCount, (IBasicProperties)cmd.Header, cmd.Body, cmd.TakeoverPayload()); + return true; + } + case ProtocolCommandId.BasicNack: + { + var __impl = (BasicNack)cmd.Method; + HandleBasicNack(__impl._deliveryTag, __impl._multiple); + return true; + } + case ProtocolCommandId.BasicRecoverOk: + { + HandleBasicRecoverOk(); + return true; + } + case ProtocolCommandId.BasicReturn: + { + var __impl = (BasicReturn)cmd.Method; + HandleBasicReturn(__impl._replyCode, __impl._replyText, __impl._exchange, __impl._routingKey, + (IBasicProperties)cmd.Header, cmd.Body, cmd.TakeoverPayload()); + return true; + } + case ProtocolCommandId.ChannelClose: + { + var __impl = (ChannelClose)cmd.Method; + HandleChannelClose(__impl._replyCode, __impl._replyText, __impl._classId, __impl._methodId); + return true; + } + case ProtocolCommandId.ChannelCloseOk: + { + HandleChannelCloseOk(); + return true; + } + case ProtocolCommandId.ChannelFlow: + { + var __impl = (ChannelFlow)cmd.Method; + HandleChannelFlow(__impl._active); + return true; + } + case ProtocolCommandId.QueueDeclareOk: + { + var __impl = (Framing.Impl.QueueDeclareOk)cmd.Method; + HandleQueueDeclareOk(__impl._queue, __impl._messageCount, __impl._consumerCount); + return true; + } + default: return false; + } + } + + protected override async ValueTask DisposeAsyncCore() + { + await this.AbortAsync().ConfigureAwait(false); + await base.DisposeAsyncCore().ConfigureAwait(false); + } + + //**************************** Wait for confirms ************************** + // TODO 8.0 - Wait for confirms - Extract to it's own class + private readonly LinkedList _pendingDeliveryTags = new LinkedList(); + private readonly CountdownEvent _deliveryTagsCountdown = new CountdownEvent(0); + private readonly object _confirmLock = new object(); + private bool _onlyAcksReceived = true; + + /// + public bool WaitForConfirms() + { + return WaitForConfirms(TimeSpan.FromMilliseconds(Timeout.Infinite), out _); + } + + /// + public bool WaitForConfirms(TimeSpan timeout) + { + return WaitForConfirms(timeout, out _); + } + + /// + public void WaitForConfirmsOrDie() + { + WaitForConfirmsOrDie(TimeSpan.FromMilliseconds(Timeout.Infinite)); + } + + /// + public void WaitForConfirmsOrDie(TimeSpan timeout) + { + bool onlyAcksReceived = WaitForConfirms(timeout, out bool timedOut); + if (!onlyAcksReceived) + { + CloseAsync(Constants.ReplySuccess, "Nacks Received", false).GetAwaiter().GetResult(); + throw new IOException("Nacks Received"); + } + if (timedOut) + { + CloseAsync(Constants.ReplySuccess, "Timed out waiting for acks", false).GetAwaiter().GetResult(); + throw new IOException("Timed out waiting for acks"); + } + } + + private bool WaitForConfirms(TimeSpan timeout, out bool timedOut) + { + if (!IsPublishAcksEnabled) + { + throw new InvalidOperationException("Confirms not selected"); + } + bool isWaitInfinite = timeout == Timeout.InfiniteTimeSpan; + Stopwatch stopwatch = Stopwatch.StartNew(); + while (true) + { + if (!IsOpen) + { + throw new AlreadyClosedException(CloseReason); + } + + if (_deliveryTagsCountdown.IsSet) + { + bool aux = _onlyAcksReceived; + _onlyAcksReceived = true; + timedOut = false; + return aux; + } + + if (isWaitInfinite) + { + _deliveryTagsCountdown.Wait(); + } + else + { + TimeSpan elapsed = stopwatch.Elapsed; + if (elapsed > timeout || !_deliveryTagsCountdown.Wait(timeout - elapsed)) + { + timedOut = true; + return _onlyAcksReceived; + } + } + } + } + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/ChannelBase.cs b/projects/RabbitMQ.Client/client/impl/Channel/ChannelBase.cs new file mode 100644 index 0000000000..dc353a0319 --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/ChannelBase.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client.Exceptions; +using RabbitMQ.Client.Impl; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + public abstract class ChannelBase : IDisposable, IAsyncDisposable + { + // TODO 8.0 - Call ModelRpc - this should be private + private protected readonly object _rpcLock = new object(); + private protected readonly ManualResetEventSlim _flowControlBlock = new ManualResetEventSlim(true); + private readonly RpcContinuationQueue _continuationQueue = new RpcContinuationQueue(); + + private ShutdownEventArgs? _closeReason; + + private protected ISession Session { get; } + + /// /> + public int ChannelNumber => Session.ChannelNumber; + + /// /> + public TimeSpan ContinuationTimeout { get; set; } + + /// /> + public bool IsOpen => CloseReason is null; + + /// /> + public ShutdownEventArgs? CloseReason => _closeReason; + + private protected ChannelBase(ISession session) + { + Session = session; + session.CommandReceived = HandleCommand; + session.SessionShutdown += HandleSessionShutdown; + } + + /// /> + public event Action?>? UnhandledExceptionOccurred; + + internal void OnUnhandledExceptionOccurred(Exception exception, string context, object consumer) + { + var handler = UnhandledExceptionOccurred; + if (handler != null) + { + var details = new Dictionary + { + {"consumer", consumer}, + {"context", context} + }; + foreach (Action?> action in handler.GetInvocationList()) + { + try + { + action(exception, details); + } + catch + { + // Exception in Callback-exception-handler. That was the app's last chance. Swallow the exception. + // FIXME: proper logging + } + } + } + } + + internal void OnUnhandledExceptionOccurred(Exception exception, string context) + { + var handler = UnhandledExceptionOccurred; + if (handler != null) + { + var details = new Dictionary + { + {"context", context} + }; + foreach (Action?> action in handler.GetInvocationList()) + { + try + { + action(exception, details); + } + catch + { + // Exception in Callback-exception-handler. That was the app's last chance. Swallow the exception. + // FIXME: proper logging + } + } + } + } + + protected virtual void HandleSessionShutdown(object? sender, ShutdownEventArgs reason) + { + SetCloseReason(reason); + GetRpcContinuation().HandleModelShutdown(reason); + _flowControlBlock.Set(); + } + + protected bool SetCloseReason(ShutdownEventArgs reason) + { + return System.Threading.Interlocked.CompareExchange(ref _closeReason, reason, null) is null; + } + + private protected T ModelRpc(MethodBase method) where T : MethodBase + { + var k = new SimpleBlockingRpcContinuation(); + var outgoingCommand = new OutgoingCommand(method); + MethodBase baseResult; + lock (_rpcLock) + { + TransmitAndEnqueue(outgoingCommand, k); + baseResult = k.GetReply(ContinuationTimeout).Method; + } + + if (baseResult is T result) + { + return result; + } + + throw new UnexpectedMethodException(baseResult.ProtocolClassId, baseResult.ProtocolMethodId, baseResult.ProtocolMethodName); + } + + private protected void ModelSend(MethodBase method) + { + ModelSend(method, null, ReadOnlyMemory.Empty); + } + + private protected void ModelSend(MethodBase method, ContentHeaderBase? header, ReadOnlyMemory body) + { + if (method.HasContent) + { + _flowControlBlock.Wait(); + Session.Transmit(new OutgoingCommand(method, header, body)); + } + else + { + Session.Transmit(new OutgoingCommand(method, header, body)); + } + } + + private protected void SendCommands(List commands) + { + _flowControlBlock.Wait(); + Session.Transmit(commands); + } + + private void TransmitAndEnqueue(in OutgoingCommand cmd, IRpcContinuation k) + { + Enqueue(k); + Session.Transmit(cmd); + } + + // TODO 8.0 - Call ModelRpc - this should be private + private protected void Enqueue(IRpcContinuation k) + { + if (CloseReason is null) + { + _continuationQueue.Enqueue(k); + } + else + { + k.HandleModelShutdown(CloseReason); + } + } + + private protected IRpcContinuation GetRpcContinuation() + { + return _continuationQueue.Next(); + } + + private void HandleCommand(in IncomingCommand cmd) + { + if (!DispatchAsynchronous(in cmd)) // Was asynchronous. Already processed. No need to process further. + { + _continuationQueue.Next().HandleCommand(in cmd); + } + } + + private protected abstract bool DispatchAsynchronous(in IncomingCommand cmd); + + protected void TakeOverChannel(ChannelBase channel) + { + UnhandledExceptionOccurred = channel.UnhandledExceptionOccurred; + } + + /// + public override string? ToString() + { + return Session.ToString(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore().ConfigureAwait(false); + + Dispose(false); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _flowControlBlock.Dispose(); + } + } + + protected virtual ValueTask DisposeAsyncCore() + { + _flowControlBlock.Dispose(); + return default; + } + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/ChannelEventDefinitions.cs b/projects/RabbitMQ.Client/client/impl/Channel/ChannelEventDefinitions.cs new file mode 100644 index 0000000000..8cfe57b65b --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/ChannelEventDefinitions.cs @@ -0,0 +1,11 @@ +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + /// + /// The delegate for a failed delivery. + /// + /// The message failed to deliver. + /// The close code (See under "Reply Codes" in the AMQP specification). + /// A message indicating the reason for closing the model. + public delegate void MessageDeliveryFailedDelegate(in Message message, string replyText, ushort replyCode); +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/ChannelExtensions.cs b/projects/RabbitMQ.Client/client/impl/Channel/ChannelExtensions.cs new file mode 100644 index 0000000000..c942774451 --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/ChannelExtensions.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + /// + /// Extension methods for the . + /// + public static class ChannelExtensions + { + /// + /// Declares an exchange. + /// + /// The channel to perform this operation on. + /// The name of the exchange. + /// The type of exchange (). + /// Whether or not to wait for server confirmation. + /// AMQP: Exchange.Declare + public static ValueTask DeclareExchangeAsync(this IChannel channel, string exchange, string type, bool waitForConfirmation = true) + { + return channel.DeclareExchangeAsync(exchange, type, false, false, waitForConfirmation: waitForConfirmation); + } + + /// + /// Declares an exchange passively. (not throwing on mismatch) + /// + /// The channel to perform this operation on. + /// The name of the exchange. + /// Whether or not to wait for server confirmation. + /// AMQP: Exchange.Declare + public static ValueTask DeclareExchangePassiveAsync(this IChannel channel, string exchange, bool waitForConfirmation = true) + { + return channel.DeclareExchangeAsync(exchange, "", false, false, false, waitForConfirmation: waitForConfirmation); + } + + /// + /// Declares an queue. + /// + /// The channel to perform this operation on. + /// The name of the queue or empty if the server shall generate a name. + /// Durable queues remain active when a server restarts. + /// Exclusive queues may only be accessed by the current connection, and are deleted when that connection closes. + /// Whether or not the queue is deleted when all consumers have finished using it. + /// Whether or not to throw an exception on mismatch. + /// A set of arguments for the declare. The syntax and semantics of these arguments depends on the server implementation. + /// The name of the queue, the current message count and the current consumer count. + /// AMQP: Queue.Declare + public static ValueTask<(string QueueName, uint MessageCount, uint ConsumerCount)> DeclareQueueAsync(this IChannel channel, + string queue = "", bool durable = false, bool exclusive = true, bool autoDelete = true, bool throwOnMismatch = true, IDictionary? arguments = null) + { + return channel.DeclareQueueAsync(queue, durable, exclusive, autoDelete, throwOnMismatch, arguments); + } + + /// + /// Declares an queue passively. (not throwing on mismatch) + /// + /// The channel to perform this operation on. + /// The name of the queue or empty if the server shall generate a name. + /// A set of arguments for the declare. The syntax and semantics of these arguments depends on the server implementation. + /// The name of the queue, the current message count and the current consumer count. + /// AMQP: Queue.Declare + public static ValueTask<(string QueueName, uint MessageCount, uint ConsumerCount)> DeclareQueuePassiveAsync(this IChannel channel, string queue, IDictionary? arguments = null) + { + return channel.DeclareQueueAsync(queue, false, true, true, false, arguments); + } + + /// + /// Gets the number of messages in the queue. + /// + /// The channel to perform this operation on. + /// The name of the queue. + /// The current message count. + /// AMQP: Queue.Declare + public static async ValueTask GetQueueMessageCountAsync(this IChannel channel, string queue) + { + var result = await channel.DeclareQueueAsync(queue, false, false, false, false).ConfigureAwait(false); + return result.MessageCount; + } + + /// + /// Gets the number of consumers in the queue. + /// + /// The channel to perform this operation on. + /// The name of the queue. + /// The current consumer count. + /// AMQP: Queue.Declare + public static async ValueTask GetQueueConsumerCountAsync(this IChannel channel, string queue) + { + var result = await channel.DeclareQueueAsync(queue, false, false, false, false).ConfigureAwait(false); + return result.ConsumerCount; + } + + /// + /// Closes the channel. + /// + /// The channel to perform this operation on. + /// AMQP: Connection.Close + public static ValueTask CloseAsync(this IChannel channel) + { + return channel.CloseAsync(Constants.ReplySuccess, "Goodbye"); + } + + /// + /// Aborts the channel. + /// In comparison to normal method, will not throw + /// or or any other during closing model. + /// + /// The channel to perform this operation on. + /// AMQP: Connection.Close + public static ValueTask AbortAsync(this IChannel channel) + { + return channel.AbortAsync(Constants.ReplySuccess, "Goodbye"); + } + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/ConnectionChannel.cs b/projects/RabbitMQ.Client/client/impl/Channel/ConnectionChannel.cs new file mode 100644 index 0000000000..032a9edaac --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/ConnectionChannel.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using RabbitMQ.Client.client.framing; +using RabbitMQ.Client.Exceptions; +using RabbitMQ.Client.Framing.Impl; +using RabbitMQ.Client.Impl; +using RabbitMQ.Util; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + internal sealed class ConnectionChannel : ChannelBase + { + public BlockingCell? ConnectionStartCell { get; set; } + + public ConnectionChannel(MainSession session) + : base(session) + { + } + + internal ValueTask ConnectionOpenAsync(string virtualHost, string capabilities, bool insist) + { + var k = new ModelBase.ConnectionOpenContinuation(); + lock (_rpcLock) + { + Enqueue(k); + try + { + ModelSend(new ConnectionOpen(virtualHost, capabilities, insist)); + } + catch (AlreadyClosedException) + { + // let continuation throw OperationInterruptedException, + // which is a much more suitable exception before connection + // negotiation finishes + } + k.GetReply(ContinuationTimeout); + } + + return new ValueTask(k.m_knownHosts); + } + + private void HandleConnectionOpenOk(string knownHosts) + { + var k = (ModelBase.ConnectionOpenContinuation)GetRpcContinuation(); + k.m_redirect = false; + k.m_host = null; + k.m_knownHosts = knownHosts; + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + private void HandleConnectionStart(byte versionMajor, byte versionMinor, IDictionary serverProperties, byte[] mechanisms, byte[] locales) + { + var cell = ConnectionStartCell; + if (cell is null) + { + var reason = new ShutdownEventArgs(ShutdownInitiator.Library, Constants.CommandInvalid, "Unexpected Connection.Start"); + Session.Connection.Close(reason); + return; + } + + cell.ContinueWithValue(new ConnectionStartDetails + { + m_versionMajor = versionMajor, + m_versionMinor = versionMinor, + m_serverProperties = serverProperties, + m_mechanisms = mechanisms, + m_locales = locales + }); + ConnectionStartCell = null; + } + + private void HandleConnectionClose(ushort replyCode, string replyText, ushort classId, ushort methodId) + { + var reason = new ShutdownEventArgs(ShutdownInitiator.Peer, replyCode, replyText, classId, methodId); + try + { + Session.Connection.InternalClose(reason); + ModelSend(new ConnectionCloseOk()); + SetCloseReason(Session.Connection.CloseReason); + } + catch (IOException) + { + // Ignored. We're only trying to be polite by sending + // the close-ok, after all. + } + catch (AlreadyClosedException) + { + // Ignored. We're only trying to be polite by sending + // the close-ok, after all. + } + } + + internal ValueTask ConnectionSecureOkAsync(byte[] response) + { + var k = new ModelBase.ConnectionStartRpcContinuation(); + lock (_rpcLock) + { + Enqueue(k); + try + { + ModelSend(new ConnectionSecureOk(response)); + } + catch (AlreadyClosedException) + { + // let continuation throw OperationInterruptedException, + // which is a much more suitable exception before connection + // negotiation finishes + } + k.GetReply(ContinuationTimeout); + } + return new ValueTask(k.m_result); + } + + private void HandleConnectionSecure(byte[] challenge) + { + var k = (ModelBase.ConnectionStartRpcContinuation)GetRpcContinuation(); + k.m_result = new ConnectionSecureOrTune + { + m_challenge = challenge + }; + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + internal ValueTask ConnectionStartOkAsync(IDictionary clientProperties, string mechanism, byte[] response, string locale) + { + var k = new ModelBase.ConnectionStartRpcContinuation(); + lock (_rpcLock) + { + Enqueue(k); + try + { + ModelSend(new ConnectionStartOk(clientProperties, mechanism, response, locale)); + } + catch (AlreadyClosedException) + { + // let continuation throw OperationInterruptedException, + // which is a much more suitable exception before connection + // negotiation finishes + } + k.GetReply(ContinuationTimeout); + } + return new ValueTask(k.m_result); + } + + internal ValueTask ConnectionTuneOkAsync(ushort channelMax, uint frameMax, ushort heartbeat) + { + ModelSend(new ConnectionTuneOk(channelMax, frameMax, heartbeat)); + return default; + } + + private void HandleConnectionTune(ushort channelMax, uint frameMax, ushort heartbeatInSeconds) + { + var k = (ModelBase.ConnectionStartRpcContinuation)GetRpcContinuation(); + k.m_result = new ConnectionSecureOrTune + { + m_tuneDetails = + { + m_channelMax = channelMax, + m_frameMax = frameMax, + m_heartbeatInSeconds = heartbeatInSeconds + } + }; + k.HandleCommand(IncomingCommand.Empty); // release the continuation. + } + + internal ValueTask UpdateSecretAsync(string newSecret, string reason) + { + if (newSecret is null) + { + throw new ArgumentNullException(nameof(newSecret)); + } + + if (reason is null) + { + throw new ArgumentNullException(nameof(reason)); + } + + ModelRpc(new ConnectionUpdateSecret(Encoding.UTF8.GetBytes(newSecret), reason)); + return default; + } + + private void HandleConnectionBlocked(string reason) + { + Session.Connection.HandleConnectionBlocked(reason); + } + + private void HandleConnectionUnblocked() + { + Session.Connection.HandleConnectionUnblocked(); + } + + internal void FinishClose(ShutdownEventArgs shutdownEventArgs) + { + SetCloseReason(shutdownEventArgs); + + if (CloseReason != null) + { + Session.Close(CloseReason); + } + + ConnectionStartCell?.ContinueWithValue(null!); + } + + private protected override bool DispatchAsynchronous(in IncomingCommand cmd) + { + switch (cmd.Method.ProtocolCommandId) + { + case ProtocolCommandId.ConnectionStart: + { + var __impl = (ConnectionStart)cmd.Method; + HandleConnectionStart(__impl._versionMajor, __impl._versionMinor, __impl._serverProperties, __impl._mechanisms, __impl._locales); + return true; + } + case ProtocolCommandId.ConnectionOpenOk: + { + var __impl = (ConnectionOpenOk)cmd.Method; + HandleConnectionOpenOk(__impl._reserved1); + return true; + } + case ProtocolCommandId.ConnectionClose: + { + var __impl = (ConnectionClose)cmd.Method; + HandleConnectionClose(__impl._replyCode, __impl._replyText, __impl._classId, __impl._methodId); + return true; + } + case ProtocolCommandId.ConnectionTune: + { + var __impl = (ConnectionTune)cmd.Method; + HandleConnectionTune(__impl._channelMax, __impl._frameMax, __impl._heartbeat); + return true; + } + case ProtocolCommandId.ConnectionSecure: + { + var __impl = (ConnectionSecure)cmd.Method; + HandleConnectionSecure(__impl._challenge); + return true; + } + case ProtocolCommandId.ConnectionBlocked: + { + var __impl = (ConnectionBlocked)cmd.Method; + HandleConnectionBlocked(__impl._reason); + return true; + } + case ProtocolCommandId.ConnectionUnblocked: + { + HandleConnectionUnblocked(); + return true; + } + default: return false; + } + } + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/IChannel.cs b/projects/RabbitMQ.Client/client/impl/Channel/IChannel.cs new file mode 100644 index 0000000000..9e4aa2f56c --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/IChannel.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + public interface IChannel : IDisposable, IAsyncDisposable + { + /// + /// Gets the channel number, unique per connections. + /// + int ChannelNumber { get; } + + /// + /// Gets the timeout for protocol operations (e.g. queue.declare). + /// + TimeSpan ContinuationTimeout { get; set; } + + /// + /// Gets a value that indicates whether the channel is still in a state where it can be used. + /// Identical to checking if equals null. + /// + bool IsOpen { get; } + + /// + /// Gets the reason why this channel was closed or null if it is still open. + /// + ShutdownEventArgs? CloseReason { get; } + + /// + /// Occurs when an exception was caught during a callback invoked by the channel. + /// Example are exceptions thrown during methods. + /// Exception - ex - the unhandled exception. + /// Dictionary{string, object}? - context - the context of the unhandled exception. + /// + event Action?>? UnhandledExceptionOccurred; + + /// + /// Occurs when the flow control has changed. + /// bool - active - whether or not the flow control is active. + /// + event Action? FlowControlChanged; + + /// + /// Configures the quality of service parameters. + /// + /// The number of messages be sent in advance so that when the client finishes processing a message, the following message is already held locally, rather than needing to be sent down the channel. + /// Specifies a prefetch window in terms of not yet acknowledged messages. The prefetch-count is ignored if the no-ack option is set. + /// Whether or not to set only for this channel or global. + /// AMQP: Basic.Qos + ValueTask SetQosAsync(uint prefetchSize, ushort prefetchCount, bool global); + + /// + /// Acknowledges one or more delivered message(s). + /// + /// The delivery tag to acknowledge. + /// Multiple means up to and including to the delivery tag, false is just the delivery tag. + /// AMQP: Basic.Ack + ValueTask AckMessageAsync(ulong deliveryTag, bool multiple); + + /// + /// Negative acknowledges one or more delivered message(s). + /// + /// The delivery tag to negative acknowledge. + /// Multiple means up to and including to the delivery tag, false is just the delivery tag. + /// If requeue, the server will attempt to requeue the message, otherwise messages are discarded or dead-lettered. + /// AMQP: Basic.Nack + ValueTask NackMessageAsync(ulong deliveryTag, bool multiple, bool requeue); + + /// + /// Activates a consumer to receive messages. + /// + /// The consumer to activate. + /// The name of the queue to consume from. + /// Whether or not messages need to be acknowledged by or . + /// Specifies the identifier for the consumer. If this field is empty the server will generate a unique tag. + /// If the no-local field is set the server will not send messages to the connection that published them. + /// Request exclusive consumer access, meaning only this consumer can access the queue. + /// A set of arguments for the consume. The syntax and semantics of these arguments depends on the server implementation. + /// AMQP: Basic.Consume + ValueTask ActivateConsumerAsync(IBasicConsumer consumer, string queue, bool autoAck, string consumerTag = "", bool noLocal = false, bool exclusive = false, IDictionary? arguments = null); + + /// + /// Cancels an activated consumer. + /// + /// The consumer tag returned by . + /// Whether or not to wait for server confirmation. + /// AMQP: Basic.Cancel + ValueTask CancelConsumerAsync(string consumerTag, bool waitForConfirmation = true); + + /// + /// Retrieves a single message from the queue. + /// + /// The name of the queue to consume from. + /// Whether or not the message is automatically acknowledged. + /// AMQP: Basic.Get + ValueTask RetrieveSingleMessageAsync(string queue, bool autoAck); + + /// + /// Activates publish tags. + /// + /// AMQP: Confirm.Select + ValueTask ActivatePublishTagsAsync(); + + /// + /// Occurs when a publish tag was acknowledged. + /// ulong - deliveryTag - the publish tag. + /// bool - multiple - whether or not the publish tag is up to and inclusive or just a single tag. + /// bool - isAck - whether or not it was a positive or negative acknowledged. + /// + /// AMQP: Basic.Ack / Basic.Nack + event Action? PublishTagAcknowledged; + + /// + /// Occurs when a publish tag was used for publishing a message. + /// ulong - deliveryTag - the publish tag. + /// + event Action? NewPublishTagUsed; + + /// + /// Occurs when a message delivery failed. + /// ulong - deliveryTag - the publish tag. + /// + event MessageDeliveryFailedDelegate? MessageDeliveryFailed; + + /// + /// Publishes a message to the exchange with the routing key. + /// + /// The exchange to publish it to. + /// The routing key to use. Must be shorter than 255 bytes. + /// The properties sent along with the body. + /// The body to send. + /// Whether or not to raise a if the message could not be routed to a queue. + /// AMQP: Basic.Publish + ValueTask PublishMessageAsync(string exchange, string routingKey, IBasicProperties? basicProperties, ReadOnlyMemory body, bool mandatory = false); + + /// + /// Publishes a batch of messages. + /// + /// The batch of messages to send. + /// AMQP: Basic.Publish + ValueTask PublishBatchAsync(MessageBatch batch); + + /// + /// Declares an exchange. + /// + /// The name of the exchange. + /// The type of exchange (). + /// Durable exchanges remain active when a server restarts + /// Whether or not to delete the exchange when all queues have finished using it. + /// Whether or not to throw an exception on mismatch. + /// A set of arguments for the declare. The syntax and semantics of these arguments depends on the server implementation. + /// Whether or not to wait for server confirmation. + /// AMQP: Exchange.Declare + ValueTask DeclareExchangeAsync(string exchange, string type, bool durable, bool autoDelete, bool throwOnMismatch = true, IDictionary? arguments = null, bool waitForConfirmation = true); + + /// + /// Deletes an exchange. + /// + /// The name of the exchange. + /// Whether or not the server will only delete the exchange if it has no queue bindings. + /// Whether or not to wait for server confirmation. + /// AMQP: Exchange.Delete + ValueTask DeleteExchangeAsync(string exchange, bool ifUnused = false, bool waitForConfirmation = true); + + /// + /// Binds an exchange. + /// + /// The name of the destination exchange to bind. + /// The name of the source exchange to bind. + /// The routing key to use. Must be shorter than 255 bytes. + /// A set of arguments for the bind. The syntax and semantics of these arguments depends on the server implementation. + /// Whether or not to wait for server confirmation. + /// AMQP: Exchange.Bind + ValueTask BindExchangeAsync(string destination, string source, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true); + + /// + /// Unbinds an exchange. + /// + /// The name of the destination exchange to unbind. + /// The name of the source exchange to unbind. + /// The routing key to use. Must be shorter than 255 bytes. + /// A set of arguments for the unbind. The syntax and semantics of these arguments depends on the server implementation. + /// Whether or not to wait for server confirmation. + /// AMQP: Exchange.Unbind + ValueTask UnbindExchangeAsync(string destination, string source, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true); + + /// + /// Declares an queue. + /// + /// The name of the queue or empty if the server shall generate a name. + /// Durable queues remain active when a server restarts. + /// Exclusive queues may only be accessed by the current connection, and are deleted when that connection closes. + /// Whether or not the queue is deleted when all consumers have finished using it. + /// Whether or not to throw an exception on mismatch. + /// A set of arguments for the declare. The syntax and semantics of these arguments depends on the server implementation. + /// The name of the queue, the current message count and the current consumer count. + /// AMQP: Queue.Declare + ValueTask<(string QueueName, uint MessageCount, uint ConsumerCount)> DeclareQueueAsync(string queue, bool durable, bool exclusive, bool autoDelete, bool throwOnMismatch = true, IDictionary? arguments = null); + + /// + /// Declares a queue without a confirmation. + /// + /// The name of the queue or empty if the server shall generate a name. + /// Durable queues remain active when a server restarts. + /// Exclusive queues may only be accessed by the current connection, and are deleted when that connection closes. + /// Whether or not the queue is deleted when all consumers have finished using it. + /// A set of arguments for the declare. The syntax and semantics of these arguments depends on the server implementation. + /// AMQP: Queue.Declare + ValueTask DeclareQueueWithoutConfirmationAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary? arguments = null); + + /// + /// Deletes a queue. + /// + /// The name of the queue. + /// Whether or not the server will only delete the queue if it has no consumers. + /// Whether or not the server will only delete the queue if it has no messages. + /// The number of messages deleted. + /// AMQP: Queue.Delete + ValueTask DeleteQueueAsync(string queue, bool ifUnused = false, bool ifEmpty = false); + + /// + /// Deletes a queue without confirmation. + /// + /// The name of the queue. + /// Whether or not the server will only delete the queue if it has no consumers. + /// Whether or not the server will only delete the queue if it has no messages. + /// AMQP: Queue.Delete + ValueTask DeleteQueueWithoutConfirmationAsync(string queue, bool ifUnused = false, bool ifEmpty = false); + + /// + /// Binds a queue to an exchange. + /// + /// The name of the queue. + /// The name of the exchange to bind to. + /// The routing key to use. Must be shorter than 255 bytes. + /// A set of arguments for the bind. The syntax and semantics of these arguments depends on the server implementation. + /// Whether or not to wait for server confirmation. + /// AMQP: Queue.Bind + ValueTask BindQueueAsync(string queue, string exchange, string routingKey, IDictionary? arguments = null, bool waitForConfirmation = true); + + /// + /// Unbinds a queue from an exchange. + /// + /// The name of the queue. + /// The name of the exchange to unbind from. + /// The routing key to use. Must be shorter than 255 bytes. + /// A set of arguments for the unbind. The syntax and semantics of these arguments depends on the server implementation. + /// AMQP: Queue.Unbind + ValueTask UnbindQueueAsync(string queue, string exchange, string routingKey, IDictionary? arguments = null); + + /// + /// Purges the queue of all messages. + /// + /// The name of the queue. + /// The number of messages purged. + /// AMQP: Queue.Purge + ValueTask PurgeQueueAsync(string queue); + + /// + /// Activates the transaction mode. + /// + /// AMQP: Tx.Select + ValueTask ActivateTransactionsAsync(); + + /// + /// Commits the current transaction. + /// + /// AMQP: Tx.Commit + ValueTask CommitTransactionAsync(); + + /// + /// Rollbacks the current transaction. + /// + /// AMQP: Tx.Rollback + ValueTask RollbackTransactionAsync(); + + /// + /// Resend all unacknowledged messages. + /// + /// AMQP: Basic.Recover + ValueTask ResendUnackedMessages(bool requeue); + + /// + /// Occurs when the channel is shut down. + /// If the model is already destroyed at the time an event handler is added to this event, the event handler will be fired immediately. + /// + event Action? Shutdown; + + /// + /// Aborts the channel. + /// In comparison to normal method, will not throw + /// or or any other during closing model. + /// + /// The close code (See under "Reply Codes" in the AMQP specification). + /// A message indicating the reason for closing the model. + /// AMQP: Connection.Close + ValueTask AbortAsync(ushort replyCode, string replyText); + + /// + /// Closes the channel. + /// + /// The close code (See under "Reply Codes" in the AMQP specification). + /// A message indicating the reason for closing the model. + /// AMQP: Connection.Close + ValueTask CloseAsync(ushort replyCode, string replyText); + + /// + /// Wait until all published messages have been confirmed. + /// + /// Whether or not all messages were acknowledged. + bool WaitForConfirms(); + + /// + /// Wait until all published messages have been confirmed. + /// + /// The max time to wait for. + /// Whether or not all messages were acknowledged. + bool WaitForConfirms(TimeSpan timeout); + + /// + /// Waits until all published messages have been confirmed. If not the channel will be closed. + /// + void WaitForConfirmsOrDie(); + + /// + /// Waits until all published messages have been confirmed. If not the channel will be closed. + /// + /// The max time to wait for. + void WaitForConfirmsOrDie(TimeSpan timeout); + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/Message.cs b/projects/RabbitMQ.Client/client/impl/Channel/Message.cs new file mode 100644 index 0000000000..73d0ff2e31 --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/Message.cs @@ -0,0 +1,46 @@ +using System; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + /// + /// The message and its information. + /// + public readonly struct Message + { + /// + /// The exchange the message was sent to. + /// + public readonly string Exchange; + + /// + /// The routing key used for the message. + /// + public readonly string RoutingKey; + + /// + /// The properties sent with the message. + /// + public readonly IBasicProperties Properties; + + /// + /// The body of the message. + /// + public readonly ReadOnlyMemory Body; + + /// + /// Creates a new instance of + /// + /// The exchange the message was sent to. + /// The routing key used for the message. + /// The properties sent with the message. + /// The body of the message. + public Message(string exchange, string routingKey, IBasicProperties properties, ReadOnlyMemory body) + { + Exchange = exchange; + RoutingKey = routingKey; + Properties = properties; + Body = body; + } + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/MessageBatch.cs b/projects/RabbitMQ.Client/client/impl/Channel/MessageBatch.cs new file mode 100644 index 0000000000..f723088690 --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/MessageBatch.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using RabbitMQ.Client.Framing.Impl; +using RabbitMQ.Client.Impl; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + /// + /// A batch of messages. + /// + public sealed class MessageBatch + { + private readonly List _commands; + + internal List Commands => _commands; + + /// + /// Creates a new instance of which can be sent by . + /// + public MessageBatch() + { + _commands = new List(); + } + + /// + /// Creates a new instance of which can be sent by . + /// + /// The initial capacity of the message list. + public MessageBatch(int sizeHint) + { + _commands = new List(sizeHint); + } + + /// + /// Adds a message to the batch. + /// + /// The exchange to publish it to. + /// The routing key to use. Must be shorter than 255 bytes. + /// The properties sent along with the body. + /// The body to send. + /// Whether or not to raise a if the message could not be routed to a queue. + public void Add(string exchange, string routingKey, IBasicProperties? basicProperties, ReadOnlyMemory body, bool mandatory) + { + var method = new BasicPublish + { + _exchange = exchange, + _routingKey = routingKey, + _mandatory = mandatory + }; + + _commands.Add(new OutgoingCommand(method, (ContentHeaderBase?)basicProperties ?? Channel.EmptyBasicProperties, body)); + } + } +} diff --git a/projects/RabbitMQ.Client/client/impl/Channel/RecoveryAwareChannel.cs b/projects/RabbitMQ.Client/client/impl/Channel/RecoveryAwareChannel.cs new file mode 100644 index 0000000000..344bac2e35 --- /dev/null +++ b/projects/RabbitMQ.Client/client/impl/Channel/RecoveryAwareChannel.cs @@ -0,0 +1,77 @@ +using System; +using System.Threading.Tasks; +using RabbitMQ.Client.Impl; + +namespace RabbitMQ.Client.client.impl.Channel +{ + #nullable enable + /// + /// A recovery aware channel created by the when is set. + /// + public sealed class RecoveryAwareChannel : Channel + { + private ulong _activeDeliveryTagOffset; + private ulong _maxSeenDeliveryTag; + + internal RecoveryAwareChannel(ISession session, ConsumerWorkService workService) + : base(session, workService) + { + } + + internal void TakeOverChannel(RecoveryAwareChannel channel) + { + base.TakeOverChannel(channel); + _activeDeliveryTagOffset = channel._activeDeliveryTagOffset + channel._maxSeenDeliveryTag; + _maxSeenDeliveryTag = 0; + } + + private ulong OffsetDeliveryTag(ulong deliveryTag) + { + return deliveryTag + _activeDeliveryTagOffset; + } + + protected override void HandleBasicGetOk(ulong deliveryTag, bool redelivered, string exchange, string routingKey, uint messageCount, IBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray) + { + if (deliveryTag > _maxSeenDeliveryTag) + { + _maxSeenDeliveryTag = deliveryTag; + } + + base.HandleBasicGetOk(OffsetDeliveryTag(deliveryTag), redelivered, exchange, routingKey, messageCount, basicProperties, body, rentedArray); + } + + protected override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, IBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray) + { + if (deliveryTag > _maxSeenDeliveryTag) + { + _maxSeenDeliveryTag = deliveryTag; + } + + base.HandleBasicDeliver(consumerTag, OffsetDeliveryTag(deliveryTag), redelivered, exchange, routingKey, basicProperties, body, rentedArray); + } + + /// + public override ValueTask AckMessageAsync(ulong deliveryTag, bool multiple) + { + ulong realTag = deliveryTag - _activeDeliveryTagOffset; + if (realTag > 0 && realTag <= deliveryTag) + { + return base.AckMessageAsync(realTag, multiple); + } + + return default; + } + + /// + public override ValueTask NackMessageAsync(ulong deliveryTag, bool multiple, bool requeue) + { + ulong realTag = deliveryTag - _activeDeliveryTagOffset; + if (realTag > 0 && realTag <= deliveryTag) + { + base.NackMessageAsync(realTag, multiple, requeue); + } + + return default; + } + } +} diff --git a/projects/RabbitMQ.Client/client/api/BasicGetResult.cs b/projects/RabbitMQ.Client/client/impl/Channel/SingleMessageRetrieval.cs similarity index 72% rename from projects/RabbitMQ.Client/client/api/BasicGetResult.cs rename to projects/RabbitMQ.Client/client/impl/Channel/SingleMessageRetrieval.cs index 2ab3746349..98ab0f5a92 100644 --- a/projects/RabbitMQ.Client/client/api/BasicGetResult.cs +++ b/projects/RabbitMQ.Client/client/impl/Channel/SingleMessageRetrieval.cs @@ -31,62 +31,17 @@ using System; using System.Buffers; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client { - /// Represents Basic.GetOk responses from the server. - /// - /// Basic.Get either returns an instance of this class, or null if a Basic.GetEmpty was received. - /// - public sealed class BasicGetResult : IDisposable + #nullable enable + /// + /// Represents the single message response of . + /// + public readonly struct SingleMessageRetrieval : IDisposable { - private readonly byte[] _rentedArray; - - /// - /// Sets the new instance's properties from the arguments passed in. - /// - /// Delivery tag for the message. - /// Redelivered flag for the message - /// The exchange this message was published to. - /// Routing key with which the message was published. - /// The number of messages pending on the queue, excluding the message being delivered. - /// The Basic-class content header properties for the message. - /// The body - public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey, - uint messageCount, IBasicProperties basicProperties, ReadOnlyMemory body) - { - DeliveryTag = deliveryTag; - Redelivered = redelivered; - Exchange = exchange; - RoutingKey = routingKey; - MessageCount = messageCount; - BasicProperties = basicProperties; - Body = body; - } - - /// - /// Sets the new instance's properties from the arguments passed in. - /// - /// Delivery tag for the message. - /// Redelivered flag for the message - /// The exchange this message was published to. - /// Routing key with which the message was published. - /// The number of messages pending on the queue, excluding the message being delivered. - /// The Basic-class content header properties for the message. - /// The body - /// The rented array which body is part of. - public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey, - uint messageCount, IBasicProperties basicProperties, ReadOnlyMemory body, byte[] rentedArray) - { - DeliveryTag = deliveryTag; - Redelivered = redelivered; - Exchange = exchange; - RoutingKey = routingKey; - MessageCount = messageCount; - BasicProperties = basicProperties; - Body = body; - _rentedArray = rentedArray; - } + private readonly byte[]? _rentedArray; /// /// Retrieves the Basic-class content header properties for this message. @@ -99,14 +54,19 @@ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, stri public ReadOnlyMemory Body { get; } /// - /// Retrieve the delivery tag for this message. See also . + /// Retrieve the exchange this message was published to. /// - public ulong DeliveryTag { get; } + public string Exchange { get; } /// - /// Retrieve the exchange this message was published to. + /// Retrieve the routing key with which this message was published. /// - public string Exchange { get; } + public string RoutingKey { get; } + + /// + /// Retrieve the delivery tag for this message. See also . + /// + public ulong DeliveryTag { get; } /// /// Retrieve the number of messages pending on the queue, excluding the message being delivered. @@ -123,9 +83,49 @@ public BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, stri public bool Redelivered { get; } /// - /// Retrieve the routing key with which this message was published. + /// Verifies whether this instance is Empty. /// - public string RoutingKey { get; } + public bool IsEmpty => _rentedArray is null; + + /// + /// Creates a new instance of that is filled with the provided arguments. + /// + /// The rented array which body is part of. + /// The Basic-class content header properties for the message. + /// The body + /// The exchange this message was published to. + /// Routing key with which the message was published. + /// Delivery tag for the message. + /// The number of messages pending on the queue, excluding the message being delivered. + /// Redelivered flag for the message + public SingleMessageRetrieval(byte[] rentedArray, IBasicProperties basicProperties, ReadOnlyMemory body, + string exchange, string routingKey, ulong deliveryTag, uint messageCount, bool redelivered) + { + _rentedArray = rentedArray; + BasicProperties = basicProperties; + Body = body; + Exchange = exchange; + RoutingKey = routingKey; + DeliveryTag = deliveryTag; + MessageCount = messageCount; + Redelivered = redelivered; + } + + /// + /// Creates a new instance of that is set empty (). + /// + /// The Basic-class content header properties for the message. + public SingleMessageRetrieval(IBasicProperties basicProperties) + { + _rentedArray = null; + BasicProperties = basicProperties; + Body = ReadOnlyMemory.Empty; + Exchange = string.Empty; + RoutingKey = string.Empty; + DeliveryTag = 0; + MessageCount = 0; + Redelivered = false; + } /// public void Dispose() diff --git a/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs b/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs index 2a728fe9fd..0e24ef26c7 100644 --- a/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs +++ b/projects/RabbitMQ.Client/client/impl/CommandAssembler.cs @@ -121,6 +121,12 @@ private void ParseHeaderFrame(in InboundFrame frame) throw new UnexpectedFrameException(frame.Type); } + if (totalBodyBytes == 0) + { + // If the body size is 0, there is no body frame coming, so assign an empty array + _bodyBytes = Array.Empty(); + } + _remainingBodyBytes = (int) totalBodyBytes; UpdateContentBodyState(); } diff --git a/projects/RabbitMQ.Client/client/impl/ConcurrentConsumerDispatcher.cs b/projects/RabbitMQ.Client/client/impl/ConcurrentConsumerDispatcher.cs index f986dba756..9eb0dc4e20 100644 --- a/projects/RabbitMQ.Client/client/impl/ConcurrentConsumerDispatcher.cs +++ b/projects/RabbitMQ.Client/client/impl/ConcurrentConsumerDispatcher.cs @@ -1,19 +1,18 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.Threading.Tasks; -using RabbitMQ.Client.Events; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Impl { internal sealed class ConcurrentConsumerDispatcher : IConsumerDispatcher { - private readonly ModelBase _model; + private readonly ChannelBase _channelBase; private readonly ConsumerWorkService _workService; - public ConcurrentConsumerDispatcher(ModelBase model, ConsumerWorkService ws) + public ConcurrentConsumerDispatcher(ChannelBase channelBase, ConsumerWorkService ws) { - _model = model; + _channelBase = channelBase; _workService = ws; IsShutdown = false; } @@ -23,9 +22,9 @@ public void Quiesce() IsShutdown = true; } - public Task Shutdown(IModel model) + public Task Shutdown() { - return _workService.StopWorkAsync(model); + return _workService.StopWorkAsync(_channelBase); } public bool IsShutdown @@ -45,12 +44,7 @@ public void HandleBasicConsumeOk(IBasicConsumer consumer, } catch (Exception e) { - var details = new Dictionary - { - {"consumer", consumer}, - {"context", "HandleBasicConsumeOk"} - }; - _model.OnCallbackException(CallbackExceptionEventArgs.Build(e, details)); + _channelBase.OnUnhandledExceptionOccurred(e, "HandleBasicConsumeOk", consumer); } }); } @@ -79,12 +73,7 @@ public void HandleBasicDeliver(IBasicConsumer consumer, } catch (Exception e) { - var details = new Dictionary - { - {"consumer", consumer}, - {"context", "HandleBasicDeliver"} - }; - _model.OnCallbackException(CallbackExceptionEventArgs.Build(e, details)); + _channelBase.OnUnhandledExceptionOccurred(e, "HandleBasicDeliver", consumer); } finally { @@ -103,12 +92,7 @@ public void HandleBasicCancelOk(IBasicConsumer consumer, string consumerTag) } catch (Exception e) { - var details = new Dictionary - { - {"consumer", consumer}, - {"context", "HandleBasicCancelOk"} - }; - _model.OnCallbackException(CallbackExceptionEventArgs.Build(e, details)); + _channelBase.OnUnhandledExceptionOccurred(e, "HandleBasicCancelOk", consumer); } }); } @@ -123,12 +107,7 @@ public void HandleBasicCancel(IBasicConsumer consumer, string consumerTag) } catch (Exception e) { - var details = new Dictionary - { - {"consumer", consumer}, - {"context", "HandleBasicCancel"} - }; - _model.OnCallbackException(CallbackExceptionEventArgs.Build(e, details)); + _channelBase.OnUnhandledExceptionOccurred(e, "HandleBasicCancel", consumer); } }); } @@ -138,17 +117,12 @@ public void HandleModelShutdown(IBasicConsumer consumer, ShutdownEventArgs reaso // the only case where we ignore the shutdown flag. try { - consumer.HandleModelShutdown(_model, reason); + consumer.HandleModelShutdown(_channelBase, reason); } catch (Exception e) { - var details = new Dictionary - { - {"consumer", consumer}, - {"context", "HandleModelShutdown"} - }; - _model.OnCallbackException(CallbackExceptionEventArgs.Build(e, details)); - }; + _channelBase.OnUnhandledExceptionOccurred(e, "HandleModelShutdown", consumer); + } } private void UnlessShuttingDown(Action fn) @@ -161,7 +135,7 @@ private void UnlessShuttingDown(Action fn) private void Execute(Action fn) { - _workService.AddWork(_model, fn); + _workService.AddWork(_channelBase, fn); } } } diff --git a/projects/RabbitMQ.Client/client/impl/Connection.cs b/projects/RabbitMQ.Client/client/impl/Connection.cs index 6e2dc2aa67..4fa3623dd4 100644 --- a/projects/RabbitMQ.Client/client/impl/Connection.cs +++ b/projects/RabbitMQ.Client/client/impl/Connection.cs @@ -30,7 +30,6 @@ //--------------------------------------------------------------------------- using System; -using System.Buffers; using System.Collections.Generic; using System.IO; using System.Net; @@ -40,7 +39,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; using RabbitMQ.Client.Impl; @@ -65,11 +64,11 @@ internal sealed class Connection : IConnection private readonly IConnectionFactory _factory; private readonly IFrameHandler _frameHandler; + private readonly ConnectionChannel _connectionChannel; + private readonly MainSession _session0; + private readonly Guid _id = Guid.NewGuid(); - private Guid _id = Guid.NewGuid(); - private readonly ModelBase _model0; private volatile bool _running = true; - private readonly MainSession _session0; private SessionManager _sessionManager; // @@ -94,7 +93,7 @@ internal sealed class Connection : IConnection // errors, otherwise as read timeouts public ConsumerWorkService ConsumerWorkService { get; } - public Connection(IConnectionFactory factory, bool insist, IFrameHandler frameHandler, string clientProvidedName = null) + public Connection(IConnectionFactory factory, IFrameHandler frameHandler, string clientProvidedName = null) { ClientProvidedName = clientProvidedName; KnownHosts = null; @@ -108,10 +107,10 @@ public Connection(IConnectionFactory factory, bool insist, IFrameHandler frameHa _sessionManager = new SessionManager(this, 0); _session0 = new MainSession(this) { Handler = NotifyReceivedCloseOk }; - _model0 = (ModelBase)Protocol.CreateModel(_session0); + _connectionChannel = new ConnectionChannel(_session0); StartMainLoop(); - Open(insist); + OpenAsync().GetAwaiter().GetResult(); } public Guid Id => _id; @@ -304,7 +303,7 @@ public void Close(ShutdownEventArgs reason, bool abort, TimeSpan timeout) #pragma warning restore 0168 catch (IOException ioe) { - if (_model0.CloseReason is null) + if (_connectionChannel.CloseReason is null) { if (!abort) { @@ -352,7 +351,7 @@ public void ClosingLoop() } catch (EndOfStreamException eose) { - if (_model0.CloseReason is null) + if (_connectionChannel.CloseReason is null) { LogCloseError("Connection didn't close cleanly. Socket closed unexpectedly", eose); } @@ -398,8 +397,7 @@ public void FinishClose() MaybeStopHeartbeatTimers(); _frameHandler.Close(); - _model0.SetCloseReason(_closeReason); - _model0.FinishClose(); + _connectionChannel.FinishClose(_closeReason); } /// @@ -678,28 +676,10 @@ public void OnShutdown() } } - public void Open(bool insist) + public async Task OpenAsync() { - StartAndTune(); - _model0.ConnectionOpen(_factory.VirtualHost, string.Empty, false); - } - - public void PrettyPrintShutdownReport() - { - if (ShutdownReport.Count == 0) - { - Console.Error.WriteLine( -"No errors reported when closing connection {0}", this); - } - else - { - Console.Error.WriteLine( -"Log of errors while closing connection {0}:", this); - for (int index = 0; index < ShutdownReport.Count; index++) - { - Console.Error.WriteLine(ShutdownReport[index].ToString()); - } - } + await StartAndTuneAsync().ConfigureAwait(false); + await _connectionChannel.ConnectionOpenAsync(_factory.VirtualHost, string.Empty, false).ConfigureAwait(false); } /// @@ -750,7 +730,7 @@ public void QuiesceChannel(SoftProtocolException pe) // Now we have all the information we need, and the event // flow of the *lower* layers is set up properly for // shutdown. Signal channel closure *up* the stack, toward - // the model and application. + // the channel and application. oldSession.Close(pe.ShutdownReason); // The upper layers have been signalled. Now we can tell @@ -915,7 +895,7 @@ public void Write(ReadOnlyMemory memory) public void UpdateSecret(string newSecret, string reason) { - _model0.UpdateSecret(newSecret, reason); + _connectionChannel.UpdateSecretAsync(newSecret, reason).AsTask().GetAwaiter().GetResult(); } ///API-side invocation of connection abort. @@ -966,14 +946,13 @@ public void Close(ushort reasonCode, string reasonText, TimeSpan timeout) Close(new ShutdownEventArgs(ShutdownInitiator.Application, reasonCode, reasonText), false, timeout); } - public IModel CreateModel() + public async ValueTask CreateChannelAsync() { EnsureIsOpen(); - ISession session = CreateSession(); - var model = (IFullModel)Protocol.CreateModel(session, ConsumerWorkService); - model.ContinuationTimeout = _factory.ContinuationTimeout; - model._Private_ChannelOpen(""); - return model; + var channel = Protocol.CreateChannel(CreateSession(), ConsumerWorkService); + channel.ContinuationTimeout = _factory.ContinuationTimeout; + await channel.OpenChannelAsync().ConfigureAwait(false); + return channel; } public void HandleConnectionBlocked(string reason) @@ -1027,11 +1006,11 @@ internal OutgoingCommand ChannelCloseWrapper(ushort reasonCode, string reasonTex return request; } - private void StartAndTune() + private async Task StartAndTuneAsync() { var connectionStartCell = new BlockingCell(); - _model0.m_connectionStartCell = connectionStartCell; - _model0.HandshakeContinuationTimeout = _factory.HandshakeContinuationTimeout; + _connectionChannel.ConnectionStartCell = connectionStartCell; + _connectionChannel.ContinuationTimeout = _factory.HandshakeContinuationTimeout; _frameHandler.ReadTimeout = _factory.HandshakeContinuationTimeout; _frameHandler.SendHeader(); @@ -1078,14 +1057,11 @@ private void StartAndTune() ConnectionSecureOrTune res; if (challenge is null) { - res = _model0.ConnectionStartOk(ClientProperties, - mechanismFactory.Name, - response, - "en_US"); + res = await _connectionChannel.ConnectionStartOkAsync(ClientProperties, mechanismFactory.Name, response, "en_US").ConfigureAwait(false); } else { - res = _model0.ConnectionSecureOk(response); + res = await _connectionChannel.ConnectionSecureOkAsync(response).ConfigureAwait(false); } if (res.m_challenge is null) @@ -1106,24 +1082,20 @@ private void StartAndTune() { throw new AuthenticationFailureException(e.ShutdownReason.ReplyText); } - throw new PossibleAuthenticationFailureException( - "Possibly caused by authentication failure", e); + throw new PossibleAuthenticationFailureException("Possibly caused by authentication failure", e); } - ushort channelMax = (ushort)NegotiatedMaxValue(_factory.RequestedChannelMax, - connectionTune.m_channelMax); + ushort channelMax = (ushort)NegotiatedMaxValue(_factory.RequestedChannelMax, connectionTune.m_channelMax); _sessionManager = new SessionManager(this, channelMax); - uint frameMax = NegotiatedMaxValue(_factory.RequestedFrameMax, - connectionTune.m_frameMax); + uint frameMax = NegotiatedMaxValue(_factory.RequestedFrameMax, connectionTune.m_frameMax); FrameMax = frameMax; TimeSpan requestedHeartbeat = _factory.RequestedHeartbeat; - uint heartbeatInSeconds = NegotiatedMaxValue((uint)requestedHeartbeat.TotalSeconds, - (uint)connectionTune.m_heartbeatInSeconds); + uint heartbeatInSeconds = NegotiatedMaxValue((uint)requestedHeartbeat.TotalSeconds, connectionTune.m_heartbeatInSeconds); Heartbeat = TimeSpan.FromSeconds(heartbeatInSeconds); - _model0.ConnectionTuneOk(channelMax, frameMax, (ushort)Heartbeat.TotalSeconds); + await _connectionChannel.ConnectionTuneOkAsync(channelMax, frameMax, (ushort)Heartbeat.TotalSeconds).ConfigureAwait(false); // now we can start heartbeat timers MaybeStartHeartbeatTimers(); diff --git a/projects/RabbitMQ.Client/client/impl/ConsumerWorkService.cs b/projects/RabbitMQ.Client/client/impl/ConsumerWorkService.cs index 30109fad68..425d75cbe4 100644 --- a/projects/RabbitMQ.Client/client/impl/ConsumerWorkService.cs +++ b/projects/RabbitMQ.Client/client/impl/ConsumerWorkService.cs @@ -3,56 +3,58 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; +using Channel = System.Threading.Channels.Channel; namespace RabbitMQ.Client.Impl { internal class ConsumerWorkService { - private readonly ConcurrentDictionary _workPools = new ConcurrentDictionary(); - private readonly Func _startNewWorkPoolFunc; + private readonly ConcurrentDictionary _workPools = new ConcurrentDictionary(); + private readonly Func _startNewWorkPoolFunc; protected readonly int _concurrency; public ConsumerWorkService(int concurrency) { _concurrency = concurrency; - _startNewWorkPoolFunc = model => StartNewWorkPool(model); + _startNewWorkPoolFunc = channelBase => StartNewWorkPool(channelBase); } - public void AddWork(IModel model, Action fn) + public void AddWork(ChannelBase channelBase, Action fn) { /* * rabbitmq/rabbitmq-dotnet-client#841 * https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd * Note that the value delegate is not atomic but the AddWork method will not be called concurrently. */ - WorkPool workPool = _workPools.GetOrAdd(model, _startNewWorkPoolFunc); + WorkPool workPool = _workPools.GetOrAdd(channelBase, _startNewWorkPoolFunc); workPool.Enqueue(fn); } - private WorkPool StartNewWorkPool(IModel model) + private WorkPool StartNewWorkPool(ChannelBase channelBase) { - var newWorkPool = new WorkPool(_concurrency); + var newWorkPool = new WorkPool(channelBase, _concurrency); newWorkPool.Start(); return newWorkPool; } public void StopWork() { - foreach (IModel model in _workPools.Keys) + foreach (ChannelBase channelBase in _workPools.Keys) { - StopWork(model); + StopWork(channelBase); } } - public void StopWork(IModel model) + public void StopWork(ChannelBase channelBase) { - StopWorkAsync(model).GetAwaiter().GetResult(); + StopWorkAsync(channelBase).GetAwaiter().GetResult(); } - internal Task StopWorkAsync(IModel model) + internal Task StopWorkAsync(ChannelBase channelBase) { - if (_workPools.TryRemove(model, out WorkPool workPool)) + if (_workPools.TryRemove(channelBase, out WorkPool workPool)) { return workPool.Stop(); } @@ -63,13 +65,15 @@ internal Task StopWorkAsync(IModel model) private class WorkPool { private readonly Channel _channel; + private readonly ChannelBase _channelBase; private readonly int _concurrency; private Task _worker; private CancellationTokenSource _tokenSource; private SemaphoreSlim _limiter; - public WorkPool(int concurrency) + public WorkPool(ChannelBase channelBase, int concurrency) { + _channelBase = channelBase; _concurrency = concurrency; _channel = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true, SingleWriter = false, AllowSynchronousContinuations = false }); } @@ -103,9 +107,9 @@ private async Task Loop() { work(); } - catch(Exception) + catch(Exception e) { - // ignored + _channelBase.OnUnhandledExceptionOccurred(e, "ConsumerWorkService"); } } } @@ -125,7 +129,7 @@ private async Task LoopWithConcurrency(CancellationToken cancellationToken) await _limiter.WaitAsync(cancellationToken).ConfigureAwait(false); } - _ = OffloadToWorkerThreadPool(action, _limiter); + _ = OffloadToWorkerThreadPool(action, _channelBase, _limiter); } } } @@ -135,7 +139,7 @@ private async Task LoopWithConcurrency(CancellationToken cancellationToken) } } - private static async Task OffloadToWorkerThreadPool(Action action, SemaphoreSlim limiter) + private static async Task OffloadToWorkerThreadPool(Action action, ChannelBase channel, SemaphoreSlim limiter) { try { @@ -146,9 +150,9 @@ await Task.Factory.StartNew(state => }, action, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default) .ConfigureAwait(false); } - catch (Exception) + catch (Exception e) { - // ignored + channel.OnUnhandledExceptionOccurred(e, "ConsumerWorkService"); } finally { diff --git a/projects/RabbitMQ.Client/client/impl/ContentHeaderBase.cs b/projects/RabbitMQ.Client/client/impl/ContentHeaderBase.cs index ca9213825d..ad568817e3 100644 --- a/projects/RabbitMQ.Client/client/impl/ContentHeaderBase.cs +++ b/projects/RabbitMQ.Client/client/impl/ContentHeaderBase.cs @@ -33,7 +33,7 @@ namespace RabbitMQ.Client.Impl { - internal abstract class ContentHeaderBase : IContentHeader + public abstract class ContentHeaderBase : IContentHeader { /// /// Retrieve the AMQP class ID of this content header. diff --git a/projects/RabbitMQ.Client/client/impl/IConsumerDispatcher.cs b/projects/RabbitMQ.Client/client/impl/IConsumerDispatcher.cs index cc3c5ab1e7..0d96e7d362 100644 --- a/projects/RabbitMQ.Client/client/impl/IConsumerDispatcher.cs +++ b/projects/RabbitMQ.Client/client/impl/IConsumerDispatcher.cs @@ -62,6 +62,6 @@ void HandleModelShutdown(IBasicConsumer consumer, void Quiesce(); - Task Shutdown(IModel model); + Task Shutdown(); } } diff --git a/projects/RabbitMQ.Client/client/impl/IFullModel.cs b/projects/RabbitMQ.Client/client/impl/IFullModel.cs index 157d86ee1e..6563600cc2 100644 --- a/projects/RabbitMQ.Client/client/impl/IFullModel.cs +++ b/projects/RabbitMQ.Client/client/impl/IFullModel.cs @@ -29,257 +29,8 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- -using System; -using System.Collections.Generic; - namespace RabbitMQ.Client.Impl { - ///Not part of the public API. Extension of IModel to - ///include utilities and connection-setup routines needed by the - ///implementation side. - /// - ///This interface is used by the API autogeneration - ///process. The AMQP XML specifications are read by the spec - ///compilation tool, and after the basic method interface and - ///implementation classes are generated, this interface is - ///scanned, and a spec-version-specific implementation is - ///autogenerated. Annotations are used on certain methods, return - ///types, and parameters, to customise the details of the - ///autogeneration process. - /// - /// - /// - internal interface IFullModel : IModel - { - ///Sends a Connection.TuneOk. Used during connection - ///initialisation. - void ConnectionTuneOk(ushort channelMax, uint frameMax, ushort heartbeat); - - ///Handle incoming Basic.Deliver methods. Dispatches - ///to waiting consumers. - void HandleBasicDeliver(string consumerTag, - ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray); - - ///Handle incoming Basic.Ack methods. Signals a - ///BasicAckEvent. - void HandleBasicAck(ulong deliveryTag, bool multiple); - - void HandleBasicCancel(string consumerTag, bool nowait); - - ///Handle incoming Basic.CancelOk methods. - void HandleBasicCancelOk(string consumerTag); - - ///Handle incoming Basic.ConsumeOk methods. - void HandleBasicConsumeOk(string consumerTag); - - ///Handle incoming Basic.GetEmpty methods. Routes the - ///information to a waiting Basic.Get continuation. - /// - /// Note that the clusterId field is ignored, as in the - /// specification it notes that it is "deprecated pending - /// review". - /// - void HandleBasicGetEmpty(); - - ///Handle incoming Basic.GetOk methods. Routes the - ///information to a waiting Basic.Get continuation. - void HandleBasicGetOk(ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - uint messageCount, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray); - - ///Handle incoming Basic.Nack methods. Signals a - ///BasicNackEvent. - void HandleBasicNack(ulong deliveryTag, bool multiple, bool requeue); - - ///Handle incoming Basic.RecoverOk methods - ///received in reply to Basic.Recover. - /// - void HandleBasicRecoverOk(); - - ///Handle incoming Basic.Return methods. Signals a - ///BasicReturnEvent. - void HandleBasicReturn(ushort replyCode, - string replyText, - string exchange, - string routingKey, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] takeoverPayload); - - ///Handle an incoming Channel.Close. Shuts down the - ///session and model. - void HandleChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId); - - ///Handle an incoming Channel.CloseOk. - void HandleChannelCloseOk(); - - ///Handle incoming Channel.Flow methods. Either - ///stops or resumes sending the methods that have content. - void HandleChannelFlow(bool active); - - ///Handle an incoming Connection.Blocked. - void HandleConnectionBlocked(string reason); - - ///Handle an incoming Connection.Close. Shuts down the - ///connection and all sessions and models. - void HandleConnectionClose(ushort replyCode, string replyText, ushort classId, ushort methodId); - - ///Handle an incoming Connection.OpenOk. - void HandleConnectionOpenOk(string knownHosts); - - /////////////////////////////////////////////////////////////////////////// - // Connection-related methods, for use in channel 0 during - // connection startup/shutdown. - - ///Handle incoming Connection.Secure - ///methods. - void HandleConnectionSecure(byte[] challenge); - - ///Handle an incoming Connection.Start. Used during - ///connection initialisation. - void HandleConnectionStart(byte versionMajor, byte versionMinor, IDictionary serverProperties, byte[] mechanisms, byte[] locales); - - ///Handle incoming Connection.Tune - ///methods. - void HandleConnectionTune(ushort channelMax, uint frameMax, ushort heartbeat); - - ///Handle an incominga Connection.Unblocked. - void HandleConnectionUnblocked(); - - ///Handle incoming Queue.DeclareOk methods. Routes the - ///information to a waiting Queue.DeclareOk continuation. - void HandleQueueDeclareOk(string queue, uint messageCount, uint consumerCount); - - ///Used to send a Basic.Cancel method. The public - ///consume API calls this while also managing internal - ///datastructures. - void _Private_BasicCancel(string consumerTag, bool nowait); - - ///Used to send a Basic.Consume method. The public - ///consume API calls this while also managing internal - ///datastructures. - void _Private_BasicConsume(string queue, string consumerTag, bool noLocal, bool autoAck, bool exclusive, bool nowait, IDictionary arguments); - - ///Used to send a Basic.Get. Basic.Get is a special - ///case, since it can result in a Basic.GetOk or a - ///Basic.GetEmpty, so this level of manual control is - ///required. - void _Private_BasicGet(string queue, bool autoAck); - - ///Used to send a Basic.Publish method. Called by the - ///public publish method after potential null-reference issues - ///have been rectified. - void _Private_BasicPublish(string exchange, string routingKey, bool mandatory, IBasicProperties basicProperties, ReadOnlyMemory body); - - void _Private_BasicRecover(bool requeue); - - ///Used to send a Channel.Close. Called during - ///session shutdown. - void _Private_ChannelClose(ushort replyCode, string replyText, ushort classId, ushort methodId); - - ///Used to send a Channel.CloseOk. Called during - ///session shutdown. - void _Private_ChannelCloseOk(); - - ///Used to send a Channel.FlowOk. Confirms that - ///Channel.Flow from the broker was processed. - void _Private_ChannelFlowOk(bool active); - - ///Used to send a Channel.Open. Called during session - ///initialisation. - void _Private_ChannelOpen(string outOfBand); - - ///Used to send a Confirm.Select method. The public - ///confirm API calls this while also managing internal - ///datastructures. - void _Private_ConfirmSelect(bool nowait); - - ///Used to send a Connection.Close. Called during - ///connection shutdown. - void _Private_ConnectionClose(ushort replyCode, string replyText, ushort classId, ushort methodId); - - ///Used to send a Connection.CloseOk. Called during - ///connection shutdown. - void _Private_ConnectionCloseOk(); - - ///Used to send a Connection.Open. Called during - ///connection startup. - void _Private_ConnectionOpen(string virtualHost, string capabilities, bool insist); - - ///Used to send a Connection.SecureOk. Again, this is - ///special, like Basic.Get. - void _Private_ConnectionSecureOk(byte[] response); - - ///Used to send a Connection.StartOk. This is - ///special, like Basic.Get. - void _Private_ConnectionStartOk(IDictionary clientProperties, string mechanism, byte[] response, string locale); - - ///Used to send a Conection.UpdateSecret method. Called by the - ///public UpdateSecret method. - /// - void _Private_UpdateSecret(byte[] newSecret, string reason); - - ///Used to send a Exchange.Bind method. Called by the - ///public bind method. - /// - void _Private_ExchangeBind(string destination, string source, string routingKey, bool nowait, IDictionary arguments); - - ///Used to send a Exchange.Declare method. Called by the - ///public declare method. - /// - void _Private_ExchangeDeclare(string exchange, - string type, - bool passive, - bool durable, - bool autoDelete, - bool @internal, - bool nowait, - IDictionary arguments); - - ///Used to send a Exchange.Delete method. Called by the - ///public delete method. - /// - void _Private_ExchangeDelete(string exchange, bool ifUnused, bool nowait); - - ///Used to send a Exchange.Unbind method. Called by the - ///public unbind method. - /// - void _Private_ExchangeUnbind(string destination, string source, string routingKey, bool nowait, IDictionary arguments); - - ///Used to send a Queue.Bind method. Called by the - ///public bind method. - void _Private_QueueBind(string queue, string exchange, string routingKey, bool nowait, IDictionary arguments); - - ///Used to send a Queue.Declare method. Called by the - ///public declare method. - void _Private_QueueDeclare(string queue, - bool passive, - bool durable, - bool exclusive, - bool autoDelete, - bool nowait, - IDictionary arguments); - - ///Used to send a Queue.Delete method. Called by the - ///public delete method. - uint _Private_QueueDelete(string queue, bool ifUnused, bool ifEmpty, bool nowait); - - ///Used to send a Queue.Purge method. Called by the - ///public purge method. - uint _Private_QueuePurge(string queue, bool nowait); - } - ///Essential information from an incoming Connection.Tune ///method. internal struct ConnectionTuneDetails diff --git a/projects/RabbitMQ.Client/client/impl/ModelBase.cs b/projects/RabbitMQ.Client/client/impl/ModelBase.cs index 1610da4829..9d34e6da02 100644 --- a/projects/RabbitMQ.Client/client/impl/ModelBase.cs +++ b/projects/RabbitMQ.Client/client/impl/ModelBase.cs @@ -29,1444 +29,10 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using RabbitMQ.Client.Events; -using RabbitMQ.Client.Exceptions; -using RabbitMQ.Client.Framing.Impl; -using RabbitMQ.Util; - namespace RabbitMQ.Client.Impl { - internal abstract class ModelBase : IFullModel, IRecoverable + internal abstract class ModelBase { - ///Only used to kick-start a connection open - ///sequence. See - public BlockingCell m_connectionStartCell = null; - internal readonly IBasicProperties _emptyBasicProperties; - - private readonly Dictionary _consumers = new Dictionary(); - private readonly RpcContinuationQueue _continuationQueue = new RpcContinuationQueue(); - private readonly ManualResetEventSlim _flowControlBlock = new ManualResetEventSlim(true); - - private readonly object _shutdownLock = new object(); - private readonly object _rpcLock = new object(); - private readonly object _confirmLock = new object(); - private readonly LinkedList _pendingDeliveryTags = new LinkedList(); - private readonly CountdownEvent _deliveryTagsCountdown = new CountdownEvent(0); - private EventHandler _modelShutdown; - - private bool _onlyAcksReceived = true; - - public IConsumerDispatcher ConsumerDispatcher { get; } - - public ModelBase(ISession session) : this(session, session.Connection.ConsumerWorkService) - { } - - public ModelBase(ISession session, ConsumerWorkService workService) - { - if (workService is AsyncConsumerWorkService asyncConsumerWorkService) - { - ConsumerDispatcher = new AsyncConsumerDispatcher(this, asyncConsumerWorkService); - } - else - { - ConsumerDispatcher = new ConcurrentConsumerDispatcher(this, workService); - } - - _emptyBasicProperties = CreateBasicProperties(); - Initialise(session); - } - - protected void Initialise(ISession session) - { - CloseReason = null; - NextPublishSeqNo = 0; - Session = session; - Session.CommandReceived = HandleCommand; - Session.SessionShutdown += OnSessionShutdown; - } - - public TimeSpan HandshakeContinuationTimeout { get; set; } = TimeSpan.FromSeconds(10); - public TimeSpan ContinuationTimeout { get; set; } = TimeSpan.FromSeconds(20); - public event EventHandler BasicAcks; - public event EventHandler BasicNacks; - public event EventHandler BasicRecoverOk; - public event EventHandler BasicReturn; - public event EventHandler CallbackException; - public event EventHandler FlowControl; - public event EventHandler ModelShutdown - { - add - { - bool ok = false; - if (CloseReason is null) - { - lock (_shutdownLock) - { - if (CloseReason is null) - { - _modelShutdown += value; - ok = true; - } - } - } - if (!ok) - { - value(this, CloseReason); - } - } - remove - { - lock (_shutdownLock) - { - _modelShutdown -= value; - } - } - } - -#pragma warning disable 67 - public event EventHandler Recovery; -#pragma warning restore 67 - - public int ChannelNumber => ((Session)Session).ChannelNumber; - - public ShutdownEventArgs CloseReason { get; private set; } - - public IBasicConsumer DefaultConsumer { get; set; } - - public bool IsClosed => !IsOpen; - - public bool IsOpen => CloseReason is null; - - public ulong NextPublishSeqNo { get; private set; } - - public ISession Session { get; private set; } - - public Task Close(ushort replyCode, string replyText, bool abort) - { - return Close(new ShutdownEventArgs(ShutdownInitiator.Application, - replyCode, replyText), - abort); - } - - public async Task Close(ShutdownEventArgs reason, bool abort) - { - var k = new ShutdownContinuation(); - ModelShutdown += k.OnConnectionShutdown; - - try - { - ConsumerDispatcher.Quiesce(); - if (SetCloseReason(reason)) - { - _Private_ChannelClose(reason.ReplyCode, reason.ReplyText, 0, 0); - } - k.Wait(TimeSpan.FromMilliseconds(10000)); - await ConsumerDispatcher.Shutdown(this).ConfigureAwait(false); - } - catch (AlreadyClosedException) - { - if (!abort) - { - throw; - } - } - catch (IOException) - { - if (!abort) - { - throw; - } - } - catch (Exception) - { - if (!abort) - { - throw; - } - } - } - - public string ConnectionOpen(string virtualHost, - string capabilities, - bool insist) - { - var k = new ConnectionOpenContinuation(); - lock (_rpcLock) - { - Enqueue(k); - try - { - _Private_ConnectionOpen(virtualHost, capabilities, insist); - } - catch (AlreadyClosedException) - { - // let continuation throw OperationInterruptedException, - // which is a much more suitable exception before connection - // negotiation finishes - } - k.GetReply(HandshakeContinuationTimeout); - } - - return k.m_knownHosts; - } - - public ConnectionSecureOrTune ConnectionSecureOk(byte[] response) - { - var k = new ConnectionStartRpcContinuation(); - lock (_rpcLock) - { - Enqueue(k); - try - { - _Private_ConnectionSecureOk(response); - } - catch (AlreadyClosedException) - { - // let continuation throw OperationInterruptedException, - // which is a much more suitable exception before connection - // negotiation finishes - } - k.GetReply(HandshakeContinuationTimeout); - } - return k.m_result; - } - - public ConnectionSecureOrTune ConnectionStartOk(IDictionary clientProperties, - string mechanism, - byte[] response, - string locale) - { - var k = new ConnectionStartRpcContinuation(); - lock (_rpcLock) - { - Enqueue(k); - try - { - _Private_ConnectionStartOk(clientProperties, mechanism, - response, locale); - } - catch (AlreadyClosedException) - { - // let continuation throw OperationInterruptedException, - // which is a much more suitable exception before connection - // negotiation finishes - } - k.GetReply(HandshakeContinuationTimeout); - } - return k.m_result; - } - - public abstract bool DispatchAsynchronous(in IncomingCommand cmd); - - public void Enqueue(IRpcContinuation k) - { - bool ok = false; - if (CloseReason is null) - { - lock (_shutdownLock) - { - if (CloseReason is null) - { - _continuationQueue.Enqueue(k); - ok = true; - } - } - } - if (!ok) - { - k.HandleModelShutdown(CloseReason); - } - } - - public void FinishClose() - { - if (CloseReason != null) - { - Session.Close(CloseReason); - } - if (m_connectionStartCell != null) - { - m_connectionStartCell.ContinueWithValue(null); - } - } - - public void HandleCommand(in IncomingCommand cmd) - { - if (!DispatchAsynchronous(in cmd)) // Was asynchronous. Already processed. No need to process further. - { - _continuationQueue.Next().HandleCommand(in cmd); - } - } - - public T ModelRpc(MethodBase method) where T : MethodBase - { - var k = new SimpleBlockingRpcContinuation(); - var outgoingCommand = new OutgoingCommand(method); - MethodBase baseResult; - lock (_rpcLock) - { - TransmitAndEnqueue(outgoingCommand, k); - baseResult = k.GetReply(ContinuationTimeout).Method; - } - - if (baseResult is T result) - { - return result; - } - - throw new UnexpectedMethodException(baseResult.ProtocolClassId, baseResult.ProtocolMethodId, baseResult.ProtocolMethodName); - } - - public void ModelSend(MethodBase method) - { - ModelSend(method, null, ReadOnlyMemory.Empty); - } - - public void ModelSend(MethodBase method, ContentHeaderBase header, ReadOnlyMemory body) - { - if (method.HasContent) - { - _flowControlBlock.Wait(); - Session.Transmit(new OutgoingCommand(method, header, body)); - } - else - { - Session.Transmit(new OutgoingCommand(method, header, body)); - } - } - - public virtual void OnBasicRecoverOk(EventArgs args) - { - foreach (EventHandler h in BasicRecoverOk?.GetInvocationList() ?? Array.Empty()) - { - try - { - h(this, args); - } - catch (Exception e) - { - OnCallbackException(CallbackExceptionEventArgs.Build(e, "OnBasicRecover")); - } - } - } - - public virtual void OnCallbackException(CallbackExceptionEventArgs args) - { - foreach (EventHandler h in CallbackException?.GetInvocationList() ?? Array.Empty()) - { - try - { - h(this, args); - } - catch - { - // Exception in - // Callback-exception-handler. That was the - // app's last chance. Swallow the exception. - // FIXME: proper logging - } - } - } - - public virtual void OnFlowControl(FlowControlEventArgs args) - { - foreach (EventHandler h in FlowControl?.GetInvocationList() ?? Array.Empty()) - { - try - { - h(this, args); - } - catch (Exception e) - { - OnCallbackException(CallbackExceptionEventArgs.Build(e, "OnFlowControl")); - } - } - } - - ///Broadcasts notification of the final shutdown of the model. - /// - /// - ///Do not call anywhere other than at the end of OnSessionShutdown. - /// - /// - ///Must not be called when m_closeReason is null, because - ///otherwise there's a window when a new continuation could be - ///being enqueued at the same time as we're broadcasting the - ///shutdown event. See the definition of Enqueue() above. - /// - /// - public virtual void OnModelShutdown(ShutdownEventArgs reason) - { - _continuationQueue.HandleModelShutdown(reason); - EventHandler handler; - lock (_shutdownLock) - { - handler = _modelShutdown; - _modelShutdown = null; - } - if (handler != null) - { - foreach (EventHandler h in handler.GetInvocationList()) - { - try - { - h(this, reason); - } - catch (Exception e) - { - OnCallbackException(CallbackExceptionEventArgs.Build(e, "OnModelShutdown")); - } - } - } - - _deliveryTagsCountdown.Reset(0); - _flowControlBlock.Set(); - } - - public void OnSessionShutdown(object sender, ShutdownEventArgs reason) - { - ConsumerDispatcher.Quiesce(); - SetCloseReason(reason); - OnModelShutdown(reason); - BroadcastShutdownToConsumers(_consumers, reason); - ConsumerDispatcher.Shutdown(this).GetAwaiter().GetResult();; - } - - protected void BroadcastShutdownToConsumers(Dictionary cs, ShutdownEventArgs reason) - { - foreach (KeyValuePair c in cs) - { - ConsumerDispatcher.HandleModelShutdown(c.Value, reason); - } - } - - public bool SetCloseReason(ShutdownEventArgs reason) - { - if (CloseReason is null) - { - lock (_shutdownLock) - { - if (CloseReason is null) - { - CloseReason = reason; - return true; - } - else - { - return false; - } - } - } - else - return false; - } - - public override string ToString() - { - return Session.ToString(); - } - - public void TransmitAndEnqueue(in OutgoingCommand cmd, IRpcContinuation k) - { - Enqueue(k); - Session.Transmit(cmd); - } - - void IDisposable.Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - // dispose managed resources - Abort(); - } - - // dispose unmanaged resources - } - - public abstract void ConnectionTuneOk(ushort channelMax, - uint frameMax, - ushort heartbeat); - - public void HandleBasicAck(ulong deliveryTag, bool multiple) - { - EventHandler @event = BasicAcks; - if (@event != null) - { - var args = new BasicAckEventArgs - { - DeliveryTag = deliveryTag, - Multiple = multiple - }; - foreach (EventHandler h in @event.GetInvocationList()) - { - try - { - h(this, args); - } - catch (Exception e) - { - OnCallbackException(CallbackExceptionEventArgs.Build(e, "OnBasicAck")); - } - } - } - - HandleAckNack(deliveryTag, multiple, false); - } - - public void HandleBasicNack(ulong deliveryTag, bool multiple, bool requeue) - { - EventHandler @event = BasicNacks; - if (@event != null) - { - var args = new BasicNackEventArgs - { - DeliveryTag = deliveryTag, - Multiple = multiple, - Requeue = requeue - }; - foreach (EventHandler h in @event.GetInvocationList()) - { - try - { - h(this, args); - } - catch (Exception e1) - { - OnCallbackException(CallbackExceptionEventArgs.Build(e1, "OnBasicNack")); - } - } - } - - HandleAckNack(deliveryTag, multiple, true); - } - - protected void HandleAckNack(ulong deliveryTag, bool multiple, bool isNack) - { - // No need to do this if publisher confirms have never been enabled. - if (NextPublishSeqNo > 0) - { - // let's take a lock so we can assume that deliveryTags are unique, never duplicated and always sorted - lock (_confirmLock) - { - // No need to do anything if there are no delivery tags in the list - if (_pendingDeliveryTags.Count > 0) - { - if (multiple) - { - int count = 0; - while (_pendingDeliveryTags.First.Value < deliveryTag) - { - _pendingDeliveryTags.RemoveFirst(); - count++; - } - - if (_pendingDeliveryTags.First.Value == deliveryTag) - { - _pendingDeliveryTags.RemoveFirst(); - count++; - } - - if (count > 0) - { - _deliveryTagsCountdown.Signal(count); - } - } - else - { - if (_pendingDeliveryTags.Remove(deliveryTag)) - { - _deliveryTagsCountdown.Signal(); - } - } - } - - _onlyAcksReceived = _onlyAcksReceived && !isNack; - } - } - } - - public void HandleBasicCancel(string consumerTag, bool nowait) - { - IBasicConsumer consumer; - lock (_consumers) - { - consumer = _consumers[consumerTag]; - _consumers.Remove(consumerTag); - } - if (consumer is null) - { - consumer = DefaultConsumer; - } - ConsumerDispatcher.HandleBasicCancel(consumer, consumerTag); - } - - public void HandleBasicCancelOk(string consumerTag) - { - var k = - (BasicConsumerRpcContinuation)_continuationQueue.Next(); - /* - Trace.Assert(k.m_consumerTag == consumerTag, string.Format( - "Consumer tag mismatch during cancel: {0} != {1}", - k.m_consumerTag, - consumerTag - )); - */ - lock (_consumers) - { - k.m_consumer = _consumers[consumerTag]; - _consumers.Remove(consumerTag); - } - ConsumerDispatcher.HandleBasicCancelOk(k.m_consumer, consumerTag); - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public void HandleBasicConsumeOk(string consumerTag) - { - var k = - (BasicConsumerRpcContinuation)_continuationQueue.Next(); - k.m_consumerTag = consumerTag; - lock (_consumers) - { - _consumers[consumerTag] = k.m_consumer; - } - ConsumerDispatcher.HandleBasicConsumeOk(k.m_consumer, consumerTag); - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public virtual void HandleBasicDeliver(string consumerTag, - ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - IBasicConsumer consumer; - lock (_consumers) - { - consumer = _consumers[consumerTag]; - } - if (consumer is null) - { - if (DefaultConsumer is null) - { - throw new InvalidOperationException("Unsolicited delivery - see IModel.DefaultConsumer to handle this case."); - } - else - { - consumer = DefaultConsumer; - } - } - - ConsumerDispatcher.HandleBasicDeliver(consumer, - consumerTag, - deliveryTag, - redelivered, - exchange, - routingKey, - basicProperties, - body, - rentedArray); - } - - public void HandleBasicGetEmpty() - { - var k = (BasicGetRpcContinuation)_continuationQueue.Next(); - k.m_result = null; - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public virtual void HandleBasicGetOk(ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - uint messageCount, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - var k = (BasicGetRpcContinuation)_continuationQueue.Next(); - k.m_result = new BasicGetResult(deliveryTag, redelivered, exchange, routingKey, messageCount, basicProperties, body, rentedArray); - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public void HandleBasicRecoverOk() - { - var k = (SimpleBlockingRpcContinuation)_continuationQueue.Next(); - OnBasicRecoverOk(EventArgs.Empty); - k.HandleCommand(IncomingCommand.Empty); - } - - public void HandleBasicReturn(ushort replyCode, - string replyText, - string exchange, - string routingKey, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - EventHandler @event = BasicReturn; - if (@event != null) - { - var e = new BasicReturnEventArgs - { - ReplyCode = replyCode, - ReplyText = replyText, - Exchange = exchange, - RoutingKey = routingKey, - BasicProperties = basicProperties, - Body = body - }; - foreach (EventHandler h in @event.GetInvocationList()) - { - try - { - h(this, e); - } - catch (Exception e1) - { - OnCallbackException(CallbackExceptionEventArgs.Build(e1, "OnBasicReturn")); - } - } - } - ArrayPool.Shared.Return(rentedArray); - } - - public void HandleChannelClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId) - { - SetCloseReason(new ShutdownEventArgs(ShutdownInitiator.Peer, - replyCode, - replyText, - classId, - methodId)); - - Session.Close(CloseReason, false); - try - { - _Private_ChannelCloseOk(); - } - finally - { - Session.Notify(); - } - } - - public void HandleChannelCloseOk() - { - FinishClose(); - } - - public void HandleChannelFlow(bool active) - { - if (active) - { - _flowControlBlock.Set(); - _Private_ChannelFlowOk(active); - } - else - { - _flowControlBlock.Reset(); - _Private_ChannelFlowOk(active); - } - OnFlowControl(new FlowControlEventArgs(active)); - } - - public void HandleConnectionBlocked(string reason) - { - var cb = (Connection)Session.Connection; - - cb.HandleConnectionBlocked(reason); - } - - public void HandleConnectionClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId) - { - var reason = new ShutdownEventArgs(ShutdownInitiator.Peer, - replyCode, - replyText, - classId, - methodId); - try - { - ((Connection)Session.Connection).InternalClose(reason); - _Private_ConnectionCloseOk(); - SetCloseReason(Session.Connection.CloseReason); - } - catch (IOException) - { - // Ignored. We're only trying to be polite by sending - // the close-ok, after all. - } - catch (AlreadyClosedException) - { - // Ignored. We're only trying to be polite by sending - // the close-ok, after all. - } - } - - public void HandleConnectionOpenOk(string knownHosts) - { - var k = (ConnectionOpenContinuation)_continuationQueue.Next(); - k.m_redirect = false; - k.m_host = null; - k.m_knownHosts = knownHosts; - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public void HandleConnectionSecure(byte[] challenge) - { - var k = (ConnectionStartRpcContinuation)_continuationQueue.Next(); - k.m_result = new ConnectionSecureOrTune - { - m_challenge = challenge - }; - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public void HandleConnectionStart(byte versionMajor, - byte versionMinor, - IDictionary serverProperties, - byte[] mechanisms, - byte[] locales) - { - if (m_connectionStartCell is null) - { - var reason = - new ShutdownEventArgs(ShutdownInitiator.Library, - Constants.CommandInvalid, - "Unexpected Connection.Start"); - ((Connection)Session.Connection).Close(reason); - } - var details = new ConnectionStartDetails - { - m_versionMajor = versionMajor, - m_versionMinor = versionMinor, - m_serverProperties = serverProperties, - m_mechanisms = mechanisms, - m_locales = locales - }; - m_connectionStartCell.ContinueWithValue(details); - m_connectionStartCell = null; - } - - ///Handle incoming Connection.Tune - ///methods. - public void HandleConnectionTune(ushort channelMax, uint frameMax, ushort heartbeatInSeconds) - { - var k = (ConnectionStartRpcContinuation)_continuationQueue.Next(); - k.m_result = new ConnectionSecureOrTune - { - m_tuneDetails = - { - m_channelMax = channelMax, - m_frameMax = frameMax, - m_heartbeatInSeconds = heartbeatInSeconds - } - }; - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public void HandleConnectionUnblocked() - { - var cb = (Connection)Session.Connection; - - cb.HandleConnectionUnblocked(); - } - - public void HandleQueueDeclareOk(string queue, - uint messageCount, - uint consumerCount) - { - var k = (QueueDeclareRpcContinuation)_continuationQueue.Next(); - k.m_result = new QueueDeclareOk(queue, messageCount, consumerCount); - k.HandleCommand(IncomingCommand.Empty); // release the continuation. - } - - public abstract void _Private_BasicCancel(string consumerTag, - bool nowait); - - public abstract void _Private_BasicConsume(string queue, - string consumerTag, - bool noLocal, - bool autoAck, - bool exclusive, - bool nowait, - IDictionary arguments); - - public abstract void _Private_BasicGet(string queue, - bool autoAck); - - public abstract void _Private_BasicPublish(string exchange, - string routingKey, - bool mandatory, - IBasicProperties basicProperties, - ReadOnlyMemory body); - - public abstract void _Private_BasicRecover(bool requeue); - - public abstract void _Private_ChannelClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId); - - public abstract void _Private_ChannelCloseOk(); - - public abstract void _Private_ChannelFlowOk(bool active); - - public abstract void _Private_ChannelOpen(string outOfBand); - - public abstract void _Private_ConfirmSelect(bool nowait); - - public abstract void _Private_ConnectionClose(ushort replyCode, - string replyText, - ushort classId, - ushort methodId); - - public abstract void _Private_ConnectionCloseOk(); - - public abstract void _Private_ConnectionOpen(string virtualHost, - string capabilities, - bool insist); - - public abstract void _Private_ConnectionSecureOk(byte[] response); - - public abstract void _Private_ConnectionStartOk(IDictionary clientProperties, - string mechanism, - byte[] response, - string locale); - - public abstract void _Private_UpdateSecret( - byte[] @newSecret, - string @reason); - - public abstract void _Private_ExchangeBind(string destination, - string source, - string routingKey, - bool nowait, - IDictionary arguments); - - public abstract void _Private_ExchangeDeclare(string exchange, - string type, - bool passive, - bool durable, - bool autoDelete, - bool @internal, - bool nowait, - IDictionary arguments); - - public abstract void _Private_ExchangeDelete(string exchange, - bool ifUnused, - bool nowait); - - public abstract void _Private_ExchangeUnbind(string destination, - string source, - string routingKey, - bool nowait, - IDictionary arguments); - - public abstract void _Private_QueueBind(string queue, - string exchange, - string routingKey, - bool nowait, - IDictionary arguments); - - public abstract void _Private_QueueDeclare(string queue, - bool passive, - bool durable, - bool exclusive, - bool autoDelete, - bool nowait, - IDictionary arguments); - - public abstract uint _Private_QueueDelete(string queue, - bool ifUnused, - bool ifEmpty, - bool nowait); - - public abstract uint _Private_QueuePurge(string queue, - bool nowait); - - public void Abort() - { - Abort(Constants.ReplySuccess, "Goodbye"); - } - - public void Abort(ushort replyCode, string replyText) - { - Close(replyCode, replyText, true); - } - - public abstract void BasicAck(ulong deliveryTag, bool multiple); - - public void BasicCancel(string consumerTag) - { - var k = new BasicConsumerRpcContinuation { m_consumerTag = consumerTag }; - - lock (_rpcLock) - { - Enqueue(k); - _Private_BasicCancel(consumerTag, false); - k.GetReply(ContinuationTimeout); - } - - lock (_consumers) - { - _consumers.Remove(consumerTag); - } - - ModelShutdown -= k.m_consumer.HandleModelShutdown; - } - - public void BasicCancelNoWait(string consumerTag) - { - _Private_BasicCancel(consumerTag, true); - - lock (_consumers) - { - _consumers.Remove(consumerTag); - } - } - - public string BasicConsume(string queue, - bool autoAck, - string consumerTag, - bool noLocal, - bool exclusive, - IDictionary arguments, - IBasicConsumer consumer) - { - // TODO: Replace with flag - if (ConsumerDispatcher is AsyncConsumerDispatcher) - { - if (!(consumer is IAsyncBasicConsumer)) - { - // TODO: Friendly message - throw new InvalidOperationException("In the async mode you have to use an async consumer"); - } - } - - var k = new BasicConsumerRpcContinuation { m_consumer = consumer }; - - lock (_rpcLock) - { - Enqueue(k); - // Non-nowait. We have an unconventional means of getting - // the RPC response, but a response is still expected. - _Private_BasicConsume(queue, consumerTag, noLocal, autoAck, exclusive, - /*nowait:*/ false, arguments); - k.GetReply(ContinuationTimeout); - } - string actualConsumerTag = k.m_consumerTag; - - return actualConsumerTag; - } - - public BasicGetResult BasicGet(string queue, bool autoAck) - { - var k = new BasicGetRpcContinuation(); - lock (_rpcLock) - { - Enqueue(k); - _Private_BasicGet(queue, autoAck); - k.GetReply(ContinuationTimeout); - } - - return k.m_result; - } - - public abstract void BasicNack(ulong deliveryTag, - bool multiple, - bool requeue); - - internal void AllocatePublishSeqNos(int count) - { - if (NextPublishSeqNo > 0) - { - lock (_confirmLock) - { - if (_deliveryTagsCountdown.IsSet) - { - _deliveryTagsCountdown.Reset(count); - } - else - { - _deliveryTagsCountdown.AddCount(count); - } - - for (int i = 0; i < count; i++) - { - _pendingDeliveryTags.AddLast(NextPublishSeqNo++); - } - } - } - } - - public void BasicPublish(string exchange, - string routingKey, - bool mandatory, - IBasicProperties basicProperties, - ReadOnlyMemory body) - { - if (routingKey is null) - { - throw new ArgumentNullException(nameof(routingKey)); - } - - if (basicProperties is null) - { - basicProperties = _emptyBasicProperties; - } - - if (NextPublishSeqNo > 0) - { - lock (_confirmLock) - { - if (_deliveryTagsCountdown.IsSet) - { - _deliveryTagsCountdown.Reset(1); - } - else - { - _deliveryTagsCountdown.AddCount(); - } - - _pendingDeliveryTags.AddLast(NextPublishSeqNo++); - } - } - - _Private_BasicPublish(exchange, - routingKey, - mandatory, - basicProperties, - body); - } - - public void UpdateSecret(string newSecret, string reason) - { - if (newSecret is null) - { - throw new ArgumentNullException(nameof(newSecret)); - } - - if (reason is null) - { - throw new ArgumentNullException(nameof(reason)); - } - - _Private_UpdateSecret(Encoding.UTF8.GetBytes(newSecret), reason); - } - - public abstract void BasicQos(uint prefetchSize, - ushort prefetchCount, - bool global); - - public void BasicRecover(bool requeue) - { - var k = new SimpleBlockingRpcContinuation(); - - lock (_rpcLock) - { - Enqueue(k); - _Private_BasicRecover(requeue); - k.GetReply(ContinuationTimeout); - } - } - - public abstract void BasicRecoverAsync(bool requeue); - - public abstract void BasicReject(ulong deliveryTag, - bool requeue); - - public void Close() - { - Close(Constants.ReplySuccess, "Goodbye"); - } - - public void Close(ushort replyCode, string replyText) - { - Close(replyCode, replyText, false); - } - - public void ConfirmSelect() - { - if (NextPublishSeqNo == 0UL) - { - NextPublishSeqNo = 1; - } - - _Private_ConfirmSelect(false); - } - - /////////////////////////////////////////////////////////////////////////// - - public abstract IBasicProperties CreateBasicProperties(); - public IBasicPublishBatch CreateBasicPublishBatch() - { - return new BasicPublishBatch(this); - } - - public IBasicPublishBatch CreateBasicPublishBatch(int sizeHint) - { - return new BasicPublishBatch(this, sizeHint); - } - - - public void ExchangeBind(string destination, - string source, - string routingKey, - IDictionary arguments) - { - _Private_ExchangeBind(destination, source, routingKey, false, arguments); - } - - public void ExchangeBindNoWait(string destination, - string source, - string routingKey, - IDictionary arguments) - { - _Private_ExchangeBind(destination, source, routingKey, true, arguments); - } - - public void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete, IDictionary arguments) - { - _Private_ExchangeDeclare(exchange, type, false, durable, autoDelete, false, false, arguments); - } - - public void ExchangeDeclareNoWait(string exchange, - string type, - bool durable, - bool autoDelete, - IDictionary arguments) - { - _Private_ExchangeDeclare(exchange, type, false, durable, autoDelete, false, true, arguments); - } - - public void ExchangeDeclarePassive(string exchange) - { - _Private_ExchangeDeclare(exchange, "", true, false, false, false, false, null); - } - - public void ExchangeDelete(string exchange, - bool ifUnused) - { - _Private_ExchangeDelete(exchange, ifUnused, false); - } - - public void ExchangeDeleteNoWait(string exchange, - bool ifUnused) - { - _Private_ExchangeDelete(exchange, ifUnused, true); - } - - public void ExchangeUnbind(string destination, - string source, - string routingKey, - IDictionary arguments) - { - _Private_ExchangeUnbind(destination, source, routingKey, false, arguments); - } - - public void ExchangeUnbindNoWait(string destination, - string source, - string routingKey, - IDictionary arguments) - { - _Private_ExchangeUnbind(destination, source, routingKey, true, arguments); - } - - public void QueueBind(string queue, - string exchange, - string routingKey, - IDictionary arguments) - { - _Private_QueueBind(queue, exchange, routingKey, false, arguments); - } - - public void QueueBindNoWait(string queue, - string exchange, - string routingKey, - IDictionary arguments) - { - _Private_QueueBind(queue, exchange, routingKey, true, arguments); - } - - public QueueDeclareOk QueueDeclare(string queue, bool durable, - bool exclusive, bool autoDelete, - IDictionary arguments) - { - return QueueDeclare(queue, false, durable, exclusive, autoDelete, arguments); - } - - public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, - bool autoDelete, IDictionary arguments) - { - _Private_QueueDeclare(queue, false, durable, exclusive, autoDelete, true, arguments); - } - - public QueueDeclareOk QueueDeclarePassive(string queue) - { - return QueueDeclare(queue, true, false, false, false, null); - } - - public uint MessageCount(string queue) - { - QueueDeclareOk ok = QueueDeclarePassive(queue); - return ok.MessageCount; - } - - public uint ConsumerCount(string queue) - { - QueueDeclareOk ok = QueueDeclarePassive(queue); - return ok.ConsumerCount; - } - - public uint QueueDelete(string queue, - bool ifUnused, - bool ifEmpty) - { - return _Private_QueueDelete(queue, ifUnused, ifEmpty, false); - } - - public void QueueDeleteNoWait(string queue, - bool ifUnused, - bool ifEmpty) - { - _Private_QueueDelete(queue, ifUnused, ifEmpty, true); - } - - public uint QueuePurge(string queue) - { - return _Private_QueuePurge(queue, false); - } - - public abstract void QueueUnbind(string queue, - string exchange, - string routingKey, - IDictionary arguments); - - public abstract void TxCommit(); - - public abstract void TxRollback(); - - public abstract void TxSelect(); - - public bool WaitForConfirms(TimeSpan timeout, out bool timedOut) - { - if (NextPublishSeqNo == 0UL) - { - throw new InvalidOperationException("Confirms not selected"); - } - bool isWaitInfinite = timeout.TotalMilliseconds == Timeout.Infinite; - Stopwatch stopwatch = Stopwatch.StartNew(); - while (true) - { - if (!IsOpen) - { - throw new AlreadyClosedException(CloseReason); - } - - if (_deliveryTagsCountdown.IsSet) - { - bool aux = _onlyAcksReceived; - _onlyAcksReceived = true; - timedOut = false; - return aux; - } - - if (isWaitInfinite) - { - _deliveryTagsCountdown.Wait(); - } - else - { - TimeSpan elapsed = stopwatch.Elapsed; - if (elapsed > timeout || !_deliveryTagsCountdown.Wait(timeout - elapsed)) - { - timedOut = true; - return _onlyAcksReceived; - } - } - } - } - - public bool WaitForConfirms() - { - return WaitForConfirms(TimeSpan.FromMilliseconds(Timeout.Infinite), out _); - } - - public bool WaitForConfirms(TimeSpan timeout) - { - return WaitForConfirms(timeout, out _); - } - - public void WaitForConfirmsOrDie() - { - WaitForConfirmsOrDie(TimeSpan.FromMilliseconds(Timeout.Infinite)); - } - - public void WaitForConfirmsOrDie(TimeSpan timeout) - { - bool onlyAcksReceived = WaitForConfirms(timeout, out bool timedOut); - if (!onlyAcksReceived) - { - Close(new ShutdownEventArgs(ShutdownInitiator.Application, - Constants.ReplySuccess, - "Nacks Received", new IOException("nack received")), - false).GetAwaiter().GetResult(); - throw new IOException("Nacks Received"); - } - if (timedOut) - { - Close(new ShutdownEventArgs(ShutdownInitiator.Application, - Constants.ReplySuccess, - "Timed out waiting for acks", - new IOException("timed out waiting for acks")), - false).GetAwaiter().GetResult(); - throw new IOException("Timed out waiting for acks"); - } - } - - internal void SendCommands(IList commands) - { - _flowControlBlock.Wait(); - AllocatePublishSeqNos(commands.Count); - Session.Transmit(commands); - } - - private QueueDeclareOk QueueDeclare(string queue, bool passive, bool durable, bool exclusive, - bool autoDelete, IDictionary arguments) - { - var k = new QueueDeclareRpcContinuation(); - lock (_rpcLock) - { - Enqueue(k); - _Private_QueueDeclare(queue, passive, durable, exclusive, autoDelete, false, arguments); - k.GetReply(ContinuationTimeout); - } - return k.m_result; - } - - public class BasicConsumerRpcContinuation : SimpleBlockingRpcContinuation { public IBasicConsumer m_consumer; @@ -1475,7 +41,7 @@ public class BasicConsumerRpcContinuation : SimpleBlockingRpcContinuation public class BasicGetRpcContinuation : SimpleBlockingRpcContinuation { - public BasicGetResult m_result; + public SingleMessageRetrieval m_result; } public class ConnectionOpenContinuation : SimpleBlockingRpcContinuation diff --git a/projects/RabbitMQ.Client/client/impl/ModelShutdown.cs b/projects/RabbitMQ.Client/client/impl/ModelShutdown.cs index b38684b74a..ffe5e0d279 100644 --- a/projects/RabbitMQ.Client/client/impl/ModelShutdown.cs +++ b/projects/RabbitMQ.Client/client/impl/ModelShutdown.cs @@ -5,19 +5,19 @@ namespace RabbitMQ.Client.Impl internal sealed class ModelShutdown : Work { private readonly ShutdownEventArgs _reason; - private readonly IModel _model; + private readonly object _channel; public override string Context => "HandleModelShutdown"; - public ModelShutdown(IBasicConsumer consumer, ShutdownEventArgs reason, IModel model) : base(consumer) + public ModelShutdown(IBasicConsumer consumer, ShutdownEventArgs reason, object channel) : base(consumer) { _reason = reason; - _model = model; + _channel = channel; } protected override Task Execute(IAsyncBasicConsumer consumer) { - return consumer.HandleModelShutdown(_model, _reason); + return consumer.HandleModelShutdown(_channel, _reason); } } } diff --git a/projects/RabbitMQ.Client/client/impl/ProtocolBase.cs b/projects/RabbitMQ.Client/client/impl/ProtocolBase.cs index ddf4fccac3..d94a1fa178 100644 --- a/projects/RabbitMQ.Client/client/impl/ProtocolBase.cs +++ b/projects/RabbitMQ.Client/client/impl/ProtocolBase.cs @@ -32,6 +32,7 @@ using System; using System.Collections.Generic; using RabbitMQ.Client.client.framing; +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Impl; namespace RabbitMQ.Client.Framing.Impl @@ -101,20 +102,15 @@ public override string ToString() return Version.ToString(); } - public IConnection CreateConnection(IConnectionFactory factory, - bool insist, - IFrameHandler frameHandler) + public IConnection CreateConnection(IConnectionFactory factory, IFrameHandler frameHandler) { - return new Connection(factory, insist, frameHandler, null); + return new Connection(factory, frameHandler); } - public IConnection CreateConnection(IConnectionFactory factory, - bool insist, - IFrameHandler frameHandler, - string clientProvidedName) + public IConnection CreateConnection(IConnectionFactory factory, IFrameHandler frameHandler, string clientProvidedName) { - return new Connection(factory, insist, frameHandler, clientProvidedName); + return new Connection(factory, frameHandler, clientProvidedName); } public IConnection CreateConnection(ConnectionFactory factory, @@ -136,15 +132,9 @@ public IConnection CreateConnection(ConnectionFactory factory, return ac; } - - public IModel CreateModel(ISession session) - { - return new Model(session); - } - - public IModel CreateModel(ISession session, ConsumerWorkService workService) + public Channel CreateChannel(ISession session, ConsumerWorkService workService) { - return new Model(session, workService); + return new Channel(session, workService); } } } diff --git a/projects/RabbitMQ.Client/client/impl/RecordedBinding.cs b/projects/RabbitMQ.Client/client/impl/RecordedBinding.cs index 34704f3b73..144afc2464 100644 --- a/projects/RabbitMQ.Client/client/impl/RecordedBinding.cs +++ b/projects/RabbitMQ.Client/client/impl/RecordedBinding.cs @@ -29,33 +29,38 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using System; using System.Collections.Generic; +using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Impl { + #nullable enable internal abstract class RecordedBinding : RecordedEntity { - public RecordedBinding(AutorecoveringModel model) : base(model) + protected RecordedBinding(AutorecoveringChannel channel, string destination, string source, string routingKey, IDictionary? arguments) + : base(channel) { + Destination = destination; + Source = source; + RoutingKey = routingKey; + Arguments = arguments; } - public IDictionary Arguments { get; protected set; } + public IDictionary? Arguments { get; } public string Destination { get; set; } - public string RoutingKey { get; protected set; } - public string Source { get; protected set; } + public string RoutingKey { get; } + public string Source { get; } - public bool Equals(RecordedBinding other) + protected bool Equals(RecordedBinding other) { - return other != null && - Source.Equals(other.Source) && - Destination.Equals(other.Destination) && - RoutingKey.Equals(other.RoutingKey) && - (Arguments == other.Arguments); + return Equals(Arguments, other.Arguments) && Destination == other.Destination && RoutingKey == other.RoutingKey && Source == other.Source; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (obj is null) + if (ReferenceEquals(null, obj)) { return false; } @@ -65,76 +70,59 @@ public override bool Equals(object obj) return true; } - var other = obj as RecordedBinding; + if (obj.GetType() != GetType()) + { + return false; + } - return Equals(other); + return Equals((RecordedBinding) obj); } public override int GetHashCode() { - return Source.GetHashCode() ^ - Destination.GetHashCode() ^ - RoutingKey.GetHashCode() ^ - (Arguments != null ? Arguments.GetHashCode() : 0); - } - - public virtual void Recover() - { +#if NETSTANDARD + unchecked + { + int hashCode = (Arguments != null ? Arguments.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Destination.GetHashCode(); + hashCode = (hashCode * 397) ^ RoutingKey.GetHashCode(); + hashCode = (hashCode * 397) ^ Source.GetHashCode(); + return hashCode; + } +#else + return HashCode.Combine(Arguments, Destination, RoutingKey, Source); +#endif } public override string ToString() { return $"{GetType().Name}: source = '{Source}', destination = '{Destination}', routingKey = '{RoutingKey}', arguments = '{Arguments}'"; } - - public RecordedBinding WithArguments(IDictionary value) - { - Arguments = value; - return this; - } - - public RecordedBinding WithDestination(string value) - { - Destination = value; - return this; - } - - public RecordedBinding WithRoutingKey(string value) - { - RoutingKey = value; - return this; - } - - public RecordedBinding WithSource(string value) - { - Source = value; - return this; - } } - internal sealed class RecordedQueueBinding : RecordedBinding { - public RecordedQueueBinding(AutorecoveringModel model) : base(model) + public RecordedQueueBinding(AutorecoveringChannel channel, string destination, string source, string routingKey, IDictionary? arguments) + : base(channel, destination, source, routingKey, arguments) { } - public override void Recover() + public override ValueTask RecoverAsync() { - ModelDelegate.QueueBind(Destination, Source, RoutingKey, Arguments); + return Channel.BindQueueAsync(Destination, Source, RoutingKey, Arguments); } } - internal sealed class RecordedExchangeBinding : RecordedBinding { - public RecordedExchangeBinding(AutorecoveringModel model) : base(model) + public RecordedExchangeBinding(AutorecoveringChannel channel, string destination, string source, string routingKey, IDictionary? arguments) + : base(channel, destination, source, routingKey, arguments) { } - public override void Recover() + public override ValueTask RecoverAsync() { - ModelDelegate.ExchangeBind(Destination, Source, RoutingKey, Arguments); + return Channel.BindExchangeAsync(Destination, Source, RoutingKey, Arguments); } } } diff --git a/projects/RabbitMQ.Client/client/impl/RecordedConsumer.cs b/projects/RabbitMQ.Client/client/impl/RecordedConsumer.cs index 27c8b0c6f9..f67e4977f2 100644 --- a/projects/RabbitMQ.Client/client/impl/RecordedConsumer.cs +++ b/projects/RabbitMQ.Client/client/impl/RecordedConsumer.cs @@ -30,66 +30,35 @@ //--------------------------------------------------------------------------- using System.Collections.Generic; +using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Impl { - internal class RecordedConsumer : RecordedEntity + #nullable enable + internal sealed class RecordedConsumer : RecordedEntity { - public RecordedConsumer(AutorecoveringModel model, string queue) : base(model) + public RecordedConsumer(AutorecoveringChannel channel, IBasicConsumer consumer, string queue, bool autoAck, string consumerTag, bool exclusive, IDictionary? arguments) + : base(channel) { + Consumer = consumer; Queue = queue; + AutoAck = autoAck; + ConsumerTag = consumerTag; + Exclusive = exclusive; + Arguments = arguments; } - public IDictionary Arguments { get; set; } - public bool AutoAck { get; set; } public IBasicConsumer Consumer { get; set; } + public string Queue { get; set; } + public bool AutoAck { get; set; } public string ConsumerTag { get; set; } public bool Exclusive { get; set; } - public string Queue { get; set; } - - public string Recover() - { - ConsumerTag = ModelDelegate.BasicConsume(Queue, AutoAck, - ConsumerTag, false, Exclusive, - Arguments, Consumer); - - return ConsumerTag; - } - - public RecordedConsumer WithArguments(IDictionary value) - { - Arguments = value; - return this; - } - - public RecordedConsumer WithAutoAck(bool value) - { - AutoAck = value; - return this; - } - - public RecordedConsumer WithConsumer(IBasicConsumer value) - { - Consumer = value; - return this; - } - - public RecordedConsumer WithConsumerTag(string value) - { - ConsumerTag = value; - return this; - } - - public RecordedConsumer WithExclusive(bool value) - { - Exclusive = value; - return this; - } + public IDictionary? Arguments { get; set; } - public RecordedConsumer WithQueue(string value) + public override async ValueTask RecoverAsync() { - Queue = value; - return this; + ConsumerTag = await Channel.ActivateConsumerAsync(Consumer, Queue, AutoAck, ConsumerTag, false, Exclusive, Arguments).ConfigureAwait(false); } } } diff --git a/projects/RabbitMQ.Client/client/impl/RecordedEntity.cs b/projects/RabbitMQ.Client/client/impl/RecordedEntity.cs index ae466f25bd..bbcc6fc049 100644 --- a/projects/RabbitMQ.Client/client/impl/RecordedEntity.cs +++ b/projects/RabbitMQ.Client/client/impl/RecordedEntity.cs @@ -29,20 +29,23 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; + namespace RabbitMQ.Client.Impl { + #nullable enable internal abstract class RecordedEntity { - public RecordedEntity(AutorecoveringModel model) - { - Model = model; - } + private readonly AutorecoveringChannel _channel; - public AutorecoveringModel Model { get; protected set; } + protected RecoveryAwareChannel Channel => _channel.NonDisposedDelegate; - protected IModel ModelDelegate + protected RecordedEntity(AutorecoveringChannel channel) { - get { return Model.Delegate; } + _channel = channel; } + + public abstract ValueTask RecoverAsync(); } } diff --git a/projects/RabbitMQ.Client/client/impl/RecordedExchange.cs b/projects/RabbitMQ.Client/client/impl/RecordedExchange.cs index 5c00650b7a..0160070e71 100644 --- a/projects/RabbitMQ.Client/client/impl/RecordedExchange.cs +++ b/projects/RabbitMQ.Client/client/impl/RecordedExchange.cs @@ -30,52 +30,37 @@ //--------------------------------------------------------------------------- using System.Collections.Generic; +using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Impl { - internal class RecordedExchange : RecordedNamedEntity + #nullable enable + internal sealed class RecordedExchange : RecordedNamedEntity { - public RecordedExchange(AutorecoveringModel model, string name) : base(model, name) - { - } + private readonly IDictionary? _arguments; + private readonly bool _durable; + private readonly string _type; - public IDictionary Arguments { get; private set; } - public bool Durable { get; private set; } - public bool IsAutoDelete { get; private set; } - public string Type { get; private set; } + public bool IsAutoDelete { get; } - public void Recover() + public RecordedExchange(AutorecoveringChannel channel, string name, string type, bool durable, bool isAutoDelete, IDictionary? arguments) + : base(channel, name) { - ModelDelegate.ExchangeDeclare(Name, Type, Durable, IsAutoDelete, Arguments); + _type = type; + _durable = durable; + IsAutoDelete = isAutoDelete; + _arguments = arguments; } - public override string ToString() + public override ValueTask RecoverAsync() { - return $"{GetType().Name}: name = '{Name}', type = '{Type}', durable = {Durable}, autoDelete = {IsAutoDelete}, arguments = '{Arguments}'"; + return Channel.DeclareExchangeAsync(Name, _type, _durable, IsAutoDelete, true, _arguments); } - public RecordedExchange WithArguments(IDictionary value) - { - Arguments = value; - return this; - } - - public RecordedExchange WithAutoDelete(bool value) - { - IsAutoDelete = value; - return this; - } - - public RecordedExchange WithDurable(bool value) - { - Durable = value; - return this; - } - - public RecordedExchange WithType(string value) + public override string ToString() { - Type = value; - return this; + return $"{GetType().Name}: name = '{Name}', type = '{_type}', durable = {_durable}, autoDelete = {IsAutoDelete}, arguments = '{_arguments}'"; } } } diff --git a/projects/RabbitMQ.Client/client/impl/RecordedNamedEntity.cs b/projects/RabbitMQ.Client/client/impl/RecordedNamedEntity.cs index 0ab048d5ee..e23a2e2dea 100644 --- a/projects/RabbitMQ.Client/client/impl/RecordedNamedEntity.cs +++ b/projects/RabbitMQ.Client/client/impl/RecordedNamedEntity.cs @@ -29,11 +29,15 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using RabbitMQ.Client.client.impl.Channel; + namespace RabbitMQ.Client.Impl { - internal class RecordedNamedEntity : RecordedEntity + #nullable enable + internal abstract class RecordedNamedEntity : RecordedEntity { - public RecordedNamedEntity(AutorecoveringModel model, string name) : base(model) + protected RecordedNamedEntity(AutorecoveringChannel channel, string name) + : base(channel) { Name = name; } diff --git a/projects/RabbitMQ.Client/client/impl/RecordedQueue.cs b/projects/RabbitMQ.Client/client/impl/RecordedQueue.cs index c59ee27451..006f91ff60 100644 --- a/projects/RabbitMQ.Client/client/impl/RecordedQueue.cs +++ b/projects/RabbitMQ.Client/client/impl/RecordedQueue.cs @@ -30,73 +30,36 @@ //--------------------------------------------------------------------------- using System.Collections.Generic; +using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Impl { - internal class RecordedQueue : RecordedNamedEntity + #nullable enable + internal sealed class RecordedQueue : RecordedNamedEntity { - private IDictionary _arguments; - private bool _durable; - private bool _exclusive; + private readonly IDictionary? _arguments; + private readonly bool _durable; + private readonly bool _exclusive; - public RecordedQueue(AutorecoveringModel model, string name) : base(model, name) - { - } - - public bool IsAutoDelete { get; private set; } - public bool IsServerNamed { get; private set; } - - protected string NameToUseForRecovery - { - get - { - if (IsServerNamed) - { - return string.Empty; - } - else - { - return Name; - } - } - } - - public RecordedQueue Arguments(IDictionary value) - { - _arguments = value; - return this; - } - - public RecordedQueue AutoDelete(bool value) - { - IsAutoDelete = value; - return this; - } - - public RecordedQueue Durable(bool value) - { - _durable = value; - return this; - } - - public RecordedQueue Exclusive(bool value) - { - _exclusive = value; - return this; - } + public bool IsAutoDelete { get; } + public bool IsServerNamed { get; } - public void Recover() + public RecordedQueue(AutorecoveringChannel channel, string name, bool isServerNamed, bool durable, bool exclusive, bool autoDelete, IDictionary? arguments) + : base(channel, name) { - QueueDeclareOk ok = ModelDelegate.QueueDeclare(NameToUseForRecovery, _durable, - _exclusive, IsAutoDelete, - _arguments); - Name = ok.QueueName; + _arguments = arguments; + _durable = durable; + _exclusive = exclusive; + IsAutoDelete = autoDelete; + IsServerNamed = isServerNamed; } - public RecordedQueue ServerNamed(bool value) + public override async ValueTask RecoverAsync() { - IsServerNamed = value; - return this; + var queueName = IsServerNamed ? string.Empty : Name; + var tuple = await Channel.DeclareQueueAsync(queueName, _durable, _exclusive, IsAutoDelete, true, _arguments).ConfigureAwait(false); + Name = tuple.QueueName; } public override string ToString() diff --git a/projects/RabbitMQ.Client/client/impl/RecoveryAwareModel.cs b/projects/RabbitMQ.Client/client/impl/RecoveryAwareModel.cs deleted file mode 100644 index 3bedeb5658..0000000000 --- a/projects/RabbitMQ.Client/client/impl/RecoveryAwareModel.cs +++ /dev/null @@ -1,132 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System; -using RabbitMQ.Client.Framing.Impl; - -namespace RabbitMQ.Client.Impl -{ - internal class RecoveryAwareModel : Model, IFullModel, IRecoverable - { - public RecoveryAwareModel(ISession session) : base(session) - { - ActiveDeliveryTagOffset = 0; - MaxSeenDeliveryTag = 0; - } - - public ulong ActiveDeliveryTagOffset { get; private set; } - public ulong MaxSeenDeliveryTag { get; private set; } - - public void InheritOffsetFrom(RecoveryAwareModel other) - { - ActiveDeliveryTagOffset = other.ActiveDeliveryTagOffset + other.MaxSeenDeliveryTag; - MaxSeenDeliveryTag = 0; - } - - public override void HandleBasicGetOk(ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - uint messageCount, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - if (deliveryTag > MaxSeenDeliveryTag) - { - MaxSeenDeliveryTag = deliveryTag; - } - - base.HandleBasicGetOk(OffsetDeliveryTag(deliveryTag), redelivered, exchange, - routingKey, messageCount, basicProperties, body, rentedArray); - } - - public override void HandleBasicDeliver(string consumerTag, - ulong deliveryTag, - bool redelivered, - string exchange, - string routingKey, - IBasicProperties basicProperties, - ReadOnlyMemory body, - byte[] rentedArray) - { - if (deliveryTag > MaxSeenDeliveryTag) - { - MaxSeenDeliveryTag = deliveryTag; - } - - base.HandleBasicDeliver(consumerTag, - OffsetDeliveryTag(deliveryTag), - redelivered, - exchange, - routingKey, - basicProperties, - body, - rentedArray); - } - - public override void BasicAck(ulong deliveryTag, - bool multiple) - { - ulong realTag = deliveryTag - ActiveDeliveryTagOffset; - if (realTag > 0 && realTag <= deliveryTag) - { - base.BasicAck(realTag, multiple); - } - } - - public override void BasicNack(ulong deliveryTag, - bool multiple, - bool requeue) - { - ulong realTag = deliveryTag - ActiveDeliveryTagOffset; - if (realTag > 0 && realTag <= deliveryTag) - { - base.BasicNack(realTag, multiple, requeue); - } - } - - public override void BasicReject(ulong deliveryTag, - bool requeue) - { - ulong realTag = deliveryTag - ActiveDeliveryTagOffset; - if (realTag > 0 && realTag <= deliveryTag) - { - base.BasicReject(realTag, requeue); - } - } - - protected ulong OffsetDeliveryTag(ulong deliveryTag) - { - return deliveryTag + ActiveDeliveryTagOffset; - } - } -} diff --git a/projects/RabbitMQ.Client/client/impl/RpcContinuationQueue.cs b/projects/RabbitMQ.Client/client/impl/RpcContinuationQueue.cs index 6c8e2ee194..54f0312740 100644 --- a/projects/RabbitMQ.Client/client/impl/RpcContinuationQueue.cs +++ b/projects/RabbitMQ.Client/client/impl/RpcContinuationQueue.cs @@ -73,7 +73,7 @@ public void HandleModelShutdown(ShutdownEventArgs reason) /// public void Enqueue(IRpcContinuation k) { - IRpcContinuation result = Interlocked.CompareExchange(ref _outstandingRpc, k, s_tmp); + IRpcContinuation result = System.Threading.Interlocked.CompareExchange(ref _outstandingRpc, k, s_tmp); if (!(result is EmptyRpcContinuation)) { throw new NotSupportedException("Pipelining of requests forbidden"); @@ -106,7 +106,7 @@ public void HandleModelShutdown(ShutdownEventArgs reason) /// public IRpcContinuation Next() { - return Interlocked.Exchange(ref _outstandingRpc, s_tmp); + return System.Threading.Interlocked.Exchange(ref _outstandingRpc, s_tmp); } } } diff --git a/projects/RabbitMQ.Client/client/impl/ShutdownContinuation.cs b/projects/RabbitMQ.Client/client/impl/ShutdownContinuation.cs index ee77c878f1..fe361c54b1 100644 --- a/projects/RabbitMQ.Client/client/impl/ShutdownContinuation.cs +++ b/projects/RabbitMQ.Client/client/impl/ShutdownContinuation.cs @@ -39,27 +39,7 @@ internal class ShutdownContinuation { public readonly BlockingCell m_cell = new BlockingCell(); - // You will note there are two practically identical overloads - // of OnShutdown() here. This is because Microsoft's C# - // compilers do not consistently support the Liskov - // substitutability principle. When I use - // OnShutdown(object,ShutdownEventArgs), the compilers - // complain that OnShutdown can't be placed into a - // ConnectionShutdownEventHandler because object doesn't - // "match" IConnection, even though there's no context in - // which the program could Go Wrong were it to accept the - // code. The same problem appears for - // ModelShutdownEventHandler. The .NET 1.1 compiler complains - // about these two cases, and the .NET 2.0 compiler does not - - // presumably they improved the type checker with the new - // release of the compiler. - - public virtual void OnConnectionShutdown(object sender, ShutdownEventArgs reason) - { - m_cell.ContinueWithValue(reason); - } - - public virtual void OnModelShutdown(IModel sender, ShutdownEventArgs reason) + public void OnConnectionShutdown(ShutdownEventArgs reason) { m_cell.ContinueWithValue(reason); } diff --git a/projects/RabbitMQ.Client/util/SetQueue.cs b/projects/RabbitMQ.Client/util/SetQueue.cs deleted file mode 100644 index 1da9e1713b..0000000000 --- a/projects/RabbitMQ.Client/util/SetQueue.cs +++ /dev/null @@ -1,86 +0,0 @@ -// This source code is dual-licensed under the Apache License, version -// 2.0, and the Mozilla Public License, version 2.0. -// -// The APL v2.0: -// -//--------------------------------------------------------------------------- -// Copyright (c) 2007-2020 VMware, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------- -// -// The MPL v2.0: -// -//--------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2007-2020 VMware, Inc. All rights reserved. -//--------------------------------------------------------------------------- - -using System.Collections.Generic; - -namespace RabbitMQ.Util -{ - internal class SetQueue - { - private readonly HashSet _members = new HashSet(); - private readonly LinkedList _queue = new LinkedList(); - - public bool Enqueue(T item) - { - if (_members.Contains(item)) - { - return false; - } - _members.Add(item); - _queue.AddLast(item); - return true; - } - - public T Dequeue() - { - if (_queue.Count == 0) - { - return default; - } - T item = _queue.First.Value; - _queue.RemoveFirst(); - _members.Remove(item); - return item; - } - - public bool Contains(T item) - { - return _members.Contains(item); - } - - public bool IsEmpty() - { - return _members.Count == 0; - } - - public bool Remove(T item) - { - _queue.Remove(item); - return _members.Remove(item); - } - - public void Clear() - { - _queue.Clear(); - _members.Clear(); - } - } -} diff --git a/projects/RabbitMQ.Client/util/DebugUtil.cs b/projects/Unit/DebugUtil.cs similarity index 97% rename from projects/RabbitMQ.Client/util/DebugUtil.cs rename to projects/Unit/DebugUtil.cs index 125e0f2684..c9506501e0 100644 --- a/projects/RabbitMQ.Client/util/DebugUtil.cs +++ b/projects/Unit/DebugUtil.cs @@ -34,7 +34,7 @@ using System.IO; using System.Reflection; -namespace RabbitMQ.Util +namespace Unit { ///Miscellaneous debugging and development utilities. /// @@ -51,18 +51,18 @@ public static void Dump(byte[] bytes) ///Print a hex dump of the supplied bytes to the supplied TextWriter. public static void Dump(byte[] bytes, TextWriter writer) { - int rowlen = 16; + const int rowLength = 16; - for (int count = 0; count < bytes.Length; count += rowlen) + for (int count = 0; count < bytes.Length; count += rowLength) { - int thisRow = Math.Min(bytes.Length - count, rowlen); + int thisRow = Math.Min(bytes.Length - count, rowLength); writer.Write("{0:X8}: ", count); for (int i = 0; i < thisRow; i++) { writer.Write("{0:X2}", bytes[count + i]); } - for (int i = 0; i < (rowlen - thisRow); i++) + for (int i = 0; i < (rowLength - thisRow); i++) { writer.Write(" "); } diff --git a/projects/Unit/Fixtures.cs b/projects/Unit/Fixtures.cs index 2c3944505c..1982b3cfd6 100644 --- a/projects/Unit/Fixtures.cs +++ b/projects/Unit/Fixtures.cs @@ -35,37 +35,36 @@ using System.Collections.Generic; using System.Text; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Framing.Impl; using static RabbitMQ.Client.Unit.RabbitMQCtl; namespace RabbitMQ.Client.Unit { - public class IntegrationFixture { - internal IConnectionFactory _connFactory; + internal ConnectionFactory _connFactory; internal IConnection _conn; - internal IModel _model; + internal IChannel _channel; internal Encoding _encoding = new UTF8Encoding(); public static TimeSpan RECOVERY_INTERVAL = TimeSpan.FromSeconds(2); [SetUp] - public virtual void Init() + public virtual async Task Init() { _connFactory = new ConnectionFactory(); _conn = _connFactory.CreateConnection(); - _model = _conn.CreateModel(); + _channel = await _conn.CreateChannelAsync().ConfigureAwait(false); } [TearDown] - public void Dispose() + public async Task Dispose() { - if(_model.IsOpen) + if(_channel != null && _channel.IsOpen) { - _model.Close(); + await _channel.CloseAsync().ConfigureAwait(false); } if(_conn.IsOpen) { @@ -141,16 +140,6 @@ internal AutorecoveringConnection CreateAutorecoveringConnectionWithTopologyReco return (AutorecoveringConnection)cf.CreateConnection($"UNIT_CONN:{Guid.NewGuid()}"); } - internal IConnection CreateNonRecoveringConnection() - { - var cf = new ConnectionFactory - { - AutomaticRecoveryEnabled = false, - TopologyRecoveryEnabled = false - }; - return cf.CreateConnection($"UNIT_CONN:{Guid.NewGuid()}"); - } - internal IConnection CreateConnectionWithContinuationTimeout(bool automaticRecoveryEnabled, TimeSpan continuationTimeout) { var cf = new ConnectionFactory @@ -165,63 +154,31 @@ internal IConnection CreateConnectionWithContinuationTimeout(bool automaticRecov // Channels // - internal void WithTemporaryAutorecoveringConnection(Action action) + internal async Task WithTemporaryChannelAsync(Func action) { - var factory = new ConnectionFactory - { - AutomaticRecoveryEnabled = true - }; + IChannel channel = await _conn.CreateChannelAsync().ConfigureAwait(false); - var connection = (AutorecoveringConnection)factory.CreateConnection($"UNIT_CONN:{Guid.NewGuid()}"); try { - action(connection); + await action(channel).ConfigureAwait(false); } finally { - connection.Abort(); + await channel.AbortAsync().ConfigureAwait(false); } } - internal void WithTemporaryModel(IConnection connection, Action action) + internal async Task WithClosedChannelAsync(Action action) { - IModel model = connection.CreateModel(); + IChannel channel = await _conn.CreateChannelAsync().ConfigureAwait(false); + await channel.CloseAsync().ConfigureAwait(false); - try - { - action(model); - } - finally - { - model.Abort(); - } + action(channel); } - internal void WithTemporaryModel(Action action) + internal bool WaitForConfirms(IChannel ch) { - IModel model = _conn.CreateModel(); - - try - { - action(model); - } - finally - { - model.Abort(); - } - } - - internal void WithClosedModel(Action action) - { - IModel model = _conn.CreateModel(); - model.Close(); - - action(model); - } - - internal bool WaitForConfirms(IModel m) - { - return m.WaitForConfirms(TimeSpan.FromSeconds(4)); + return ch.WaitForConfirms(TimeSpan.FromSeconds(4)); } // @@ -238,18 +195,6 @@ internal byte[] RandomMessageBody() return _encoding.GetBytes(Guid.NewGuid().ToString()); } - internal string DeclareNonDurableExchange(IModel m, string x) - { - m.ExchangeDeclare(x, "fanout", false); - return x; - } - - internal string DeclareNonDurableExchangeNoWait(IModel m, string x) - { - m.ExchangeDeclareNoWait(x, "fanout", false, false, null); - return x; - } - // // Queues // @@ -259,120 +204,85 @@ internal string GenerateQueueName() return $"queue{Guid.NewGuid()}"; } - internal void WithTemporaryQueue(Action action) - { - WithTemporaryQueue(_model, action); - } - - internal void WithTemporaryNonExclusiveQueue(Action action) - { - WithTemporaryNonExclusiveQueue(_model, action); - } - - internal void WithTemporaryQueue(IModel model, Action action) + internal Task WithTemporaryNonExclusiveQueueAsync(Func action) { - WithTemporaryQueue(model, action, GenerateQueueName()); + return WithTemporaryNonExclusiveQueueAsync(_channel, action); } - internal void WithTemporaryNonExclusiveQueue(IModel model, Action action) + internal Task WithTemporaryNonExclusiveQueueAsync(IChannel channel, Func action) { - WithTemporaryNonExclusiveQueue(model, action, GenerateQueueName()); + return WithTemporaryNonExclusiveQueueAsync(channel, action, GenerateQueueName()); } - internal void WithTemporaryQueue(Action action, string q) - { - WithTemporaryQueue(_model, action, q); - } - - internal void WithTemporaryQueue(IModel model, Action action, string queue) + internal async Task WithTemporaryNonExclusiveQueueAsync(IChannel channel, Func action, string queue) { try { - model.QueueDeclare(queue, false, true, false, null); - action(model, queue); + await channel.DeclareQueueAsync(queue, false, false, false).ConfigureAwait(false); + await action(channel, queue).ConfigureAwait(false); } finally { - WithTemporaryModel(x => x.QueueDelete(queue)); + await WithTemporaryChannelAsync(ch => ch.DeleteQueueAsync(queue).AsTask()).ConfigureAwait(false); } } - internal void WithTemporaryNonExclusiveQueue(IModel model, Action action, string queue) + internal async Task WithTemporaryQueueNoWaitAsync(IChannel channel, Func action, string queue) { try { - model.QueueDeclare(queue, false, false, false, null); - action(model, queue); + await channel.DeclareQueueWithoutConfirmationAsync(queue, false, true, false).ConfigureAwait(false); + await action(channel, queue).ConfigureAwait(false); } finally { - WithTemporaryModel(tm => tm.QueueDelete(queue)); + await WithTemporaryChannelAsync(x => x.DeleteQueueAsync(queue).AsTask()).ConfigureAwait(false); } } - internal void WithTemporaryQueueNoWait(IModel model, Action action, string queue) + internal Task EnsureNotEmpty(string q, string body) { - try - { - model.QueueDeclareNoWait(queue, false, true, false, null); - action(model, queue); - } finally - { - WithTemporaryModel(x => x.QueueDelete(queue)); - } + return WithTemporaryChannelAsync(x => x.PublishMessageAsync("", q, null, _encoding.GetBytes(body)).AsTask()); } - internal void EnsureNotEmpty(string q) + internal Task WithNonEmptyQueueAsync(Func action) { - EnsureNotEmpty(q, "msg"); + return WithNonEmptyQueueAsync(action, "msg"); } - internal void EnsureNotEmpty(string q, string body) + internal Task WithNonEmptyQueueAsync(Func action, string msg) { - WithTemporaryModel(x => x.BasicPublish("", q, null, _encoding.GetBytes(body))); - } - - internal void WithNonEmptyQueue(Action action) - { - WithNonEmptyQueue(action, "msg"); - } - - internal void WithNonEmptyQueue(Action action, string msg) - { - WithTemporaryNonExclusiveQueue((m, q) => + return WithTemporaryNonExclusiveQueueAsync((ch, q) => { EnsureNotEmpty(q, msg); - action(m, q); + return action(ch, q); }); } - internal void WithEmptyQueue(Action action) + internal Task WithEmptyQueueAsync(Func action) { - WithTemporaryNonExclusiveQueue((model, queue) => + return WithTemporaryNonExclusiveQueueAsync(async (channel, queue) => { - model.QueuePurge(queue); - action(model, queue); + await channel.PurgeQueueAsync(queue).ConfigureAwait(false); + await action(channel, queue).ConfigureAwait(false); }); } - internal void AssertMessageCount(string q, int count) + internal Task AssertMessageCountAsync(string q, int count) { - WithTemporaryModel((m) => { - QueueDeclareOk ok = m.QueueDeclarePassive(q); - Assert.AreEqual(count, ok.MessageCount); + return WithTemporaryChannelAsync(async ch => { + uint messageCount = await ch.GetQueueMessageCountAsync(q).ConfigureAwait(false); + Assert.AreEqual(count, messageCount); }); } - internal void AssertConsumerCount(string q, int count) + internal Task AssertConsumerCountAsync(string q, int count) { - WithTemporaryModel((m) => { - QueueDeclareOk ok = m.QueueDeclarePassive(q); - Assert.AreEqual(count, ok.ConsumerCount); - }); + return WithTemporaryChannelAsync(ch => AssertConsumerCountAsync(ch, q, count)); } - internal void AssertConsumerCount(IModel m, string q, int count) + internal async Task AssertConsumerCountAsync(IChannel ch, string q, int count) { - QueueDeclareOk ok = m.QueueDeclarePassive(q); - Assert.AreEqual(count, ok.ConsumerCount); + uint consumerCount = await ch.GetQueueConsumerCountAsync(q).ConfigureAwait(false); + Assert.AreEqual(count, consumerCount); } // @@ -391,28 +301,16 @@ internal void AssertPreconditionFailed(ShutdownEventArgs args) internal bool InitiatedByPeerOrLibrary(ShutdownEventArgs evt) { - return !(evt.Initiator == ShutdownInitiator.Application); - } - - // - // Concurrency - // - - internal void WaitOn(object o) - { - lock(o) - { - Monitor.Wait(o, TimingFixture.TestTimeout); - } + return evt.Initiator != ShutdownInitiator.Application; } // // Flow Control // - internal void Block() + internal Task BlockAsync() { - RabbitMQCtl.Block(_conn, _encoding); + return RabbitMQCtl.BlockAsync(_conn, _encoding); } internal void Unblock() @@ -420,11 +318,6 @@ internal void Unblock() RabbitMQCtl.Unblock(); } - internal void Publish(IConnection conn) - { - RabbitMQCtl.Publish(conn, _encoding); - } - // // Connection Closure // diff --git a/projects/Unit/RabbitMQCtl.cs b/projects/Unit/RabbitMQCtl.cs index 262fef29b9..19a7f11d97 100644 --- a/projects/Unit/RabbitMQCtl.cs +++ b/projects/Unit/RabbitMQCtl.cs @@ -39,6 +39,8 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { @@ -213,18 +215,18 @@ public static bool IsRunningOnMonoOrDotNetCore() // Flow Control // - public static void Block(IConnection conn, Encoding encoding) + public static Task BlockAsync(IConnection conn, Encoding encoding) { ExecRabbitMQCtl("set_vm_memory_high_watermark 0.000000001"); // give rabbitmqctl some time to do its job Thread.Sleep(1200); - Publish(conn, encoding); + return PublishAsync(conn, encoding); } - public static void Publish(IConnection conn, Encoding encoding) + public static async Task PublishAsync(IConnection conn, Encoding encoding) { - IModel ch = conn.CreateModel(); - ch.BasicPublish("amq.fanout", "", null, encoding.GetBytes("message")); + await using IChannel ch = await conn.CreateChannelAsync().ConfigureAwait(false); + await ch.PublishMessageAsync("amq.fanout", "", null, encoding.GetBytes("message")).ConfigureAwait(false); } diff --git a/projects/Unit/TestAsyncConsumer.cs b/projects/Unit/TestAsyncConsumer.cs index 38ebbe4c23..aef122e9c8 100644 --- a/projects/Unit/TestAsyncConsumer.cs +++ b/projects/Unit/TestAsyncConsumer.cs @@ -35,8 +35,9 @@ using System.Threading.Tasks; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; +using RabbitMQ.Client.Framing; namespace RabbitMQ.Client.Unit { @@ -44,30 +45,30 @@ namespace RabbitMQ.Client.Unit public class TestAsyncConsumer { [Test] - public void TestBasicRoundtrip() + public async Task TestBasicRoundtrip() { var cf = new ConnectionFactory{ DispatchConsumersAsync = true }; using(IConnection c = cf.CreateConnection()) - using(IModel m = c.CreateModel()) + await using(IChannel ch = await c.CreateChannelAsync().ConfigureAwait(false)) { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); - byte[] body = System.Text.Encoding.UTF8.GetBytes("async-hi"); - m.BasicPublish("", q.QueueName, bp, body); - var consumer = new AsyncEventingBasicConsumer(m); + (string q, _, _) = await ch.DeclareQueueAsync().ConfigureAwait(false); + BasicProperties bp = new BasicProperties(); + byte[] body = Encoding.UTF8.GetBytes("async-hi"); + await ch.PublishMessageAsync("", q, bp, body).ConfigureAwait(false); + var consumer = new AsyncEventingBasicConsumer(ch); var are = new AutoResetEvent(false); consumer.Received += async (o, a) => { are.Set(); await Task.Yield(); }; - string tag = m.BasicConsume(q.QueueName, true, consumer); + string tag = await ch.ActivateConsumerAsync(consumer, q, true).ConfigureAwait(false); // ensure we get a delivery bool waitRes = are.WaitOne(2000); Assert.IsTrue(waitRes); // unsubscribe and ensure no further deliveries - m.BasicCancel(tag); - m.BasicPublish("", q.QueueName, bp, body); + await ch.CancelConsumerAsync(tag).ConfigureAwait(false); + await ch.PublishMessageAsync("", q, bp, body).ConfigureAwait(false); bool waitResFalse = are.WaitOne(2000); Assert.IsFalse(waitResFalse); } @@ -78,18 +79,18 @@ public async Task TestBasicRoundtripConcurrent() { var cf = new ConnectionFactory{ DispatchConsumersAsync = true, ConsumerDispatchConcurrency = 2 }; using(IConnection c = cf.CreateConnection()) - using(IModel m = c.CreateModel()) + await using(IChannel ch = await c.CreateChannelAsync().ConfigureAwait(false)) { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); + (string q, _, _) = await ch.DeclareQueueAsync().ConfigureAwait(false); + BasicProperties bp = new BasicProperties(); const string publish1 = "async-hi-1"; byte[] body = Encoding.UTF8.GetBytes(publish1); - m.BasicPublish("", q.QueueName, bp, body); + await ch.PublishMessageAsync("", q, bp, body).ConfigureAwait(false); const string publish2 = "async-hi-2"; body = Encoding.UTF8.GetBytes(publish2); - m.BasicPublish("", q.QueueName, bp, body); + await ch.PublishMessageAsync("", q, bp, body).ConfigureAwait(false); - var consumer = new AsyncEventingBasicConsumer(m); + var consumer = new AsyncEventingBasicConsumer(ch); var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -116,7 +117,7 @@ public async Task TestBasicRoundtripConcurrent() } }; - m.BasicConsume(q.QueueName, true, consumer); + await ch.ActivateConsumerAsync(consumer, q, true).ConfigureAwait(false); // ensure we get a delivery await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); @@ -127,50 +128,48 @@ public async Task TestBasicRoundtripConcurrent() } [Test] - public void TestBasicRoundtripNoWait() + public async Task TestBasicRoundtripNoWait() { var cf = new ConnectionFactory{ DispatchConsumersAsync = true }; using (IConnection c = cf.CreateConnection()) + await using (IChannel ch = await c.CreateChannelAsync().ConfigureAwait(false)) { - using (IModel m = c.CreateModel()) - { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); - byte[] body = System.Text.Encoding.UTF8.GetBytes("async-hi"); - m.BasicPublish("", q.QueueName, bp, body); - var consumer = new AsyncEventingBasicConsumer(m); - var are = new AutoResetEvent(false); - consumer.Received += async (o, a) => - { - are.Set(); - await Task.Yield(); - }; - string tag = m.BasicConsume(q.QueueName, true, consumer); - // ensure we get a delivery - bool waitRes = are.WaitOne(2000); - Assert.IsTrue(waitRes); - // unsubscribe and ensure no further deliveries - m.BasicCancelNoWait(tag); - m.BasicPublish("", q.QueueName, bp, body); - bool waitResFalse = are.WaitOne(2000); - Assert.IsFalse(waitResFalse); - } + (string q, _, _) = await ch.DeclareQueueAsync().ConfigureAwait(false); + BasicProperties bp = new BasicProperties(); + byte[] body = Encoding.UTF8.GetBytes("async-hi"); + await ch.PublishMessageAsync("", q, bp, body).ConfigureAwait(false); + var consumer = new AsyncEventingBasicConsumer(ch); + var are = new AutoResetEvent(false); + consumer.Received += async (o, a) => + { + are.Set(); + await Task.Yield(); + }; + string tag = await ch.ActivateConsumerAsync(consumer, q, true).ConfigureAwait(false); + // ensure we get a delivery + bool waitRes = are.WaitOne(2000); + Assert.IsTrue(waitRes); + // unsubscribe and ensure no further deliveries + await ch.CancelConsumerAsync(tag, false).ConfigureAwait(false); + await ch.PublishMessageAsync("", q, bp, body).ConfigureAwait(false); + bool waitResFalse = are.WaitOne(2000); + Assert.IsFalse(waitResFalse); } } [Test] - public void NonAsyncConsumerShouldThrowInvalidOperationException() + public async Task NonAsyncConsumerShouldThrowInvalidOperationException() { var cf = new ConnectionFactory{ DispatchConsumersAsync = true }; using(IConnection c = cf.CreateConnection()) - using(IModel m = c.CreateModel()) + await using (IChannel ch = await c.CreateChannelAsync().ConfigureAwait(false)) { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); - byte[] body = System.Text.Encoding.UTF8.GetBytes("async-hi"); - m.BasicPublish("", q.QueueName, bp, body); - var consumer = new EventingBasicConsumer(m); - Assert.Throws(() => m.BasicConsume(q.QueueName, false, consumer)); + (string q, _, _) = await ch.DeclareQueueAsync().ConfigureAwait(false); + BasicProperties bp = new BasicProperties(); + byte[] body = Encoding.UTF8.GetBytes("async-hi"); + await ch.PublishMessageAsync("", q, bp, body).ConfigureAwait(false); + var consumer = new EventingBasicConsumer(ch); + Assert.ThrowsAsync(() => ch.ActivateConsumerAsync(consumer, q, false).AsTask()); } } } diff --git a/projects/Unit/TestAsyncConsumerExceptions.cs b/projects/Unit/TestAsyncConsumerExceptions.cs index 535ba4e73e..4cb9565021 100644 --- a/projects/Unit/TestAsyncConsumerExceptions.cs +++ b/projects/Unit/TestAsyncConsumerExceptions.cs @@ -33,6 +33,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client.Unit @@ -40,32 +41,34 @@ namespace RabbitMQ.Client.Unit [TestFixture] public class TestAsyncConsumerExceptions : IntegrationFixture { - private static Exception TestException = new Exception("oops"); + private static readonly Exception TestException = new Exception("oops"); - protected void TestExceptionHandlingWith(IBasicConsumer consumer, - Action action) + protected async Task TestExceptionHandlingWith(IBasicConsumer consumer, Func action) { var resetEvent = new AutoResetEvent(false); bool notified = false; - string q = _model.QueueDeclare(); + (string q, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); - _model.CallbackException += (m, evt) => + _channel.UnhandledExceptionOccurred += (ex, _) => { - if (evt.Exception != TestException) return; + if (ex != TestException) + { + return; + } notified = true; resetEvent.Set(); }; - string tag = _model.BasicConsume(q, true, consumer); - action(_model, q, consumer, tag); + string tag = await _channel.ActivateConsumerAsync(consumer, q, true); + await action(_channel, q, consumer, tag).ConfigureAwait(false); resetEvent.WaitOne(); Assert.IsTrue(notified); } [SetUp] - public override void Init() + public override async Task Init() { _connFactory = new ConnectionFactory { @@ -73,47 +76,47 @@ public override void Init() }; _conn = _connFactory.CreateConnection(); - _model = _conn.CreateModel(); + _channel = await _conn.CreateChannelAsync().ConfigureAwait(false); } [Test] - public void TestCancelNotificationExceptionHandling() + public Task TestCancelNotificationExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnCancel(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.QueueDelete(q)); + IBasicConsumer consumer = new ConsumerFailingOnCancel(_channel); + return TestExceptionHandlingWith(consumer, (ch, q, c, ct) => ch.DeleteQueueAsync(q).AsTask()); } [Test] - public void TestConsumerCancelOkExceptionHandling() + public Task TestConsumerCancelOkExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnCancelOk(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.BasicCancel(ct)); + IBasicConsumer consumer = new ConsumerFailingOnCancelOk(_channel); + return TestExceptionHandlingWith(consumer, (ch, q, c, ct) => ch.CancelConsumerAsync(ct).AsTask()); } [Test] - public void TestConsumerConsumeOkExceptionHandling() + public Task TestConsumerConsumeOkExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnConsumeOk(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => { }); + IBasicConsumer consumer = new ConsumerFailingOnConsumeOk(_channel); + return TestExceptionHandlingWith(consumer, (ch, q, c, ct) => Task.CompletedTask); } [Test] - public void TestConsumerShutdownExceptionHandling() + public Task TestConsumerShutdownExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnShutdown(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.Close()); + IBasicConsumer consumer = new ConsumerFailingOnShutdown(_channel); + return TestExceptionHandlingWith(consumer, (ch, q, c, ct) => ch.CloseAsync().AsTask()); } [Test] - public void TestDeliveryExceptionHandling() + public Task TestDeliveryExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnDelivery(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.BasicPublish("", q, null, _encoding.GetBytes("msg"))); + IBasicConsumer consumer = new ConsumerFailingOnDelivery(_channel); + return TestExceptionHandlingWith(consumer, (ch, q, c, ct) => ch.PublishMessageAsync("", q, null, _encoding.GetBytes("msg")).AsTask()); } private class ConsumerFailingOnDelivery : AsyncEventingBasicConsumer { - public ConsumerFailingOnDelivery(IModel model) : base(model) + public ConsumerFailingOnDelivery(IChannel channel) : base(channel) { } @@ -131,7 +134,7 @@ public override Task HandleBasicDeliver(string consumerTag, private class ConsumerFailingOnCancel : AsyncEventingBasicConsumer { - public ConsumerFailingOnCancel(IModel model) : base(model) + public ConsumerFailingOnCancel(IChannel channel) : base(channel) { } @@ -143,11 +146,11 @@ public override Task HandleBasicCancel(string consumerTag) private class ConsumerFailingOnShutdown : AsyncEventingBasicConsumer { - public ConsumerFailingOnShutdown(IModel model) : base(model) + public ConsumerFailingOnShutdown(IChannel channel) : base(channel) { } - public override Task HandleModelShutdown(object model, ShutdownEventArgs reason) + public override Task HandleModelShutdown(object channel, ShutdownEventArgs reason) { return Task.FromException(TestException); } @@ -155,7 +158,7 @@ public override Task HandleModelShutdown(object model, ShutdownEventArgs reason) private class ConsumerFailingOnConsumeOk : AsyncEventingBasicConsumer { - public ConsumerFailingOnConsumeOk(IModel model) : base(model) + public ConsumerFailingOnConsumeOk(IChannel channel) : base(channel) { } @@ -167,7 +170,7 @@ public override Task HandleBasicConsumeOk(string consumerTag) private class ConsumerFailingOnCancelOk : AsyncEventingBasicConsumer { - public ConsumerFailingOnCancelOk(IModel model) : base(model) + public ConsumerFailingOnCancelOk(IChannel channel) : base(channel) { } diff --git a/projects/Unit/TestAuth.cs b/projects/Unit/TestAuth.cs index e7e50e20d3..80d8a69d1e 100644 --- a/projects/Unit/TestAuth.cs +++ b/projects/Unit/TestAuth.cs @@ -38,7 +38,6 @@ namespace RabbitMQ.Client.Unit [TestFixture] public class TestAuth { - [Test] public void TestAuthFailure() { diff --git a/projects/Unit/TestBasicGet.cs b/projects/Unit/TestBasicGet.cs index e34d321180..ca6a9db436 100644 --- a/projects/Unit/TestBasicGet.cs +++ b/projects/Unit/TestBasicGet.cs @@ -29,6 +29,7 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using System.Threading.Tasks; using NUnit.Framework; using RabbitMQ.Client.Exceptions; @@ -39,36 +40,36 @@ namespace RabbitMQ.Client.Unit public class TestBasicGet : IntegrationFixture { [Test] - public void TestBasicGetWithClosedChannel() + public Task TestBasicGetWithClosedChannel() { - WithNonEmptyQueue( (_, q) => + return WithNonEmptyQueueAsync((_, q) => + { + return WithClosedChannelAsync(cm => { - WithClosedModel(cm => - { - Assert.Throws(Is.InstanceOf(), () => cm.BasicGet(q, true)); - }); + Assert.ThrowsAsync(() => cm.RetrieveSingleMessageAsync(q, true).AsTask()); }); + }); } [Test] - public void TestBasicGetWithEmptyResponse() + public Task TestBasicGetWithEmptyResponse() { - WithEmptyQueue((model, queue) => + return WithEmptyQueueAsync(async (channel, queue) => { - BasicGetResult res = model.BasicGet(queue, false); - Assert.IsNull(res); + SingleMessageRetrieval res = await channel.RetrieveSingleMessageAsync(queue, false).ConfigureAwait(false); + Assert.IsTrue(res.IsEmpty); }); } [Test] - public void TestBasicGetWithNonEmptyResponseAndAutoAckMode() + public Task TestBasicGetWithNonEmptyResponseAndAutoAckMode() { const string msg = "for basic.get"; - WithNonEmptyQueue((model, queue) => + return WithNonEmptyQueueAsync(async (channel, queue) => { - BasicGetResult res = model.BasicGet(queue, true); + SingleMessageRetrieval res = await channel.RetrieveSingleMessageAsync(queue, true).ConfigureAwait(false); Assert.AreEqual(msg, _encoding.GetString(res.Body.ToArray())); - AssertMessageCount(queue, 0); + await AssertMessageCountAsync(queue, 0).ConfigureAwait(false); }, msg); } } diff --git a/projects/Unit/TestBasicProperties.cs b/projects/Unit/TestBasicProperties.cs index 36b6e1b410..664926af27 100644 --- a/projects/Unit/TestBasicProperties.cs +++ b/projects/Unit/TestBasicProperties.cs @@ -34,9 +34,8 @@ namespace RabbitMQ.Client.Unit { - [TestFixture] - class TestBasicProperties + public class TestBasicProperties { [Test] public void TestPersistentPropertyChangesDeliveryMode_PersistentTrueDelivery2() diff --git a/projects/Unit/TestBasicPublish.cs b/projects/Unit/TestBasicPublish.cs index c1eb3d6efc..8a7d601f6d 100644 --- a/projects/Unit/TestBasicPublish.cs +++ b/projects/Unit/TestBasicPublish.cs @@ -2,7 +2,9 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; +using RabbitMQ.Client.Framing; namespace RabbitMQ.Client.Unit { @@ -10,17 +12,17 @@ namespace RabbitMQ.Client.Unit public class TestBasicPublish { [Test] - public void TestBasicRoundtripArray() + public async Task TestChannelRoundtripArray() { var cf = new ConnectionFactory(); using(IConnection c = cf.CreateConnection()) - using(IModel m = c.CreateModel()) + await using(IChannel channel = await c.CreateChannelAsync().ConfigureAwait(false)) { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); + var q = await channel.DeclareQueueAsync().ConfigureAwait(false); + var bp = new BasicProperties(); byte[] sendBody = System.Text.Encoding.UTF8.GetBytes("hi"); byte[] consumeBody = null; - var consumer = new EventingBasicConsumer(m); + var consumer = new EventingBasicConsumer(channel); var are = new AutoResetEvent(false); consumer.Received += async (o, a) => { @@ -28,11 +30,11 @@ public void TestBasicRoundtripArray() are.Set(); await Task.Yield(); }; - string tag = m.BasicConsume(q.QueueName, true, consumer); + string tag = await channel.ActivateConsumerAsync(consumer, q.QueueName, true).ConfigureAwait(false); - m.BasicPublish("", q.QueueName, bp, sendBody); + await channel.PublishMessageAsync("", q.QueueName, bp, sendBody).ConfigureAwait(false); bool waitResFalse = are.WaitOne(2000); - m.BasicCancel(tag); + await channel.CancelConsumerAsync(tag).ConfigureAwait(false); Assert.IsTrue(waitResFalse); CollectionAssert.AreEqual(sendBody, consumeBody); @@ -40,46 +42,16 @@ public void TestBasicRoundtripArray() } [Test] - public void TestBasicRoundtripReadOnlyMemory() + public async Task CanNotModifyPayloadAfterPublish() { var cf = new ConnectionFactory(); using(IConnection c = cf.CreateConnection()) - using(IModel m = c.CreateModel()) + await using(IChannel channel = await c.CreateChannelAsync().ConfigureAwait(false)) { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); - byte[] sendBody = System.Text.Encoding.UTF8.GetBytes("hi"); - byte[] consumeBody = null; - var consumer = new EventingBasicConsumer(m); - var are = new AutoResetEvent(false); - consumer.Received += async (o, a) => - { - consumeBody = a.Body.ToArray(); - are.Set(); - await Task.Yield(); - }; - string tag = m.BasicConsume(q.QueueName, true, consumer); - - m.BasicPublish("", q.QueueName, bp, new ReadOnlyMemory(sendBody)); - bool waitResFalse = are.WaitOne(2000); - m.BasicCancel(tag); - - Assert.IsTrue(waitResFalse); - CollectionAssert.AreEqual(sendBody, consumeBody); - } - } - - [Test] - public void CanNotModifyPayloadAfterPublish() - { - var cf = new ConnectionFactory(); - using(IConnection c = cf.CreateConnection()) - using(IModel m = c.CreateModel()) - { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); + var q = await channel.DeclareQueueAsync().ConfigureAwait(false); + var bp = new BasicProperties(); byte[] sendBody = new byte[1000]; - var consumer = new EventingBasicConsumer(m); + var consumer = new EventingBasicConsumer(channel); var are = new AutoResetEvent(false); bool modified = true; consumer.Received += (o, a) => @@ -90,15 +62,15 @@ public void CanNotModifyPayloadAfterPublish() } are.Set(); }; - string tag = m.BasicConsume(q.QueueName, true, consumer); + string tag = await channel.ActivateConsumerAsync(consumer, q.QueueName, true).ConfigureAwait(false); - m.BasicPublish("", q.QueueName, bp, sendBody); + await channel.PublishMessageAsync("", q.QueueName, bp, sendBody).ConfigureAwait(false); sendBody.AsSpan().Fill(1); Assert.IsTrue(are.WaitOne(2000)); Assert.IsFalse(modified, "Payload was modified after the return of BasicPublish"); - m.BasicCancel(tag); + await channel.CancelConsumerAsync(tag).ConfigureAwait(false); } } } diff --git a/projects/Unit/TestBasicPublishBatch.cs b/projects/Unit/TestBasicPublishBatch.cs index cae5e8410b..ca380fa6ab 100644 --- a/projects/Unit/TestBasicPublishBatch.cs +++ b/projects/Unit/TestBasicPublishBatch.cs @@ -30,64 +30,65 @@ //--------------------------------------------------------------------------- using System; - +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { internal class TestBasicPublishBatch : IntegrationFixture { [Test] - public void TestBasicPublishBatchSend() + public async Task TestBasicPublishBatchSend() { - _model.ConfirmSelect(); - _model.QueueDeclare(queue: "test-message-batch-a", durable: false); - _model.QueueDeclare(queue: "test-message-batch-b", durable: false); - IBasicPublishBatch batch = _model.CreateBasicPublishBatch(); - batch.Add("", "test-message-batch-a", false, null, new ReadOnlyMemory()); - batch.Add("", "test-message-batch-b", false, null, new ReadOnlyMemory()); - batch.Publish(); - _model.WaitForConfirmsOrDie(TimeSpan.FromSeconds(15)); - BasicGetResult resultA = _model.BasicGet("test-message-batch-a", true); - Assert.NotNull(resultA); - BasicGetResult resultB = _model.BasicGet("test-message-batch-b", true); - Assert.NotNull(resultB); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); + await _channel.DeclareQueueAsync(queue: "test-message-batch-a", durable: false).ConfigureAwait(false); + await _channel.DeclareQueueAsync(queue: "test-message-batch-b", durable: false).ConfigureAwait(false); + MessageBatch batch = new MessageBatch(); + batch.Add("", "test-message-batch-a", null, ReadOnlyMemory.Empty, false); + batch.Add("", "test-message-batch-b", null, ReadOnlyMemory.Empty, false); + await _channel.PublishBatchAsync(batch).ConfigureAwait(false); + _channel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(15)); + SingleMessageRetrieval resultA = await _channel.RetrieveSingleMessageAsync("test-message-batch-a", true).ConfigureAwait(false); + Assert.IsFalse(resultA.IsEmpty); + SingleMessageRetrieval resultB = await _channel.RetrieveSingleMessageAsync("test-message-batch-b", true).ConfigureAwait(false); + Assert.IsFalse(resultB.IsEmpty); } [Test] - public void TestBasicPublishBatchSendWithSizeHint() + public async Task TestBasicPublishBatchSendWithSizeHint() { - _model.ConfirmSelect(); - _model.QueueDeclare(queue: "test-message-batch-a", durable: false); - _model.QueueDeclare(queue: "test-message-batch-b", durable: false); - IBasicPublishBatch batch = _model.CreateBasicPublishBatch(2); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); + await _channel.DeclareQueueAsync(queue: "test-message-batch-a", durable: false).ConfigureAwait(false); + await _channel.DeclareQueueAsync(queue: "test-message-batch-b", durable: false).ConfigureAwait(false); + MessageBatch batch = new MessageBatch(2); ReadOnlyMemory bodyAsMemory = new byte [] {}; - batch.Add("", "test-message-batch-a", false, null, bodyAsMemory); - batch.Add("", "test-message-batch-b", false, null, bodyAsMemory); - batch.Publish(); - _model.WaitForConfirmsOrDie(TimeSpan.FromSeconds(15)); - BasicGetResult resultA = _model.BasicGet("test-message-batch-a", true); - Assert.NotNull(resultA); - BasicGetResult resultB = _model.BasicGet("test-message-batch-b", true); - Assert.NotNull(resultB); + batch.Add("", "test-message-batch-a", null, bodyAsMemory, false); + batch.Add("", "test-message-batch-b", null, bodyAsMemory, false); + await _channel.PublishBatchAsync(batch).ConfigureAwait(false); + _channel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(15)); + SingleMessageRetrieval resultA = await _channel.RetrieveSingleMessageAsync("test-message-batch-a", true).ConfigureAwait(false); + Assert.IsFalse(resultA.IsEmpty); + SingleMessageRetrieval resultB = await _channel.RetrieveSingleMessageAsync("test-message-batch-b", true).ConfigureAwait(false); + Assert.IsFalse(resultB.IsEmpty); } [Test] - public void TestBasicPublishBatchSendWithWrongSizeHint() + public async Task TestBasicPublishBatchSendWithWrongSizeHint() { - _model.ConfirmSelect(); - _model.QueueDeclare(queue: "test-message-batch-a", durable: false); - _model.QueueDeclare(queue: "test-message-batch-b", durable: false); - IBasicPublishBatch batch = _model.CreateBasicPublishBatch(1); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); + await _channel.DeclareQueueAsync(queue: "test-message-batch-a", durable: false).ConfigureAwait(false); + await _channel.DeclareQueueAsync(queue: "test-message-batch-b", durable: false).ConfigureAwait(false); + MessageBatch batch = new MessageBatch(1); ReadOnlyMemory bodyAsMemory = new byte [] {}; - batch.Add("", "test-message-batch-a", false, null, bodyAsMemory); - batch.Add("", "test-message-batch-b", false, null, bodyAsMemory); - batch.Publish(); - _model.WaitForConfirmsOrDie(TimeSpan.FromSeconds(15)); - BasicGetResult resultA = _model.BasicGet("test-message-batch-a", true); - Assert.NotNull(resultA); - BasicGetResult resultB = _model.BasicGet("test-message-batch-b", true); - Assert.NotNull(resultB); + batch.Add("", "test-message-batch-a", null, bodyAsMemory, false); + batch.Add("", "test-message-batch-b", null, bodyAsMemory, false); + await _channel.PublishBatchAsync(batch).ConfigureAwait(false); + _channel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(15)); + SingleMessageRetrieval resultA = await _channel.RetrieveSingleMessageAsync("test-message-batch-a", true).ConfigureAwait(false); + Assert.IsFalse(resultA.IsEmpty); + SingleMessageRetrieval resultB = await _channel.RetrieveSingleMessageAsync("test-message-batch-b", true).ConfigureAwait(false); + Assert.IsFalse(resultB.IsEmpty); } } } diff --git a/projects/Unit/TestBlockingCell.cs b/projects/Unit/TestBlockingCell.cs index f5c34defda..cfcb436f3e 100644 --- a/projects/Unit/TestBlockingCell.cs +++ b/projects/Unit/TestBlockingCell.cs @@ -39,7 +39,7 @@ namespace RabbitMQ.Client.Unit { [TestFixture] - class TestBlockingCell : TimingFixture + internal class TestBlockingCell : TimingFixture { public class DelayedSetter { diff --git a/projects/Unit/TestChannelAllocation.cs b/projects/Unit/TestChannelAllocation.cs index 9ea1004802..8deb0a1714 100644 --- a/projects/Unit/TestChannelAllocation.cs +++ b/projects/Unit/TestChannelAllocation.cs @@ -30,80 +30,93 @@ //--------------------------------------------------------------------------- using System.Collections.Generic; - +using System.Threading.Tasks; using NUnit.Framework; - -using RabbitMQ.Client.Impl; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { - [TestFixture] - public class TestIModelAllocation - { - public const int CHANNEL_COUNT = 100; - - IConnection _c; - - public int ModelNumber(IModel model) - { - return ((ModelBase)((AutorecoveringModel)model).Delegate).Session.ChannelNumber; - } - - [SetUp] public void Connect() - { - _c = new ConnectionFactory().CreateConnection(); - } - - [TearDown] public void Disconnect() - { - _c.Close(); - } - - - [Test] public void AllocateInOrder() + public class TestIChannelAllocation { - for(int i = 1; i <= CHANNEL_COUNT; i++) - Assert.AreEqual(i, ModelNumber(_c.CreateModel())); - } - - [Test] public void AllocateAfterFreeingLast() { - IModel ch = _c.CreateModel(); - Assert.AreEqual(1, ModelNumber(ch)); - ch.Close(); - ch = _c.CreateModel(); - Assert.AreEqual(1, ModelNumber(ch)); - } - - public int CompareModels(IModel x, IModel y) - { - int i = ModelNumber(x); - int j = ModelNumber(y); - return (i < j) ? -1 : (i == j) ? 0 : 1; - } - - [Test] public void AllocateAfterFreeingMany() { - List channels = new List(); - - for(int i = 1; i <= CHANNEL_COUNT; i++) - channels.Add(_c.CreateModel()); - - foreach(IModel channel in channels){ - channel.Close(); - } - - channels = new List(); - - for(int j = 1; j <= CHANNEL_COUNT; j++) - channels.Add(_c.CreateModel()); - - // In the current implementation the list should actually - // already be sorted, but we don't want to force that behaviour - channels.Sort(CompareModels); - - int k = 1; - foreach(IModel channel in channels) - Assert.AreEqual(k++, ModelNumber(channel)); + public const int CHANNEL_COUNT = 100; + + private IConnection _c; + + [SetUp] + public void Connect() + { + _c = new ConnectionFactory().CreateConnection(); + } + + [TearDown] + public void Disconnect() + { + _c.Close(); + } + + [Test] + public async Task AllocateInOrder() + { + for (int i = 1; i <= CHANNEL_COUNT; i++) + { + Assert.AreEqual(i, ChannelNumber(await _c.CreateChannelAsync().ConfigureAwait(false))); + } + } + + [Test] + public async Task AllocateAfterFreeingLast() + { + IChannel ch = await _c.CreateChannelAsync().ConfigureAwait(false); + Assert.AreEqual(1, ChannelNumber(ch)); + await ch.CloseAsync().ConfigureAwait(false); + ch = await _c.CreateChannelAsync().ConfigureAwait(false); + Assert.AreEqual(1, ChannelNumber(ch)); + } + + public int CompareChannels(IChannel x, IChannel y) + { + int i = ChannelNumber(x); + int j = ChannelNumber(y); + return (i < j) ? -1 : (i == j) ? 0 : 1; + } + + [Test] + public async Task AllocateAfterFreeingMany() + { + List channels = new List(); + + for (int i = 1; i <= CHANNEL_COUNT; i++) + { + channels.Add(await _c.CreateChannelAsync().ConfigureAwait(false)); + } + + foreach (IChannel channel in channels) + { + await channel.CloseAsync().ConfigureAwait(false); + } + + channels.Clear(); + + for (int j = 1; j <= CHANNEL_COUNT; j++) + { + channels.Add(await _c.CreateChannelAsync().ConfigureAwait(false)); + } + + // In the current implementation the list should actually + // already be sorted, but we don't want to force that behaviour + channels.Sort(CompareChannels); + + int k = 1; + foreach (IChannel channel in channels) + { + Assert.AreEqual(k++, ChannelNumber(channel)); + } + } + + private static int ChannelNumber(IChannel channel) + { + return channel.ChannelNumber; + } } - } } diff --git a/projects/Unit/TestModelShutdown.cs b/projects/Unit/TestChannelShutdown.cs similarity index 71% rename from projects/Unit/TestModelShutdown.cs rename to projects/Unit/TestChannelShutdown.cs index d68e47d187..7a2aa07e49 100644 --- a/projects/Unit/TestModelShutdown.cs +++ b/projects/Unit/TestChannelShutdown.cs @@ -31,30 +31,27 @@ using System; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; - -using RabbitMQ.Client.Impl; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { [TestFixture] - internal class TestModelShutdown : IntegrationFixture + internal class TestChannelShutdown : IntegrationFixture { [Test] - public void TestConsumerDispatcherShutdown() + public async Task TestConsumerDispatcherShutdown() { - var m = (AutorecoveringModel)_model; - var latch = new ManualResetEventSlim(false); + var ch = (AutorecoveringChannel)_channel; + using var latch = new ManualResetEventSlim(false); + + _channel.Shutdown += args => latch.Set(); - _model.ModelShutdown += (model, args) => - { - latch.Set(); - }; - Assert.IsFalse(m.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close"); - _model.Close(); + Assert.IsFalse(ch.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close"); + await _channel.CloseAsync().ConfigureAwait(false); Wait(latch, TimeSpan.FromSeconds(3)); - Assert.IsTrue(m.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close"); + Assert.IsTrue(ch.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close"); } } } diff --git a/projects/Unit/TestConcurrentAccessWithSharedConnection.cs b/projects/Unit/TestConcurrentAccessWithSharedConnection.cs index 94af0852e7..0e79cdb3fa 100644 --- a/projects/Unit/TestConcurrentAccessWithSharedConnection.cs +++ b/projects/Unit/TestConcurrentAccessWithSharedConnection.cs @@ -36,21 +36,21 @@ using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { [TestFixture] public class TestConcurrentAccessWithSharedConnection : IntegrationFixture { - internal const int Threads = 32; internal CountdownEvent _latch; internal TimeSpan _completionTimeout = TimeSpan.FromSeconds(90); [SetUp] - public override void Init() + public override async Task Init() { - base.Init(); + await base.Init().ConfigureAwait(false); ThreadPool.SetMinThreads(Threads, Threads); _latch = new CountdownEvent(Threads); } @@ -143,19 +143,14 @@ public void TestConcurrentChannelOpenAndPublishingCase12() [Test] public void TestConcurrentChannelOpenCloseLoop() { - TestConcurrentChannelOperations((conn) => + TestConcurrentChannelOperations(async conn => { - IModel ch = conn.CreateModel(); - ch.Close(); + IChannel channel = await conn.CreateChannelAsync().ConfigureAwait(false); + await channel.CloseAsync().ConfigureAwait(false); }, 50); } - internal void TestConcurrentChannelOpenAndPublishingWithBodyOfSize(int length) - { - TestConcurrentChannelOpenAndPublishingWithBodyOfSize(length, 30); - } - - internal void TestConcurrentChannelOpenAndPublishingWithBodyOfSize(int length, int iterations) + internal void TestConcurrentChannelOpenAndPublishingWithBodyOfSize(int length, int iterations = 30) { string s = "payload"; if (length > s.Length) @@ -168,36 +163,35 @@ internal void TestConcurrentChannelOpenAndPublishingWithBodyOfSize(int length, i internal void TestConcurrentChannelOpenAndPublishingWithBody(byte[] body, int iterations) { - TestConcurrentChannelOperations((conn) => + TestConcurrentChannelOperations(async conn => { // publishing on a shared channel is not supported // and would missing the point of this test anyway - IModel ch = _conn.CreateModel(); - ch.ConfirmSelect(); - foreach (int j in Enumerable.Range(0, 200)) + IChannel ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + await ch.ActivatePublishTagsAsync().ConfigureAwait(false); + for (int i = 0; i < 200; i++) { - ch.BasicPublish(exchange: "", routingKey: "_______", basicProperties: ch.CreateBasicProperties(), body: body); + await ch.PublishMessageAsync("", "_______", null, body).ConfigureAwait(false); } + ch.WaitForConfirms(TimeSpan.FromSeconds(40)); }, iterations); } - internal void TestConcurrentChannelOperations(Action actions, - int iterations) + internal void TestConcurrentChannelOperations(Func actions, int iterations) { TestConcurrentChannelOperations(actions, iterations, _completionTimeout); } - internal void TestConcurrentChannelOperations(Action actions, - int iterations, TimeSpan timeout) + internal void TestConcurrentChannelOperations(Func actions, int iterations, TimeSpan timeout) { Task[] tasks = Enumerable.Range(0, Threads).Select(x => { - return Task.Run(() => + return Task.Run(async () => { - foreach (int j in Enumerable.Range(0, iterations)) + for (int i = 0; i < iterations; i++) { - actions(_conn); + await actions(_conn).ConfigureAwait(false); } _latch.Signal(); diff --git a/projects/Unit/TestConfirmSelect.cs b/projects/Unit/TestConfirmSelect.cs index b08956654f..af0d5156f8 100644 --- a/projects/Unit/TestConfirmSelect.cs +++ b/projects/Unit/TestConfirmSelect.cs @@ -29,35 +29,39 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using System.Threading.Tasks; using NUnit.Framework; namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestConfirmSelect : IntegrationFixture { - + public class TestConfirmSelect : IntegrationFixture + { [Test] - public void TestConfirmSelectIdempotency() + public async Task TestConfirmSelectIdempotency() { - _model.ConfirmSelect(); - Assert.AreEqual(1, _model.NextPublishSeqNo); - Publish(); - Assert.AreEqual(2, _model.NextPublishSeqNo); - Publish(); - Assert.AreEqual(3, _model.NextPublishSeqNo); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); + ulong tag = 0; + _channel.NewPublishTagUsed += usedTag => tag = usedTag; + await PublishAsync().ConfigureAwait(false); + Assert.AreEqual(1, tag); + await PublishAsync().ConfigureAwait(false); + Assert.AreEqual(2, tag); + await PublishAsync().ConfigureAwait(false); + Assert.AreEqual(3, tag); - _model.ConfirmSelect(); - Publish(); - Assert.AreEqual(4, _model.NextPublishSeqNo); - Publish(); - Assert.AreEqual(5, _model.NextPublishSeqNo); - Publish(); - Assert.AreEqual(6, _model.NextPublishSeqNo); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); + await PublishAsync().ConfigureAwait(false); + Assert.AreEqual(4, tag); + await PublishAsync().ConfigureAwait(false); + Assert.AreEqual(5, tag); + await PublishAsync().ConfigureAwait(false); + Assert.AreEqual(6, tag); } - protected void Publish() + private ValueTask PublishAsync() { - _model.BasicPublish("", "amq.fanout", null, _encoding.GetBytes("message")); + return _channel.PublishMessageAsync("", "amq.fanout", null, _encoding.GetBytes("message")); } } } diff --git a/projects/Unit/TestConnectionBlocked.cs b/projects/Unit/TestConnectionBlocked.cs index 34f642d57d..54eef438a8 100644 --- a/projects/Unit/TestConnectionBlocked.cs +++ b/projects/Unit/TestConnectionBlocked.cs @@ -31,7 +31,7 @@ using System; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; using RabbitMQ.Client.Events; @@ -49,7 +49,6 @@ public void HandleBlocked(object sender, ConnectionBlockedEventArgs args) Unblock(); } - public void HandleUnblocked(object sender, EventArgs ea) { lock (_lockObject) @@ -65,12 +64,12 @@ protected override void ReleaseResources() } [Test] - public void TestConnectionBlockedNotification() + public async Task TestConnectionBlockedNotification() { _conn.ConnectionBlocked += HandleBlocked; _conn.ConnectionUnblocked += HandleUnblocked; - Block(); + await BlockAsync().ConfigureAwait(false); lock (_lockObject) { if (!_notified) diff --git a/projects/Unit/TestConnectionFactory.cs b/projects/Unit/TestConnectionFactory.cs index b234ad5fe8..a7962758a1 100644 --- a/projects/Unit/TestConnectionFactory.cs +++ b/projects/Unit/TestConnectionFactory.cs @@ -38,7 +38,7 @@ namespace RabbitMQ.Client.Unit { [TestFixture] - class TestConnectionFactory + internal class TestConnectionFactory { [Test] public void TestProperties() @@ -76,7 +76,7 @@ public void TestCreateConnectionUsesSpecifiedPort() }; Assert.That(() => { - using(IConnection conn = cf.CreateConnection()) {} + using(cf.CreateConnection()) {} }, Throws.TypeOf()); } @@ -91,7 +91,7 @@ public void TestCreateConnectionWithClientProvidedNameUsesSpecifiedPort() }; Assert.That(() => { - using(IConnection conn = cf.CreateConnection("some_name")) {} + using(cf.CreateConnection("some_name")) {} }, Throws.TypeOf()); } @@ -191,7 +191,7 @@ public void TestCreateConnectionWithAutoRecoveryUsesAmqpTcpEndpoint() Port = 1234 }; var ep = new AmqpTcpEndpoint("localhost"); - using(IConnection conn = cf.CreateConnection(new System.Collections.Generic.List { ep })) {} + using(cf.CreateConnection(new System.Collections.Generic.List { ep })) {} } [Test] @@ -204,7 +204,7 @@ public void TestCreateConnectionWithAutoRecoveryUsesInvalidAmqpTcpEndpoint() var ep = new AmqpTcpEndpoint("localhost", 1234); Assert.That(() => { - using(IConnection conn = cf.CreateConnection(new System.Collections.Generic.List { ep })){} + using(cf.CreateConnection(new System.Collections.Generic.List { ep })){} }, Throws.TypeOf()); } @@ -217,7 +217,7 @@ public void TestCreateConnectionUsesAmqpTcpEndpoint() Port = 1234 }; var ep = new AmqpTcpEndpoint("localhost"); - using(IConnection conn = cf.CreateConnection(new System.Collections.Generic.List { ep })) {} + using(cf.CreateConnection(new System.Collections.Generic.List { ep })) {} } [Test] @@ -232,7 +232,7 @@ public void TestCreateConnectionWithForcedAddressFamily() AddressFamily = System.Net.Sockets.AddressFamily.InterNetwork }; cf.Endpoint = ep; - using(IConnection conn = cf.CreateConnection()){}; + using(cf.CreateConnection()){}; } [Test] @@ -242,17 +242,17 @@ public void TestCreateConnectionUsesInvalidAmqpTcpEndpoint() var ep = new AmqpTcpEndpoint("localhost", 1234); Assert.That(() => { - using(IConnection conn = cf.CreateConnection(new System.Collections.Generic.List { ep })) {} + using(cf.CreateConnection(new System.Collections.Generic.List { ep })) {} }, Throws.TypeOf()); } [Test] - public void TestCreateConnectioUsesValidEndpointWhenMultipleSupplied() + public void TestCreateConnectionUsesValidEndpointWhenMultipleSupplied() { var cf = new ConnectionFactory(); var invalidEp = new AmqpTcpEndpoint("not_localhost"); var ep = new AmqpTcpEndpoint("localhost"); - using(IConnection conn = cf.CreateConnection(new List { invalidEp, ep })) {}; + using(cf.CreateConnection(new List { invalidEp, ep })) {}; } } } diff --git a/projects/Unit/TestConnectionFactoryContinuationTimeout.cs b/projects/Unit/TestConnectionFactoryContinuationTimeout.cs index 416d9351e7..e46f19a4b2 100644 --- a/projects/Unit/TestConnectionFactoryContinuationTimeout.cs +++ b/projects/Unit/TestConnectionFactoryContinuationTimeout.cs @@ -30,6 +30,7 @@ //--------------------------------------------------------------------------- using System; +using System.Threading.Tasks; using NUnit.Framework; namespace RabbitMQ.Client.Unit @@ -38,22 +39,24 @@ namespace RabbitMQ.Client.Unit internal class TestConnectionFactoryContinuationTimeout : IntegrationFixture { [Test] - public void TestConnectionFactoryContinuationTimeoutOnRecoveringConnection() + public async Task TestConnectionFactoryContinuationTimeoutOnRecoveringConnection() { var continuationTimeout = TimeSpan.FromSeconds(777); using (IConnection c = CreateConnectionWithContinuationTimeout(true, continuationTimeout)) { - Assert.AreEqual(continuationTimeout, c.CreateModel().ContinuationTimeout); + await using var channel = await c.CreateChannelAsync().ConfigureAwait(false); + Assert.AreEqual(continuationTimeout, channel.ContinuationTimeout); } } [Test] - public void TestConnectionFactoryContinuationTimeoutOnNonRecoveringConnection() + public async Task TestConnectionFactoryContinuationTimeoutOnNonRecoveringConnection() { var continuationTimeout = TimeSpan.FromSeconds(777); using (IConnection c = CreateConnectionWithContinuationTimeout(false, continuationTimeout)) { - Assert.AreEqual(continuationTimeout, c.CreateModel().ContinuationTimeout); + await using var channel = await c.CreateChannelAsync().ConfigureAwait(false); + Assert.AreEqual(continuationTimeout, channel.ContinuationTimeout); } } } diff --git a/projects/Unit/TestConnectionRecovery.cs b/projects/Unit/TestConnectionRecovery.cs index ca4551660a..b46b3d9e3a 100644 --- a/projects/Unit/TestConnectionRecovery.cs +++ b/projects/Unit/TestConnectionRecovery.cs @@ -32,40 +32,25 @@ using System; using System.Collections.Generic; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; using RabbitMQ.Client.Framing.Impl; -using RabbitMQ.Client.Impl; #pragma warning disable 0618 namespace RabbitMQ.Client.Unit { - class DisposableConnection : IDisposable - { - public DisposableConnection(AutorecoveringConnection c) - { - Connection = c; - } - - public AutorecoveringConnection Connection { get; } - - public void Dispose() - { - Connection.Close(); - } - } [TestFixture] public class TestConnectionRecovery : IntegrationFixture { [SetUp] - public override void Init() + public override async Task Init() { _conn = CreateAutorecoveringConnection(); - _model = _conn.CreateModel(); + _channel = await _conn.CreateChannelAsync().ConfigureAwait(false); } [TearDown] @@ -75,46 +60,48 @@ public void CleanUp() } [Test] - public void TestBasicAckAfterChannelRecovery() + public Task TestBasicAckAfterChannelRecovery() { var latch = new ManualResetEventSlim(false); - var cons = new AckingBasicConsumer(_model, latch, CloseAndWaitForRecovery); + var cons = new AckingBasicConsumer(_channel, latch, CloseAndWaitForRecovery); - TestDelayedBasicAckNackAfterChannelRecovery(cons, latch); + return TestDelayedBasicAckNackAfterChannelRecovery(cons, latch); } [Test] - public void TestBasicAckAfterBasicGetAndChannelRecovery() + public async Task TestBasicAckAfterBasicGetAndChannelRecovery() { string q = GenerateQueueName(); - _model.QueueDeclare(q, false, false, false, null); + await _channel.DeclareQueueAsync(q, false, false, false).ConfigureAwait(false); // create an offset - IBasicProperties bp = _model.CreateBasicProperties(); - _model.BasicPublish("", q, bp, new byte[] { }); + await _channel.PublishMessageAsync("", q, null, Array.Empty()).ConfigureAwait(false); Thread.Sleep(50); - BasicGetResult g = _model.BasicGet(q, false); + SingleMessageRetrieval g = await _channel.RetrieveSingleMessageAsync(q, false).ConfigureAwait(false); + Assert.IsFalse(g.IsEmpty); CloseAndWaitForRecovery(); Assert.IsTrue(_conn.IsOpen); - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); // ack the message after recovery - this should be out of range and ignored - _model.BasicAck(g.DeliveryTag, false); + await _channel.AckMessageAsync(g.DeliveryTag, false); // do a sync operation to 'check' there is no channel exception - _model.BasicGet(q, false); + await _channel.RetrieveSingleMessageAsync(q, false).ConfigureAwait(false); } [Test] - public void TestBasicAckEventHandlerRecovery() + public async Task TestBasicAckEventHandlerRecovery() { - _model.ConfirmSelect(); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); var latch = new ManualResetEventSlim(false); - ((AutorecoveringModel)_model).BasicAcks += (m, args) => latch.Set(); - ((AutorecoveringModel)_model).BasicNacks += (m, args) => latch.Set(); + ((AutorecoveringChannel)_channel).PublishTagAcknowledged += (_, __, ___) => latch.Set(); CloseAndWaitForRecovery(); CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); - WithTemporaryNonExclusiveQueue(_model, (m, q) => m.BasicPublish("", q, null, _encoding.GetBytes(""))); + await WithTemporaryNonExclusiveQueueAsync( + _channel, + (ch, q) => ch.PublishMessageAsync("", q, null, _encoding.GetBytes("")).AsTask()) + .ConfigureAwait(false); Wait(latch); } @@ -208,163 +195,153 @@ public void TestBasicConnectionRecoveryOnBrokerRestart() } [Test] - public void TestBasicModelRecovery() + public void TestChannelRecovery() { - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); } [Test] - public void TestBasicModelRecoveryOnServerRestart() + public void TestChannelRecoveryOnServerRestart() { - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); RestartServerAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); } [Test] - public void TestBasicNackAfterChannelRecovery() + public Task TestBasicNackAfterChannelRecovery() { var latch = new ManualResetEventSlim(false); - var cons = new NackingBasicConsumer(_model, latch, CloseAndWaitForRecovery); + var cons = new NackingBasicConsumer(_channel, latch, CloseAndWaitForRecovery); - TestDelayedBasicAckNackAfterChannelRecovery(cons, latch); + return TestDelayedBasicAckNackAfterChannelRecovery(cons, latch); } [Test] - public void TestBasicRejectAfterChannelRecovery() - { - var latch = new ManualResetEventSlim(false); - var cons = new RejectingBasicConsumer(_model, latch, CloseAndWaitForRecovery); - - TestDelayedBasicAckNackAfterChannelRecovery(cons, latch); - } - - [Test] - public void TestBlockedListenersRecovery() + public async Task TestBlockedListenersRecovery() { var latch = new ManualResetEventSlim(false); _conn.ConnectionBlocked += (c, reason) => latch.Set(); CloseAndWaitForRecovery(); CloseAndWaitForRecovery(); - Block(); + await BlockAsync().ConfigureAwait(false); Wait(latch); Unblock(); } [Test] - public void TestClientNamedQueueRecovery() + public Task TestClientNamedQueueRecovery() { string s = "dotnet-client.test.recovery.q1"; - WithTemporaryNonExclusiveQueue(_model, (m, q) => + return WithTemporaryNonExclusiveQueueAsync(_channel, async (ch, q) => { CloseAndWaitForRecovery(); - AssertQueueRecovery(m, q, false); - _model.QueueDelete(q); + await AssertQueueRecoveryAsync(ch, q, false).ConfigureAwait(false); + await _channel.DeleteQueueAsync(q).ConfigureAwait(false); }, s); } [Test] - public void TestClientNamedQueueRecoveryNoWait() + public Task TestClientNamedQueueRecoveryNoWait() { string s = "dotnet-client.test.recovery.q1-nowait"; - WithTemporaryQueueNoWait(_model, (m, q) => + return WithTemporaryQueueNoWaitAsync(_channel, (ch, q) => { CloseAndWaitForRecovery(); - AssertQueueRecovery(m, q); + return AssertQueueRecoveryAsync(ch, q); }, s); } [Test] - public void TestClientNamedQueueRecoveryOnServerRestart() + public Task TestClientNamedQueueRecoveryOnServerRestart() { string s = "dotnet-client.test.recovery.q1"; - WithTemporaryNonExclusiveQueue(_model, (m, q) => + return WithTemporaryNonExclusiveQueueAsync(_channel, async (ch, q) => { RestartServerAndWaitForRecovery(); - AssertQueueRecovery(m, q, false); - _model.QueueDelete(q); + await AssertQueueRecoveryAsync(ch, q, false).ConfigureAwait(false); + await _channel.DeleteQueueAsync(q).ConfigureAwait(false); }, s); } [Test] - public void TestConsumerWorkServiceRecovery() + public async Task TestConsumerWorkServiceRecovery() { using (AutorecoveringConnection c = CreateAutorecoveringConnection()) { - IModel m = c.CreateModel(); - string q = m.QueueDeclare("dotnet-client.recovery.consumer_work_pool1", - false, false, false, null).QueueName; - var cons = new EventingBasicConsumer(m); - m.BasicConsume(q, true, cons); - AssertConsumerCount(m, q, 1); + IChannel ch = await c.CreateChannelAsync().ConfigureAwait(false); + (string q, _, _) = await ch.DeclareQueueAsync("dotnet-client.recovery.consumer_work_pool1", false, false, false).ConfigureAwait(false); + var cons = new EventingBasicConsumer(ch); + await ch.ActivateConsumerAsync(cons, q, true).ConfigureAwait(false); + await AssertConsumerCountAsync(ch, q, 1).ConfigureAwait(false); CloseAndWaitForRecovery(c); - Assert.IsTrue(m.IsOpen); + Assert.IsTrue(ch.IsOpen); var latch = new ManualResetEventSlim(false); cons.Received += (s, args) => latch.Set(); - m.BasicPublish("", q, null, _encoding.GetBytes("msg")); + await ch.PublishMessageAsync("", q, null, _encoding.GetBytes("msg")).ConfigureAwait(false); Wait(latch); - m.QueueDelete(q); + await ch.DeleteQueueAsync(q).ConfigureAwait(false); } } [Test] - public void TestConsumerRecoveryOnClientNamedQueueWithOneRecovery() + public async Task TestConsumerRecoveryOnClientNamedQueueWithOneRecovery() { string q0 = "dotnet-client.recovery.queue1"; using (AutorecoveringConnection c = CreateAutorecoveringConnection()) { - IModel m = c.CreateModel(); - string q1 = m.QueueDeclare(q0, false, false, false, null).QueueName; + IChannel ch = await c.CreateChannelAsync().ConfigureAwait(false); + (string q1, _, _) = await ch.DeclareQueueAsync(q0, false, false, false).ConfigureAwait(false); Assert.AreEqual(q0, q1); - var cons = new EventingBasicConsumer(m); - m.BasicConsume(q1, true, cons); - AssertConsumerCount(m, q1, 1); + var cons = new EventingBasicConsumer(ch); + await ch.ActivateConsumerAsync(cons, q1, true).ConfigureAwait(false); + await AssertConsumerCountAsync(ch, q1, 1).ConfigureAwait(false); bool queueNameChangeAfterRecoveryCalled = false; c.QueueNameChangeAfterRecovery += (source, ea) => { queueNameChangeAfterRecoveryCalled = true; }; CloseAndWaitForRecovery(c); - AssertConsumerCount(m, q1, 1); + await AssertConsumerCountAsync(ch, q1, 1).ConfigureAwait(false); Assert.False(queueNameChangeAfterRecoveryCalled); CloseAndWaitForRecovery(c); - AssertConsumerCount(m, q1, 1); + await AssertConsumerCountAsync(ch, q1, 1).ConfigureAwait(false); Assert.False(queueNameChangeAfterRecoveryCalled); CloseAndWaitForRecovery(c); - AssertConsumerCount(m, q1, 1); + await AssertConsumerCountAsync(ch, q1, 1).ConfigureAwait(false); Assert.False(queueNameChangeAfterRecoveryCalled); var latch = new ManualResetEventSlim(false); cons.Received += (s, args) => latch.Set(); - m.BasicPublish("", q1, null, _encoding.GetBytes("msg")); + await ch.PublishMessageAsync("", q1, null, _encoding.GetBytes("msg")).ConfigureAwait(false); Wait(latch); - m.QueueDelete(q1); + await ch.DeleteQueueAsync(q1).ConfigureAwait(false); } } [Test] - public void TestConsumerRecoveryWithManyConsumers() + public async Task TestConsumerRecoveryWithManyConsumers() { - string q = _model.QueueDeclare(GenerateQueueName(), false, false, false, null).QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync(GenerateQueueName(), false, false, false).ConfigureAwait(false); int n = 1024; for (int i = 0; i < n; i++) { - var cons = new EventingBasicConsumer(_model); - _model.BasicConsume(q, true, cons); + var cons = new EventingBasicConsumer(_channel); + await _channel.ActivateConsumerAsync(cons, q, true).ConfigureAwait(false); } var latch = new ManualResetEventSlim(false); @@ -372,12 +349,12 @@ public void TestConsumerRecoveryWithManyConsumers() CloseAndWaitForRecovery(); Wait(latch); - Assert.IsTrue(_model.IsOpen); - AssertConsumerCount(q, n); + Assert.IsTrue(_channel.IsOpen); + await AssertConsumerCountAsync(q, n).ConfigureAwait(false); } [Test] - public void TestCreateModelOnClosedAutorecoveringConnectionDoesNotHang() + public async Task TestCreateChannelOnClosedAutorecoveringConnectionDoesNotHang() { // we don't want this to recover quickly in this test AutorecoveringConnection c = CreateAutorecoveringConnection(TimeSpan.FromSeconds(20)); @@ -387,7 +364,7 @@ public void TestCreateModelOnClosedAutorecoveringConnectionDoesNotHang() c.Close(); WaitForShutdown(c); Assert.IsFalse(c.IsOpen); - c.CreateModel(); + await c.CreateChannelAsync().ConfigureAwait(false); Assert.Fail("Expected an exception"); } catch (AlreadyClosedException) @@ -405,183 +382,184 @@ public void TestCreateModelOnClosedAutorecoveringConnectionDoesNotHang() } [Test] - public void TestDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreDeleted() + public async Task TestDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreDeleted() { AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); for (int i = 0; i < 3; i++) { string x1 = $"source-{Guid.NewGuid()}"; - _model.ExchangeDeclare(x1, "fanout", false, true, null); + await _channel.DeclareExchangeAsync(x1, "fanout", false, true).ConfigureAwait(false); string x2 = $"destination-{Guid.NewGuid()}"; - _model.ExchangeDeclare(x2, "fanout", false, false, null); - _model.ExchangeBind(x2, x1, ""); - _model.ExchangeDelete(x2); + await _channel.DeclareExchangeAsync(x2, "fanout", false, false).ConfigureAwait(false); + await _channel.BindExchangeAsync(x2, x1, "").ConfigureAwait(false); + await _channel.DeleteExchangeAsync(x2).ConfigureAwait(false); } AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); } [Test] - public void TestDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreUnbound() + public async Task TestDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreUnbound() { AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); for (int i = 0; i < 1000; i++) { string x1 = $"source-{Guid.NewGuid()}"; - _model.ExchangeDeclare(x1, "fanout", false, true, null); + await _channel.DeclareExchangeAsync(x1, "fanout", false, true).ConfigureAwait(false); string x2 = $"destination-{Guid.NewGuid()}"; - _model.ExchangeDeclare(x2, "fanout", false, false, null); - _model.ExchangeBind(x2, x1, ""); - _model.ExchangeUnbind(x2, x1, ""); - _model.ExchangeDelete(x2); + await _channel.DeclareExchangeAsync(x2, "fanout", false, false).ConfigureAwait(false); + await _channel.BindExchangeAsync(x2, x1, "").ConfigureAwait(false); + await _channel.UnbindExchangeAsync(x2, x1, "").ConfigureAwait(false); + await _channel.DeleteExchangeAsync(x2).ConfigureAwait(false); } AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); } [Test] - public void TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreDeleted() + public async Task TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreDeleted() { AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); for (int i = 0; i < 1000; i++) { string x = Guid.NewGuid().ToString(); - _model.ExchangeDeclare(x, "fanout", false, true, null); - QueueDeclareOk q = _model.QueueDeclare(); - _model.QueueBind(q, x, ""); - _model.QueueDelete(q); + await _channel.DeclareExchangeAsync(x, "fanout", false, true).ConfigureAwait(false); + (string q, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); + await _channel.BindQueueAsync(q, x, "").ConfigureAwait(false); + await _channel.DeleteQueueAsync(q).ConfigureAwait(false); } AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); } [Test] - public void TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreUnbound() + public async Task TestDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreUnbound() { AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); for (int i = 0; i < 1000; i++) { string x = Guid.NewGuid().ToString(); - _model.ExchangeDeclare(x, "fanout", false, true, null); - QueueDeclareOk q = _model.QueueDeclare(); - _model.QueueBind(q, x, ""); - _model.QueueUnbind(q, x, "", null); + await _channel.DeclareExchangeAsync(x, "fanout", false, true).ConfigureAwait(false); + (string q, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); + await _channel.BindQueueAsync(q, x, "").ConfigureAwait(false); + await _channel.UnbindQueueAsync(q, x, "").ConfigureAwait(false); } AssertRecordedExchanges((AutorecoveringConnection)_conn, 0); } [Test] - public void TestDeclarationOfManyAutoDeleteQueuesWithTransientConsumer() + public async Task TestDeclarationOfManyAutoDeleteQueuesWithTransientConsumer() { AssertRecordedQueues((AutorecoveringConnection)_conn, 0); for (int i = 0; i < 1000; i++) { string q = Guid.NewGuid().ToString(); - _model.QueueDeclare(q, false, false, true, null); - var dummy = new EventingBasicConsumer(_model); - string tag = _model.BasicConsume(q, true, dummy); - _model.BasicCancel(tag); + await _channel.DeclareQueueAsync(q, false, false, true).ConfigureAwait(false); + var dummy = new EventingBasicConsumer(_channel); + string tag = await _channel.ActivateConsumerAsync(dummy, q, true).ConfigureAwait(false); + await _channel.CancelConsumerAsync(tag).ConfigureAwait(false); } AssertRecordedQueues((AutorecoveringConnection)_conn, 0); } [Test] - public void TestExchangeRecovery() + public async Task TestExchangeRecovery() { string x = "dotnet-client.test.recovery.x1"; - DeclareNonDurableExchange(_model, x); + await _channel.DeclareExchangeAsync(x, "fanout", false).ConfigureAwait(false); CloseAndWaitForRecovery(); - AssertExchangeRecovery(_model, x); - _model.ExchangeDelete(x); + await AssertExchangeRecoveryAsync(_channel, x).ConfigureAwait(false); + await _channel.DeleteExchangeAsync(x).ConfigureAwait(false); } [Test] - public void TestExchangeRecoveryWithNoWait() + public async Task TestExchangeRecoveryWithNoWait() { string x = "dotnet-client.test.recovery.x1-nowait"; - DeclareNonDurableExchangeNoWait(_model, x); + await _channel.DeclareExchangeAsync(x, "fanout", false, false, waitForConfirmation: false); CloseAndWaitForRecovery(); - AssertExchangeRecovery(_model, x); - _model.ExchangeDelete(x); + await AssertExchangeRecoveryAsync(_channel, x).ConfigureAwait(false); + await _channel.DeleteExchangeAsync(x).ConfigureAwait(false); } [Test] - public void TestExchangeToExchangeBindingRecovery() + public async Task TestExchangeToExchangeBindingRecovery() { - string q = _model.QueueDeclare("", false, false, false, null).QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync("", false, false, false).ConfigureAwait(false); string x1 = "amq.fanout"; string x2 = GenerateExchangeName(); - _model.ExchangeDeclare(x2, "fanout"); - _model.ExchangeBind(x1, x2, ""); - _model.QueueBind(q, x1, ""); + await _channel.DeclareExchangeAsync(x2, "fanout").ConfigureAwait(false); + await _channel.BindExchangeAsync(x1, x2, "").ConfigureAwait(false); + await _channel.BindQueueAsync(q, x1, "").ConfigureAwait(false); try { CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); - _model.BasicPublish(x2, "", null, _encoding.GetBytes("msg")); - AssertMessageCount(q, 1); + Assert.IsTrue(_channel.IsOpen); + await _channel.PublishMessageAsync(x2, "", null, _encoding.GetBytes("msg")).ConfigureAwait(false); + await AssertMessageCountAsync(q, 1).ConfigureAwait(false); } finally { - WithTemporaryModel(m => + await WithTemporaryChannelAsync(async ch => { - m.ExchangeDelete(x2); - m.QueueDelete(q); + await ch.DeleteExchangeAsync(x2).ConfigureAwait(false); + await ch.DeleteQueueAsync(q).ConfigureAwait(false); }); } } [Test] - public void TestQueueRecoveryWithManyQueues() + public async Task TestQueueRecoveryWithManyQueues() { var qs = new List(); int n = 1024; for (int i = 0; i < n; i++) { - qs.Add(_model.QueueDeclare(GenerateQueueName(), false, false, false, null).QueueName); + (string queueName, _, _) = await _channel.DeclareQueueAsync(GenerateQueueName(), false, false, false).ConfigureAwait(false); + qs.Add(queueName); } CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); foreach (string q in qs) { - AssertQueueRecovery(_model, q, false); - _model.QueueDelete(q); + await AssertQueueRecoveryAsync(_channel, q, false).ConfigureAwait(false); + await _channel.DeleteQueueAsync(q).ConfigureAwait(false); } } // rabbitmq/rabbitmq-dotnet-client#43 [Test] - public void TestClientNamedTransientAutoDeleteQueueAndBindingRecovery() + public async Task TestClientNamedTransientAutoDeleteQueueAndBindingRecovery() { string q = Guid.NewGuid().ToString(); string x = "tmp-fanout"; - IModel ch = _conn.CreateModel(); - ch.QueueDelete(q); - ch.ExchangeDelete(x); - ch.ExchangeDeclare(exchange: x, type: "fanout"); - ch.QueueDeclare(queue: q, durable: false, exclusive: false, autoDelete: true, arguments: null); - ch.QueueBind(queue: q, exchange: x, routingKey: ""); + IChannel ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + await ch.DeleteQueueAsync(q).ConfigureAwait(false); + await ch.DeleteExchangeAsync(x).ConfigureAwait(false); + await ch.DeclareExchangeAsync(exchange: x, type: "fanout").ConfigureAwait(false); + await ch.DeclareQueueAsync(queue: q, durable: false, exclusive: false, autoDelete: true, arguments: null).ConfigureAwait(false); + await ch.BindQueueAsync(queue: q, exchange: x, routingKey: "").ConfigureAwait(false); RestartServerAndWaitForRecovery(); Assert.IsTrue(ch.IsOpen); - ch.ConfirmSelect(); - ch.QueuePurge(q); - ch.ExchangeDeclare(exchange: x, type: "fanout"); - ch.BasicPublish(exchange: x, routingKey: "", basicProperties: null, body: _encoding.GetBytes("msg")); + await ch.ActivatePublishTagsAsync().ConfigureAwait(false); + await ch.PurgeQueueAsync(q).ConfigureAwait(false); + await ch.DeclareExchangeAsync(exchange: x, type: "fanout").ConfigureAwait(false); + await ch.PublishMessageAsync(exchange: x, routingKey: "", basicProperties: null, body: _encoding.GetBytes("msg")).ConfigureAwait(false); WaitForConfirms(ch); - QueueDeclareOk ok = ch.QueueDeclare(queue: q, durable: false, exclusive: false, autoDelete: true, arguments: null); - Assert.AreEqual(1, ok.MessageCount); - ch.QueueDelete(q); - ch.ExchangeDelete(x); + uint messageCount = await ch.GetQueueMessageCountAsync(q).ConfigureAwait(false); + Assert.AreEqual(1, messageCount); + await ch.DeleteQueueAsync(q).ConfigureAwait(false); + await ch.DeleteExchangeAsync(x).ConfigureAwait(false); } // rabbitmq/rabbitmq-dotnet-client#43 [Test] - public void TestServerNamedTransientAutoDeleteQueueAndBindingRecovery() + public async Task TestServerNamedTransientAutoDeleteQueueAndBindingRecovery() { string x = "tmp-fanout"; - IModel ch = _conn.CreateModel(); - ch.ExchangeDelete(x); - ch.ExchangeDeclare(exchange: x, type: "fanout"); - string q = ch.QueueDeclare(queue: "", durable: false, exclusive: false, autoDelete: true, arguments: null).QueueName; + IChannel ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + await ch.DeleteExchangeAsync(x).ConfigureAwait(false); + await ch.DeclareExchangeAsync(exchange: x, type: "fanout").ConfigureAwait(false); + (string q, _, _) = await ch.DeclareQueueAsync(queue: "", durable: false, exclusive: false, autoDelete: true).ConfigureAwait(false); string nameBefore = q; string nameAfter = null; var latch = new ManualResetEventSlim(false); @@ -591,26 +569,26 @@ public void TestServerNamedTransientAutoDeleteQueueAndBindingRecovery() nameAfter = ea.NameAfter; latch.Set(); }; - ch.QueueBind(queue: nameBefore, exchange: x, routingKey: ""); + await ch.BindQueueAsync(queue: nameBefore, exchange: x, routingKey: "").ConfigureAwait(false); RestartServerAndWaitForRecovery(); Wait(latch); Assert.IsTrue(ch.IsOpen); Assert.AreNotEqual(nameBefore, nameAfter); - ch.ConfirmSelect(); - ch.ExchangeDeclare(exchange: x, type: "fanout"); - ch.BasicPublish(exchange: x, routingKey: "", basicProperties: null, body: _encoding.GetBytes("msg")); + await ch.ActivatePublishTagsAsync().ConfigureAwait(false); + await ch.DeclareExchangeAsync(exchange: x, type: "fanout").ConfigureAwait(false); + await ch.PublishMessageAsync(exchange: x, routingKey: "", basicProperties: null, body: _encoding.GetBytes("msg")).ConfigureAwait(false); WaitForConfirms(ch); - QueueDeclareOk ok = ch.QueueDeclarePassive(nameAfter); - Assert.AreEqual(1, ok.MessageCount); - ch.QueueDelete(q); - ch.ExchangeDelete(x); + uint messageCount = await ch.GetQueueMessageCountAsync(nameAfter).ConfigureAwait(false); + Assert.AreEqual(1, messageCount); + await ch.DeleteQueueAsync(q).ConfigureAwait(false); + await ch.DeleteExchangeAsync(x).ConfigureAwait(false); } [Test] public void TestRecoveryEventHandlersOnChannel() { int counter = 0; - ((AutorecoveringModel)_model).Recovery += (source, ea) => Interlocked.Increment(ref counter); + ((AutorecoveringChannel)_channel).Recovery += (source, ea) => System.Threading.Interlocked.Increment(ref counter); CloseAndWaitForRecovery(); CloseAndWaitForRecovery(); @@ -620,51 +598,21 @@ public void TestRecoveryEventHandlersOnChannel() } [Test] - public void TestRecoveryEventHandlersOnConnection() - { - int counter = 0; - ((AutorecoveringConnection)_conn).RecoverySucceeded += (source, ea) => Interlocked.Increment(ref counter); - - CloseAndWaitForRecovery(); - CloseAndWaitForRecovery(); - CloseAndWaitForRecovery(); - CloseAndWaitForRecovery(); - Assert.IsTrue(_conn.IsOpen); - - Assert.IsTrue(counter >= 3); - } - - [Test] - public void TestRecoveryEventHandlersOnModel() - { - int counter = 0; - ((AutorecoveringModel)_model).Recovery += (source, ea) => Interlocked.Increment(ref counter); - - CloseAndWaitForRecovery(); - CloseAndWaitForRecovery(); - CloseAndWaitForRecovery(); - CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); - - Assert.IsTrue(counter >= 3); - } - - [Test] - public void TestRecoveryWithTopologyDisabled() + public async Task TestRecoveryWithTopologyDisabled() { AutorecoveringConnection conn = CreateAutorecoveringConnectionWithTopologyRecoveryDisabled(); - IModel ch = conn.CreateModel(); + IChannel ch = await conn.CreateChannelAsync().ConfigureAwait(false); string s = "dotnet-client.test.recovery.q2"; - ch.QueueDelete(s); - ch.QueueDeclare(s, false, true, false, null); - ch.QueueDeclarePassive(s); + await ch.DeleteQueueAsync(s).ConfigureAwait(false); + await ch.DeclareQueueAsync(s, false, true, false).ConfigureAwait(false); + await ch.DeclareQueuePassiveAsync(s).ConfigureAwait(false); Assert.IsTrue(ch.IsOpen); try { CloseAndWaitForRecovery(conn); Assert.IsTrue(ch.IsOpen); - ch.QueueDeclarePassive(s); + await ch.DeclareQueuePassiveAsync(s).ConfigureAwait(false); Assert.Fail("Expected an exception"); } catch (OperationInterruptedException) @@ -678,11 +626,11 @@ public void TestRecoveryWithTopologyDisabled() } [Test] - public void TestServerNamedQueueRecovery() + public async Task TestServerNamedQueueRecovery() { - string q = _model.QueueDeclare("", false, false, false, null).QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync("", false, false, false).ConfigureAwait(false); string x = "amq.fanout"; - _model.QueueBind(q, x, ""); + await _channel.BindQueueAsync(q, x, "").ConfigureAwait(false); string nameBefore = q; string nameAfter = null; @@ -700,14 +648,14 @@ public void TestServerNamedQueueRecovery() Assert.IsTrue(nameAfter.StartsWith("amq.")); Assert.AreNotEqual(nameBefore, nameAfter); - _model.QueueDeclarePassive(nameAfter); + await _channel.DeclareQueuePassiveAsync(nameAfter).ConfigureAwait(false); } [Test] public void TestShutdownEventHandlersRecoveryOnConnection() { int counter = 0; - _conn.ConnectionShutdown += (c, args) => Interlocked.Increment(ref counter); + _conn.ConnectionShutdown += (c, args) => System.Threading.Interlocked.Increment(ref counter); Assert.IsTrue(_conn.IsOpen); CloseAndWaitForRecovery(); @@ -723,7 +671,7 @@ public void TestShutdownEventHandlersRecoveryOnConnection() public void TestShutdownEventHandlersRecoveryOnConnectionAfterDelayedServerRestart() { int counter = 0; - _conn.ConnectionShutdown += (c, args) => Interlocked.Increment(ref counter); + _conn.ConnectionShutdown += (c, args) => System.Threading.Interlocked.Increment(ref counter); ManualResetEventSlim shutdownLatch = PrepareForShutdown(_conn); ManualResetEventSlim recoveryLatch = PrepareForRecovery((AutorecoveringConnection)_conn); @@ -740,79 +688,79 @@ public void TestShutdownEventHandlersRecoveryOnConnectionAfterDelayedServerResta } [Test] - public void TestShutdownEventHandlersRecoveryOnModel() + public void TestShutdownEventHandlersRecoveryOnChannel() { int counter = 0; - _model.ModelShutdown += (c, args) => Interlocked.Increment(ref counter); + _channel.Shutdown += args => System.Threading.Interlocked.Increment(ref counter); - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); CloseAndWaitForRecovery(); CloseAndWaitForRecovery(); CloseAndWaitForRecovery(); CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); + Assert.IsTrue(_channel.IsOpen); - Assert.IsTrue(counter >= 3); + Assert.GreaterOrEqual(3, counter); } [Test] - public void TestThatCancelledConsumerDoesNotReappearOnRecovery() + public async Task TestThatCancelledConsumerDoesNotReappearOnRecovery() { - string q = _model.QueueDeclare(GenerateQueueName(), false, false, false, null).QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync(GenerateQueueName(), false, false, false).ConfigureAwait(false); int n = 1024; for (int i = 0; i < n; i++) { - var cons = new EventingBasicConsumer(_model); - string tag = _model.BasicConsume(q, true, cons); - _model.BasicCancel(tag); + var cons = new EventingBasicConsumer(_channel); + string tag = await _channel.ActivateConsumerAsync(cons, q, true); + await _channel.CancelConsumerAsync(tag).ConfigureAwait(false); } CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); - AssertConsumerCount(q, 0); + Assert.IsTrue(_channel.IsOpen); + await AssertConsumerCountAsync(q, 0); } [Test] - public void TestThatDeletedExchangeBindingsDontReappearOnRecovery() + public async Task TestThatDeletedExchangeBindingsDontReappearOnRecovery() { - string q = _model.QueueDeclare("", false, false, false, null).QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync("", false, false, false).ConfigureAwait(false); string x1 = "amq.fanout"; string x2 = GenerateExchangeName(); - _model.ExchangeDeclare(x2, "fanout"); - _model.ExchangeBind(x1, x2, ""); - _model.QueueBind(q, x1, ""); - _model.ExchangeUnbind(x1, x2, "", null); + await _channel.DeclareExchangeAsync(x2, "fanout").ConfigureAwait(false); + await _channel.BindExchangeAsync(x1, x2, "").ConfigureAwait(false); + await _channel.BindQueueAsync(q, x1, "").ConfigureAwait(false); + await _channel.UnbindExchangeAsync(x1, x2, "").ConfigureAwait(false); try { CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); - _model.BasicPublish(x2, "", null, _encoding.GetBytes("msg")); - AssertMessageCount(q, 0); + Assert.IsTrue(_channel.IsOpen); + await _channel.PublishMessageAsync(x2, "", null, _encoding.GetBytes("msg")).ConfigureAwait(false); + await AssertMessageCountAsync(q, 0).ConfigureAwait(false); } finally { - WithTemporaryModel(m => + await WithTemporaryChannelAsync(async ch => { - m.ExchangeDelete(x2); - m.QueueDelete(q); + await ch.DeleteExchangeAsync(x2).ConfigureAwait(false); + await ch.DeleteQueueAsync(q).ConfigureAwait(false); }); } } [Test] - public void TestThatDeletedExchangesDontReappearOnRecovery() + public async Task TestThatDeletedExchangesDontReappearOnRecovery() { string x = GenerateExchangeName(); - _model.ExchangeDeclare(x, "fanout"); - _model.ExchangeDelete(x); + await _channel.DeclareExchangeAsync(x, "fanout").ConfigureAwait(false); + await _channel.DeleteExchangeAsync(x).ConfigureAwait(false); try { CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); - _model.ExchangeDeclarePassive(x); + Assert.IsTrue(_channel.IsOpen); + await _channel.DeclareExchangePassiveAsync(x).ConfigureAwait(false); Assert.Fail("Expected an exception"); } catch (OperationInterruptedException e) @@ -823,46 +771,46 @@ public void TestThatDeletedExchangesDontReappearOnRecovery() } [Test] - public void TestThatDeletedQueueBindingsDontReappearOnRecovery() + public async Task TestThatDeletedQueueBindingsDontReappearOnRecovery() { - string q = _model.QueueDeclare("", false, false, false, null).QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync("", false, false, false).ConfigureAwait(false); string x1 = "amq.fanout"; string x2 = GenerateExchangeName(); - _model.ExchangeDeclare(x2, "fanout"); - _model.ExchangeBind(x1, x2, ""); - _model.QueueBind(q, x1, ""); - _model.QueueUnbind(q, x1, "", null); + await _channel.DeclareExchangeAsync(x2, "fanout").ConfigureAwait(false); + await _channel.BindExchangeAsync(x1, x2, "").ConfigureAwait(false); + await _channel.BindQueueAsync(q, x1, "").ConfigureAwait(false); + await _channel.UnbindQueueAsync(q, x1, "").ConfigureAwait(false); try { CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); - _model.BasicPublish(x2, "", null, _encoding.GetBytes("msg")); - AssertMessageCount(q, 0); + Assert.IsTrue(_channel.IsOpen); + await _channel.PublishMessageAsync(x2, "", null, _encoding.GetBytes("msg")).ConfigureAwait(false); + await AssertMessageCountAsync(q, 0); } finally { - WithTemporaryModel(m => + await WithTemporaryChannelAsync(async ch => { - m.ExchangeDelete(x2); - m.QueueDelete(q); + await ch.DeleteExchangeAsync(x2).ConfigureAwait(false); + await ch.DeleteQueueAsync(q).ConfigureAwait(false); }); } } [Test] - public void TestThatDeletedQueuesDontReappearOnRecovery() + public async Task TestThatDeletedQueuesDontReappearOnRecovery() { string q = "dotnet-client.recovery.q1"; - _model.QueueDeclare(q, false, false, false, null); - _model.QueueDelete(q); + await _channel.DeclareQueueAsync(q, false, false, false).ConfigureAwait(false); + await _channel.DeleteQueueAsync(q).ConfigureAwait(false); try { CloseAndWaitForRecovery(); - Assert.IsTrue(_model.IsOpen); - _model.QueueDeclarePassive(q); + Assert.IsTrue(_channel.IsOpen); + await _channel.DeclareQueuePassiveAsync(q).ConfigureAwait(false); Assert.Fail("Expected an exception"); } catch (OperationInterruptedException e) @@ -873,47 +821,42 @@ public void TestThatDeletedQueuesDontReappearOnRecovery() } [Test] - public void TestUnblockedListenersRecovery() + public async Task TestUnblockedListenersRecovery() { var latch = new ManualResetEventSlim(false); _conn.ConnectionUnblocked += (source, ea) => latch.Set(); CloseAndWaitForRecovery(); CloseAndWaitForRecovery(); - Block(); + await BlockAsync().ConfigureAwait(false); Unblock(); Wait(latch); } - internal void AssertExchangeRecovery(IModel m, string x) + internal async Task AssertExchangeRecoveryAsync(IChannel ch, string x) { - m.ConfirmSelect(); - WithTemporaryNonExclusiveQueue(m, (_, q) => + await ch.ActivatePublishTagsAsync().ConfigureAwait(false); + await WithTemporaryNonExclusiveQueueAsync(ch, async (_, q) => { string rk = "routing-key"; - m.QueueBind(q, x, rk); + await ch.BindQueueAsync(q, x, rk).ConfigureAwait(false); byte[] mb = RandomMessageBody(); - m.BasicPublish(x, rk, null, mb); + await ch.PublishMessageAsync(x, rk, null, mb).ConfigureAwait(false); - Assert.IsTrue(WaitForConfirms(m)); - m.ExchangeDeclarePassive(x); + Assert.IsTrue(WaitForConfirms(ch)); + await ch.DeclareExchangePassiveAsync(x).ConfigureAwait(false); }); } - internal void AssertQueueRecovery(IModel m, string q) + internal async Task AssertQueueRecoveryAsync(IChannel ch, string q, bool exclusive = true) { - AssertQueueRecovery(m, q, true); - } - - internal void AssertQueueRecovery(IModel m, string q, bool exclusive) - { - m.ConfirmSelect(); - m.QueueDeclarePassive(q); - QueueDeclareOk ok1 = m.QueueDeclare(q, false, exclusive, false, null); + await ch.ActivatePublishTagsAsync().ConfigureAwait(false); + await ch.DeclareQueuePassiveAsync(q).ConfigureAwait(false); + var ok1 = await ch.DeclareQueueAsync(q, false, exclusive, false).ConfigureAwait(false); Assert.AreEqual(ok1.MessageCount, 0); - m.BasicPublish("", q, null, _encoding.GetBytes("")); - Assert.IsTrue(WaitForConfirms(m)); - QueueDeclareOk ok2 = m.QueueDeclare(q, false, exclusive, false, null); + await ch.PublishMessageAsync("", q, null, _encoding.GetBytes("")).ConfigureAwait(false); + Assert.IsTrue(WaitForConfirms(ch)); + var ok2 = await ch.DeclareQueueAsync(q, false, exclusive, false).ConfigureAwait(false); Assert.AreEqual(ok2.MessageCount, 1); } @@ -927,11 +870,6 @@ internal void AssertRecordedQueues(AutorecoveringConnection c, int n) Assert.AreEqual(n, c.RecordedQueuesCount); } - internal void CloseAllAndWaitForRecovery() - { - CloseAllAndWaitForRecovery((AutorecoveringConnection)_conn); - } - internal void CloseAllAndWaitForRecovery(AutorecoveringConnection conn) { ManualResetEventSlim rl = PrepareForRecovery(conn); @@ -953,13 +891,6 @@ internal void CloseAndWaitForRecovery(AutorecoveringConnection conn) Wait(rl); } - internal void CloseAndWaitForShutdown(AutorecoveringConnection conn) - { - ManualResetEventSlim sl = PrepareForShutdown(conn); - CloseConnection(conn); - Wait(sl); - } - internal ManualResetEventSlim PrepareForRecovery(AutorecoveringConnection conn) { var latch = new ManualResetEventSlim(false); @@ -995,42 +926,27 @@ internal void RestartServerAndWaitForRecovery(AutorecoveringConnection conn) Wait(rl); } - internal void TestDelayedBasicAckNackAfterChannelRecovery(TestBasicConsumer1 cons, ManualResetEventSlim latch) + internal async Task TestDelayedBasicAckNackAfterChannelRecovery(TestBasicConsumer1 cons, ManualResetEventSlim latch) { - string q = _model.QueueDeclare(GenerateQueueName(), false, false, false, null).QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync(GenerateQueueName(), false, false, false).ConfigureAwait(false); int n = 30; - _model.BasicQos(0, 1, false); - _model.BasicConsume(q, false, cons); + await _channel.SetQosAsync(0, 1, false).ConfigureAwait(false); + await _channel.ActivateConsumerAsync(cons, q, false).ConfigureAwait(false); AutorecoveringConnection publishingConn = CreateAutorecoveringConnection(); - IModel publishingModel = publishingConn.CreateModel(); + IChannel publishingChannel = await publishingConn.CreateChannelAsync().ConfigureAwait(false); for (int i = 0; i < n; i++) { - publishingModel.BasicPublish("", q, null, _encoding.GetBytes("")); + await publishingChannel.PublishMessageAsync("", q, null, _encoding.GetBytes("")).ConfigureAwait(false); } Wait(latch, TimeSpan.FromSeconds(20)); - _model.QueueDelete(q); - publishingModel.Close(); + await _channel.DeleteQueueAsync(q); + await publishingChannel.CloseAsync().ConfigureAwait(false); publishingConn.Close(); } - internal void WaitForRecovery() - { - Wait(PrepareForRecovery((AutorecoveringConnection)_conn)); - } - - internal void WaitForRecovery(AutorecoveringConnection conn) - { - Wait(PrepareForRecovery(conn)); - } - - internal void WaitForShutdown() - { - Wait(PrepareForShutdown(_conn)); - } - internal void WaitForShutdown(IConnection conn) { Wait(PrepareForShutdown(conn)); @@ -1038,51 +954,37 @@ internal void WaitForShutdown(IConnection conn) public class AckingBasicConsumer : TestBasicConsumer1 { - public AckingBasicConsumer(IModel model, ManualResetEventSlim latch, Action fn) - : base(model, latch, fn) + public AckingBasicConsumer(IChannel channel, ManualResetEventSlim latch, Action fn) + : base(channel, latch, fn) { } public override void PostHandleDelivery(ulong deliveryTag) { - Model.BasicAck(deliveryTag, false); + Channel.AckMessageAsync(deliveryTag, false).AsTask().GetAwaiter().GetResult(); } } public class NackingBasicConsumer : TestBasicConsumer1 { - public NackingBasicConsumer(IModel model, ManualResetEventSlim latch, Action fn) - : base(model, latch, fn) - { - } - - public override void PostHandleDelivery(ulong deliveryTag) - { - Model.BasicNack(deliveryTag, false, false); - } - } - - public class RejectingBasicConsumer : TestBasicConsumer1 - { - public RejectingBasicConsumer(IModel model, ManualResetEventSlim latch, Action fn) - : base(model, latch, fn) + public NackingBasicConsumer(IChannel channel, ManualResetEventSlim latch, Action fn) + : base(channel, latch, fn) { } public override void PostHandleDelivery(ulong deliveryTag) { - Model.BasicReject(deliveryTag, false); + Channel.NackMessageAsync(deliveryTag, false, false).AsTask().GetAwaiter().GetResult(); } } - public class TestBasicConsumer1 : DefaultBasicConsumer { private readonly Action _action; private readonly ManualResetEventSlim _latch; private ushort _counter = 0; - public TestBasicConsumer1(IModel model, ManualResetEventSlim latch, Action fn) - : base(model) + public TestBasicConsumer1(IChannel channel, ManualResetEventSlim latch, Action fn) + : base(channel) { _latch = latch; _action = fn; diff --git a/projects/Unit/TestConnectionShutdown.cs b/projects/Unit/TestConnectionShutdown.cs index a4c0c782d8..af2cc533e5 100644 --- a/projects/Unit/TestConnectionShutdown.cs +++ b/projects/Unit/TestConnectionShutdown.cs @@ -33,8 +33,7 @@ using System.Threading; using NUnit.Framework; - -using RabbitMQ.Client.Impl; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { @@ -46,9 +45,7 @@ public void TestShutdownSignalPropagationToChannels() { var latch = new ManualResetEventSlim(false); - _model.ModelShutdown += (model, args) => { - latch.Set(); - }; + _channel.Shutdown += args => latch.Set(); _conn.Close(); Wait(latch, TimeSpan.FromSeconds(3)); @@ -57,17 +54,15 @@ public void TestShutdownSignalPropagationToChannels() [Test] public void TestConsumerDispatcherShutdown() { - var m = (AutorecoveringModel)_model; + var ch = (AutorecoveringChannel)_channel; var latch = new ManualResetEventSlim(false); - _model.ModelShutdown += (model, args) => - { - latch.Set(); - }; - Assert.IsFalse(m.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close"); + _channel.Shutdown += args => latch.Set(); + + Assert.IsFalse(ch.ConsumerDispatcher.IsShutdown, "dispatcher should NOT be shut down before Close"); _conn.Close(); Wait(latch, TimeSpan.FromSeconds(3)); - Assert.IsTrue(m.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close"); + Assert.IsTrue(ch.ConsumerDispatcher.IsShutdown, "dispatcher should be shut down after Close"); } } } diff --git a/projects/Unit/TestConsumer.cs b/projects/Unit/TestConsumer.cs index ccb98a7789..07fa2421ad 100644 --- a/projects/Unit/TestConsumer.cs +++ b/projects/Unit/TestConsumer.cs @@ -3,7 +3,9 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; +using RabbitMQ.Client.Framing; namespace RabbitMQ.Client.Unit { @@ -15,18 +17,18 @@ public async Task TestBasicRoundtripConcurrent() { var cf = new ConnectionFactory{ ConsumerDispatchConcurrency = 2 }; using(IConnection c = cf.CreateConnection()) - using(IModel m = c.CreateModel()) + await using(IChannel channel = await c.CreateChannelAsync().ConfigureAwait(false)) { - QueueDeclareOk q = m.QueueDeclare(); - IBasicProperties bp = m.CreateBasicProperties(); + (string queueName, _, _) = await channel.DeclareQueueAsync().ConfigureAwait(false); + BasicProperties bp = new BasicProperties(); const string publish1 = "sync-hi-1"; byte[] body = Encoding.UTF8.GetBytes(publish1); - m.BasicPublish("", q.QueueName, bp, body); + await channel.PublishMessageAsync("", queueName, bp, body).ConfigureAwait(false); const string publish2 = "sync-hi-2"; body = Encoding.UTF8.GetBytes(publish2); - m.BasicPublish("", q.QueueName, bp, body); + await channel.PublishMessageAsync("", queueName, bp, body).ConfigureAwait(false); - var consumer = new EventingBasicConsumer(m); + var consumer = new EventingBasicConsumer(channel); var publish1SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var publish2SyncSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -53,7 +55,7 @@ public async Task TestBasicRoundtripConcurrent() } }; - m.BasicConsume(q.QueueName, true, consumer); + await channel.ActivateConsumerAsync(consumer, queueName, true).ConfigureAwait(false); await Task.WhenAll(publish1SyncSource.Task, publish2SyncSource.Task); diff --git a/projects/Unit/TestConsumerCancelNotify.cs b/projects/Unit/TestConsumerCancelNotify.cs index 7d72fcb727..7e5d98b413 100644 --- a/projects/Unit/TestConsumerCancelNotify.cs +++ b/projects/Unit/TestConsumerCancelNotify.cs @@ -31,9 +31,9 @@ using System.Linq; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client.Unit @@ -41,63 +41,63 @@ namespace RabbitMQ.Client.Unit [TestFixture] public class TestConsumerCancelNotify : IntegrationFixture { - protected readonly object lockObject = new object(); - protected bool notifiedCallback; - protected bool notifiedEvent; - protected string consumerTag; + private readonly ManualResetEventSlim _latch = new ManualResetEventSlim(false); + private bool _notifiedCallback; + private bool _notifiedEvent; + private string _consumerTag; [Test] - public void TestConsumerCancelNotification() + public Task TestConsumerCancelNotification() { - TestConsumerCancel("queue_consumer_cancel_notify", false, ref notifiedCallback); + return TestConsumerCancelAsync("queue_consumer_cancel_notify", false); } [Test] - public void TestConsumerCancelEvent() + public Task TestConsumerCancelEvent() { - TestConsumerCancel("queue_consumer_cancel_event", true, ref notifiedEvent); + return TestConsumerCancelAsync("queue_consumer_cancel_event", true); } [Test] - public void TestCorrectConsumerTag() + public async Task TestCorrectConsumerTag() { string q1 = GenerateQueueName(); string q2 = GenerateQueueName(); - _model.QueueDeclare(q1, false, false, false, null); - _model.QueueDeclare(q2, false, false, false, null); + await _channel.DeclareQueueAsync(q1, false, false, false).ConfigureAwait(false); + await _channel.DeclareQueueAsync(q2, false, false, false).ConfigureAwait(false); - EventingBasicConsumer consumer = new EventingBasicConsumer(_model); - string consumerTag1 = _model.BasicConsume(q1, true, consumer); - string consumerTag2 = _model.BasicConsume(q2, true, consumer); + EventingBasicConsumer consumer = new EventingBasicConsumer(_channel); + string consumerTag1 = await _channel.ActivateConsumerAsync(consumer, q1, true).ConfigureAwait(false); + string consumerTag2 = await _channel.ActivateConsumerAsync(consumer, q2, true).ConfigureAwait(false); string notifiedConsumerTag = null; consumer.ConsumerCancelled += (sender, args) => { - lock (lockObject) - { - notifiedConsumerTag = args.ConsumerTags.First(); - Monitor.PulseAll(lockObject); - } + notifiedConsumerTag = args.ConsumerTags.First(); + _latch.Set(); }; - _model.QueueDelete(q1); - WaitOn(lockObject); + _latch.Reset(); + await _channel.DeleteQueueAsync(q1).ConfigureAwait(false); + Wait(_latch); + Assert.AreEqual(consumerTag1, notifiedConsumerTag); - _model.QueueDelete(q2); + await _channel.DeleteQueueAsync(q2).ConfigureAwait(false); } - public void TestConsumerCancel(string queue, bool EventMode, ref bool notified) + public async Task TestConsumerCancelAsync(string queue, bool EventMode) { - _model.QueueDeclare(queue, false, true, false, null); - IBasicConsumer consumer = new CancelNotificationConsumer(_model, this, EventMode); - string actualConsumerTag = _model.BasicConsume(queue, false, consumer); - - _model.QueueDelete(queue); - WaitOn(lockObject); - Assert.IsTrue(notified); - Assert.AreEqual(actualConsumerTag, consumerTag); + await _channel.DeclareQueueAsync(queue, false, true, false).ConfigureAwait(false); + IBasicConsumer consumer = new CancelNotificationConsumer(_channel, this, EventMode); + string actualConsumerTag = await _channel.ActivateConsumerAsync(consumer, queue, false).ConfigureAwait(false); + + await _channel.DeleteQueueAsync(queue).ConfigureAwait(false); + Wait(_latch); + + Assert.IsTrue(EventMode ? _notifiedEvent : _notifiedCallback); + Assert.AreEqual(actualConsumerTag, _consumerTag); } private class CancelNotificationConsumer : DefaultBasicConsumer @@ -105,8 +105,8 @@ private class CancelNotificationConsumer : DefaultBasicConsumer private readonly TestConsumerCancelNotify _testClass; private readonly bool _eventMode; - public CancelNotificationConsumer(IModel model, TestConsumerCancelNotify tc, bool EventMode) - : base(model) + public CancelNotificationConsumer(IChannel channel, TestConsumerCancelNotify tc, bool EventMode) + : base(channel) { _testClass = tc; _eventMode = EventMode; @@ -114,30 +114,29 @@ public CancelNotificationConsumer(IModel model, TestConsumerCancelNotify tc, boo { ConsumerCancelled += Cancelled; } + + tc._consumerTag = default; + tc._notifiedCallback = default; + tc._notifiedEvent = default; + tc._latch.Reset(); } public override void HandleBasicCancel(string consumerTag) { if (!_eventMode) { - lock (_testClass.lockObject) - { - _testClass.notifiedCallback = true; - _testClass.consumerTag = consumerTag; - Monitor.PulseAll(_testClass.lockObject); - } + _testClass._notifiedCallback = true; + _testClass._consumerTag = consumerTag; + _testClass._latch.Set(); } base.HandleBasicCancel(consumerTag); } private void Cancelled(object sender, ConsumerEventArgs arg) { - lock (_testClass.lockObject) - { - _testClass.notifiedEvent = true; - _testClass.consumerTag = arg.ConsumerTags[0]; - Monitor.PulseAll(_testClass.lockObject); - } + _testClass._notifiedEvent = true; + _testClass._consumerTag = arg.ConsumerTags[0]; + _testClass._latch.Set(); } } } diff --git a/projects/Unit/TestConsumerCount.cs b/projects/Unit/TestConsumerCount.cs index 8a9cb4fe3c..30b001610a 100644 --- a/projects/Unit/TestConsumerCount.cs +++ b/projects/Unit/TestConsumerCount.cs @@ -29,8 +29,10 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client.Unit @@ -38,17 +40,17 @@ namespace RabbitMQ.Client.Unit internal class TestConsumerCount : IntegrationFixture { [Test] - public void TestConsumerCountMethod() + public async Task TestConsumerCountMethod() { string q = GenerateQueueName(); - _model.QueueDeclare(queue: q, durable: false, exclusive: true, autoDelete: false, arguments: null); - Assert.AreEqual(0, _model.ConsumerCount(q)); + await _channel.DeclareQueueAsync(q, false, true, false).ConfigureAwait(false); + Assert.AreEqual(0, await _channel.GetQueueConsumerCountAsync(q).ConfigureAwait(false)); - string tag = _model.BasicConsume(q, true, new EventingBasicConsumer(_model)); - Assert.AreEqual(1, _model.ConsumerCount(q)); + string tag = await _channel.ActivateConsumerAsync(new EventingBasicConsumer(_channel), q, true).ConfigureAwait(false); + Assert.AreEqual(1, await _channel.GetQueueConsumerCountAsync(q).ConfigureAwait(false)); - _model.BasicCancel(tag); - Assert.AreEqual(0, _model.ConsumerCount(q)); + await _channel.CancelConsumerAsync(tag).ConfigureAwait(false); + Assert.AreEqual(0, await _channel.GetQueueConsumerCountAsync(q).ConfigureAwait(false)); } } } diff --git a/projects/Unit/TestConsumerExceptions.cs b/projects/Unit/TestConsumerExceptions.cs index 36795ffbb9..862e6bb68a 100644 --- a/projects/Unit/TestConsumerExceptions.cs +++ b/projects/Unit/TestConsumerExceptions.cs @@ -31,8 +31,9 @@ using System; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { @@ -41,7 +42,7 @@ public class TestConsumerExceptions : IntegrationFixture { private class ConsumerFailingOnDelivery : DefaultBasicConsumer { - public ConsumerFailingOnDelivery(IModel model) : base(model) + public ConsumerFailingOnDelivery(IChannel channel) : base(channel) { } @@ -59,7 +60,7 @@ public override void HandleBasicDeliver(string consumerTag, private class ConsumerFailingOnCancel : DefaultBasicConsumer { - public ConsumerFailingOnCancel(IModel model) : base(model) + public ConsumerFailingOnCancel(IChannel channel) : base(channel) { } @@ -71,7 +72,7 @@ public override void HandleBasicCancel(string consumerTag) private class ConsumerFailingOnShutdown : DefaultBasicConsumer { - public ConsumerFailingOnShutdown(IModel model) : base(model) + public ConsumerFailingOnShutdown(IChannel channel) : base(channel) { } @@ -83,7 +84,7 @@ public override void HandleModelShutdown(object model, ShutdownEventArgs reason) private class ConsumerFailingOnConsumeOk : DefaultBasicConsumer { - public ConsumerFailingOnConsumeOk(IModel model) : base(model) + public ConsumerFailingOnConsumeOk(IChannel channel) : base(channel) { } @@ -95,7 +96,7 @@ public override void HandleBasicConsumeOk(string consumerTag) private class ConsumerFailingOnCancelOk : DefaultBasicConsumer { - public ConsumerFailingOnCancelOk(IModel model) : base(model) + public ConsumerFailingOnCancelOk(IChannel channel) : base(channel) { } @@ -105,60 +106,58 @@ public override void HandleBasicCancelOk(string consumerTag) } } - protected void TestExceptionHandlingWith(IBasicConsumer consumer, - Action action) + protected async Task TestExceptionHandlingWith(IBasicConsumer consumer, Func action) { - object o = new object(); bool notified = false; - string q = _model.QueueDeclare(); - + (string q, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); + var latch = new ManualResetEventSlim(false); - _model.CallbackException += (m, evt) => + _channel.UnhandledExceptionOccurred += (_, __) => { notified = true; - Monitor.PulseAll(o); + latch.Set(); }; - string tag = _model.BasicConsume(q, true, consumer); - action(_model, q, consumer, tag); - WaitOn(o); + string tag = await _channel.ActivateConsumerAsync(consumer, q, true).ConfigureAwait(false); + await action(_channel, q, consumer, tag).ConfigureAwait(false); + Wait(latch); Assert.IsTrue(notified); } [Test] - public void TestCancelNotificationExceptionHandling() + public Task TestCancelNotificationExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnCancel(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.QueueDelete(q)); + IBasicConsumer consumer = new ConsumerFailingOnCancel(_channel); + return TestExceptionHandlingWith(consumer, (channel, q, c, ct) => channel.DeleteQueueAsync(q).AsTask()); } [Test] - public void TestConsumerCancelOkExceptionHandling() + public Task TestConsumerCancelOkExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnCancelOk(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.BasicCancel(ct)); + IBasicConsumer consumer = new ConsumerFailingOnCancelOk(_channel); + return TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.CancelConsumerAsync(ct).AsTask()); } [Test] - public void TestConsumerConsumeOkExceptionHandling() + public Task TestConsumerConsumeOkExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnConsumeOk(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => { }); + IBasicConsumer consumer = new ConsumerFailingOnConsumeOk(_channel); + return TestExceptionHandlingWith(consumer, (m, q, c, ct) => Task.CompletedTask); } [Test] - public void TestConsumerShutdownExceptionHandling() + public Task TestConsumerShutdownExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnShutdown(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.Close()); + IBasicConsumer consumer = new ConsumerFailingOnShutdown(_channel); + return TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.CloseAsync().AsTask()); } [Test] - public void TestDeliveryExceptionHandling() + public Task TestDeliveryExceptionHandling() { - IBasicConsumer consumer = new ConsumerFailingOnDelivery(_model); - TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.BasicPublish("", q, null, _encoding.GetBytes("msg"))); + IBasicConsumer consumer = new ConsumerFailingOnDelivery(_channel); + return TestExceptionHandlingWith(consumer, (m, q, c, ct) => m.PublishMessageAsync("", q, null, _encoding.GetBytes("msg")).AsTask()); } } } diff --git a/projects/Unit/TestConsumerOperationDispatch.cs b/projects/Unit/TestConsumerOperationDispatch.cs index 92d85310b7..848c9a4a3e 100644 --- a/projects/Unit/TestConsumerOperationDispatch.cs +++ b/projects/Unit/TestConsumerOperationDispatch.cs @@ -32,9 +32,9 @@ using System; using System.Collections.Generic; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; using RabbitMQ.Client.Framing; @@ -44,7 +44,7 @@ namespace RabbitMQ.Client.Unit internal class TestConsumerOperationDispatch : IntegrationFixture { private readonly string _x = "dotnet.tests.consumer-operation-dispatch.fanout"; - private readonly List _channels = new List(); + private readonly List _channels = new List(); private readonly List _queues = new List(); private readonly List _consumers = new List(); @@ -59,11 +59,11 @@ internal class TestConsumerOperationDispatch : IntegrationFixture [TearDown] protected override void ReleaseResources() { - foreach (IModel ch in _channels) + foreach (IChannel ch in _channels) { if (ch.IsOpen) { - ch.Close(); + ch.CloseAsync().GetAwaiter().GetResult(); } } _queues.Clear(); @@ -76,15 +76,13 @@ private class CollectingConsumer : DefaultBasicConsumer { public List DeliveryTags { get; } - public CollectingConsumer(IModel model) - : base(model) + public CollectingConsumer(IChannel channel) + : base(channel) { DeliveryTags = new List(); } - public override void HandleBasicDeliver(string consumerTag, - ulong deliveryTag, bool redelivered, string exchange, string routingKey, - IBasicProperties properties, ReadOnlyMemory body) + public override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, IBasicProperties properties, ReadOnlyMemory body) { // we test concurrent dispatch from the moment basic.delivery is returned. // delivery tags have guaranteed ordering and we verify that it is preserved @@ -96,33 +94,32 @@ public override void HandleBasicDeliver(string consumerTag, counter.Signal(); } - Model.BasicAck(deliveryTag: deliveryTag, multiple: false); + Channel.AckMessageAsync(deliveryTag, false).GetAwaiter().GetResult(); } } [Test] - public void TestDeliveryOrderingWithSingleChannel() + public async Task TestDeliveryOrderingWithSingleChannel() { - IModel Ch = _conn.CreateModel(); - Ch.ExchangeDeclare(_x, "fanout", durable: false); + IChannel Ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + await Ch.DeclareExchangeAsync(_x, "fanout", false, false).ConfigureAwait(false); for (int i = 0; i < Y; i++) { - IModel ch = _conn.CreateModel(); - QueueDeclareOk q = ch.QueueDeclare("", durable: false, exclusive: true, autoDelete: true, arguments: null); - ch.QueueBind(queue: q, exchange: _x, routingKey: ""); + IChannel ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + (string q, _, _) = await ch.DeclareQueueAsync("", false, true, true).ConfigureAwait(false); + await ch.BindQueueAsync(q, _x, "").ConfigureAwait(false); _channels.Add(ch); _queues.Add(q); var cons = new CollectingConsumer(ch); _consumers.Add(cons); - ch.BasicConsume(queue: q, autoAck: false, consumer: cons); + await ch.ActivateConsumerAsync(cons, q, false).ConfigureAwait(false); } + byte[] body = _encoding.GetBytes("msg"); for (int i = 0; i < N; i++) { - Ch.BasicPublish(exchange: _x, routingKey: "", - basicProperties: new BasicProperties(), - body: _encoding.GetBytes("msg")); + await Ch.PublishMessageAsync(_x, "", null, body).ConfigureAwait(false); } counter.Wait(TimeSpan.FromSeconds(120)); @@ -144,28 +141,28 @@ public void TestDeliveryOrderingWithSingleChannel() // see rabbitmq/rabbitmq-dotnet-client#61 [Test] - public void TestChannelShutdownDoesNotShutDownDispatcher() + public async Task TestChannelShutdownDoesNotShutDownDispatcher() { - IModel ch1 = _conn.CreateModel(); - IModel ch2 = _conn.CreateModel(); - _model.ExchangeDeclare(_x, "fanout", durable: false); + IChannel ch1 = await _conn.CreateChannelAsync().ConfigureAwait(false); + IChannel ch2 = await _conn.CreateChannelAsync().ConfigureAwait(false); + await _channel.DeclareExchangeAsync(_x, "fanout").ConfigureAwait(false); - string q1 = ch1.QueueDeclare().QueueName; - string q2 = ch2.QueueDeclare().QueueName; - ch2.QueueBind(queue: q2, exchange: _x, routingKey: ""); + (string q1, _, _) = await ch1.DeclareQueueAsync().ConfigureAwait(false); + (string q2, _, _) = await ch2.DeclareQueueAsync().ConfigureAwait(false); + await ch2.BindQueueAsync(q2, _x, "").ConfigureAwait(false); var latch = new ManualResetEventSlim(false); - ch1.BasicConsume(q1, true, new EventingBasicConsumer(ch1)); + await ch1.ActivateConsumerAsync(new EventingBasicConsumer(ch1), q1, true); var c2 = new EventingBasicConsumer(ch2); c2.Received += (object sender, BasicDeliverEventArgs e) => { latch.Set(); }; - ch2.BasicConsume(q2, true, c2); + await ch2.ActivateConsumerAsync(c2, q2, true).ConfigureAwait(false); // closing this channel must not affect ch2 - ch1.Close(); + await ch1.CloseAsync().ConfigureAwait(false); - ch2.BasicPublish(exchange: _x, basicProperties: null, body: _encoding.GetBytes("msg"), routingKey: ""); + await ch2.PublishMessageAsync(_x, "", null, _encoding.GetBytes("msg")); Wait(latch); } @@ -192,18 +189,17 @@ public override void HandleModelShutdown(object model, ShutdownEventArgs reason) } [Test] - public void TestModelShutdownHandler() + public async Task TestModelShutdownHandler() { var latch = new ManualResetEventSlim(false); var duplicateLatch = new ManualResetEventSlim(false); - string q = _model.QueueDeclare().QueueName; + (string q, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); var c = new ShutdownLatchConsumer(latch, duplicateLatch); - _model.BasicConsume(queue: q, autoAck: true, consumer: c); - _model.Close(); + await _channel.ActivateConsumerAsync(c, q, true).ConfigureAwait(false); + await _channel.CloseAsync().ConfigureAwait(false); Wait(latch, TimeSpan.FromSeconds(5)); - Assert.IsFalse(duplicateLatch.Wait(TimeSpan.FromSeconds(5)), - "event handler fired more than once"); + Assert.IsFalse(duplicateLatch.Wait(TimeSpan.FromSeconds(5)), "event handler fired more than once"); } } } diff --git a/projects/Unit/TestContentHeaderCodec.cs b/projects/Unit/TestContentHeaderCodec.cs index 152f94a151..52261de394 100644 --- a/projects/Unit/TestContentHeaderCodec.cs +++ b/projects/Unit/TestContentHeaderCodec.cs @@ -33,12 +33,12 @@ using System.Collections.Generic; using NUnit.Framework; -using RabbitMQ.Util; +using Unit; namespace RabbitMQ.Client.Unit { [TestFixture] - class TestContentHeaderCodec + internal class TestContentHeaderCodec { public void Check(ReadOnlyMemory actual, ReadOnlyMemory expected) { diff --git a/projects/Unit/TestEventingConsumer.cs b/projects/Unit/TestEventingConsumer.cs index b9ecda2b9e..725aaa3bd6 100644 --- a/projects/Unit/TestEventingConsumer.cs +++ b/projects/Unit/TestEventingConsumer.cs @@ -30,27 +30,28 @@ //--------------------------------------------------------------------------- using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestEventingConsumer : IntegrationFixture { - + public class TestEventingConsumer : IntegrationFixture + { [Test] - public void TestEventingConsumerRegistrationEvents() + public async Task TestEventingConsumerRegistrationEvents() { - string q = _model.QueueDeclare(); + (string q, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); var registeredLatch = new ManualResetEventSlim(false); object registeredSender = null; var unregisteredLatch = new ManualResetEventSlim(false); object unregisteredSender = null; - EventingBasicConsumer ec = new EventingBasicConsumer(_model); + EventingBasicConsumer ec = new EventingBasicConsumer(_channel); ec.Registered += (s, args) => { registeredSender = s; @@ -63,46 +64,46 @@ public void TestEventingConsumerRegistrationEvents() unregisteredLatch.Set(); }; - string tag = _model.BasicConsume(q, false, ec); + string tag = await _channel.ActivateConsumerAsync(ec, q, false).ConfigureAwait(false); Wait(registeredLatch); Assert.IsNotNull(registeredSender); Assert.AreEqual(ec, registeredSender); - Assert.AreEqual(_model, ((EventingBasicConsumer)registeredSender).Model); + Assert.AreEqual(_channel, ((EventingBasicConsumer)registeredSender).Channel); - _model.BasicCancel(tag); + await _channel.CancelConsumerAsync(tag).ConfigureAwait(false); Wait(unregisteredLatch); Assert.IsNotNull(unregisteredSender); Assert.AreEqual(ec, unregisteredSender); - Assert.AreEqual(_model, ((EventingBasicConsumer)unregisteredSender).Model); + Assert.AreEqual(_channel, ((EventingBasicConsumer)unregisteredSender).Channel); } [Test] - public void TestEventingConsumerDeliveryEvents() + public async Task TestEventingConsumerDeliveryEvents() { - string q = _model.QueueDeclare(); - object o = new object(); + (string q, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); + var latch = new ManualResetEventSlim(false); bool receivedInvoked = false; object receivedSender = null; - EventingBasicConsumer ec = new EventingBasicConsumer(_model); + EventingBasicConsumer ec = new EventingBasicConsumer(_channel); ec.Received += (s, args) => { receivedInvoked = true; receivedSender = s; - Monitor.PulseAll(o); + latch.Set(); }; - _model.BasicConsume(q, true, ec); - _model.BasicPublish("", q, null, _encoding.GetBytes("msg")); + await _channel.ActivateConsumerAsync(ec, q, true).ConfigureAwait(false); + await _channel.PublishMessageAsync("", q, null, _encoding.GetBytes("msg")).ConfigureAwait(false); - WaitOn(o); + Wait(latch); Assert.IsTrue(receivedInvoked); Assert.IsNotNull(receivedSender); Assert.AreEqual(ec, receivedSender); - Assert.AreEqual(_model, ((EventingBasicConsumer)receivedSender).Model); + Assert.AreEqual(_channel, ((EventingBasicConsumer)receivedSender).Channel); bool shutdownInvoked = false; object shutdownSender = null; @@ -112,16 +113,17 @@ public void TestEventingConsumerDeliveryEvents() shutdownInvoked = true; shutdownSender = s; - Monitor.PulseAll(o); + latch.Set(); }; - _model.Close(); - WaitOn(o); + latch.Reset(); + await _channel.CloseAsync().ConfigureAwait(false); + Wait(latch); Assert.IsTrue(shutdownInvoked); Assert.IsNotNull(shutdownSender); Assert.AreEqual(ec, shutdownSender); - Assert.AreEqual(_model, ((EventingBasicConsumer)shutdownSender).Model); + Assert.AreEqual(_channel, ((EventingBasicConsumer)shutdownSender).Channel); } } } diff --git a/projects/Unit/TestExceptionMessages.cs b/projects/Unit/TestExceptionMessages.cs index 6c913b7f82..6a952670d3 100644 --- a/projects/Unit/TestExceptionMessages.cs +++ b/projects/Unit/TestExceptionMessages.cs @@ -30,9 +30,9 @@ //--------------------------------------------------------------------------- using System; - +using System.Threading.Tasks; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Exceptions; namespace RabbitMQ.Client.Unit @@ -41,23 +41,23 @@ namespace RabbitMQ.Client.Unit public class TestExceptionMessages : IntegrationFixture { [Test] - public void TestAlreadyClosedExceptionMessage() + public async Task TestAlreadyClosedExceptionMessage() { - string uuid = System.Guid.NewGuid().ToString(); + string uuid = Guid.NewGuid().ToString(); try { - _model.QueueDeclarePassive(uuid); + await _channel.DeclareQueuePassiveAsync(uuid).ConfigureAwait(false); } catch (Exception e) { Assert.That(e, Is.TypeOf(typeof(OperationInterruptedException))); } - Assert.IsFalse(_model.IsOpen); + Assert.IsFalse(_channel.IsOpen); try { - _model.QueueDeclarePassive(uuid); + await _channel.DeclareQueuePassiveAsync(uuid).ConfigureAwait(false); } catch (AlreadyClosedException e) { diff --git a/projects/Unit/TestExchangeDeclare.cs b/projects/Unit/TestExchangeDeclare.cs index 7d4f0c1ff3..5ad4bafa84 100644 --- a/projects/Unit/TestExchangeDeclare.cs +++ b/projects/Unit/TestExchangeDeclare.cs @@ -32,17 +32,17 @@ using System; using System.Collections.Generic; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestExchangeDeclare : IntegrationFixture { - + public class TestExchangeDeclare : IntegrationFixture + { [Test] [Category("RequireSMP")] - public void TestConcurrentExchangeDeclare() + public Task TestConcurrentExchangeDeclare() { string x = GenerateExchangeName(); Random rnd = new Random(); @@ -58,7 +58,7 @@ public void TestConcurrentExchangeDeclare() // sleep for a random amount of time to increase the chances // of thread interleaving. MK. Thread.Sleep(rnd.Next(5, 500)); - _model.ExchangeDeclare(x, "fanout", false, false, null); + _channel.DeclareExchangeAsync(x, "fanout", false, false).AsTask().GetAwaiter().GetResult(); } catch (System.NotSupportedException e) { nse = e; @@ -74,7 +74,7 @@ public void TestConcurrentExchangeDeclare() } Assert.IsNull(nse); - _model.ExchangeDelete(x); + return _channel.DeleteExchangeAsync(x).AsTask(); } } } diff --git a/projects/Unit/TestExtensions.cs b/projects/Unit/TestExtensions.cs index d6bbaa3299..b01e78b42b 100644 --- a/projects/Unit/TestExtensions.cs +++ b/projects/Unit/TestExtensions.cs @@ -30,8 +30,9 @@ //--------------------------------------------------------------------------- using System; - +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { @@ -39,46 +40,48 @@ namespace RabbitMQ.Client.Unit public class TestExtensions : IntegrationFixture { [Test] - public void TestConfirmBarrier() + public async Task TestConfirmBarrier() { - _model.ConfirmSelect(); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); for (int i = 0; i < 10; i++) { - _model.BasicPublish("", string.Empty, null, new byte[] {}); + await _channel.PublishMessageAsync("", string.Empty, null, new byte[] {}).ConfigureAwait(false); } - Assert.That(_model.WaitForConfirms(), Is.True); + Assert.That(_channel.WaitForConfirms(), Is.True); } [Test] public void TestConfirmBeforeWait() { - Assert.Throws(typeof (InvalidOperationException), () => _model.WaitForConfirms()); + Assert.Throws(typeof (InvalidOperationException), () => _channel.WaitForConfirms()); } [Test] - public void TestExchangeBinding() + public async Task TestExchangeBinding() { - _model.ConfirmSelect(); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); - _model.ExchangeDeclare("src", ExchangeType.Direct, false, false, null); - _model.ExchangeDeclare("dest", ExchangeType.Direct, false, false, null); - string queue = _model.QueueDeclare(); + await _channel.DeclareExchangeAsync("src", ExchangeType.Direct, false, false).ConfigureAwait(false); + await _channel.DeclareExchangeAsync("dest", ExchangeType.Direct, false, false).ConfigureAwait(false); + (string queue, _, _) = await _channel.DeclareQueueAsync().ConfigureAwait(false); - _model.ExchangeBind("dest", "src", string.Empty); - _model.ExchangeBind("dest", "src", string.Empty); - _model.QueueBind(queue, "dest", string.Empty); + await _channel.BindExchangeAsync("dest", "src", string.Empty).ConfigureAwait(false); + await _channel.BindExchangeAsync("dest", "src", string.Empty).ConfigureAwait(false); + await _channel.BindQueueAsync(queue, "dest", string.Empty).ConfigureAwait(false); - _model.BasicPublish("src", string.Empty, null, new byte[] {}); - _model.WaitForConfirms(); - Assert.IsNotNull(_model.BasicGet(queue, true)); + await _channel.PublishMessageAsync("src", string.Empty, null, new byte[] {}).ConfigureAwait(false); + _channel.WaitForConfirms(); + var singleMessage = await _channel.RetrieveSingleMessageAsync(queue, true).ConfigureAwait(false); + Assert.IsFalse(singleMessage.IsEmpty); - _model.ExchangeUnbind("dest", "src", string.Empty); - _model.BasicPublish("src", string.Empty, null, new byte[] {}); - _model.WaitForConfirms(); - Assert.IsNull(_model.BasicGet(queue, true)); + await _channel.UnbindExchangeAsync("dest", "src", string.Empty).ConfigureAwait(false); + await _channel.PublishMessageAsync("src", string.Empty, null, new byte[] {}).ConfigureAwait(false); + _channel.WaitForConfirms(); + singleMessage = await _channel.RetrieveSingleMessageAsync(queue, true).ConfigureAwait(false); + Assert.IsTrue(singleMessage.IsEmpty); - _model.ExchangeDelete("src"); - _model.ExchangeDelete("dest"); + await _channel.DeleteExchangeAsync("src").ConfigureAwait(false); + await _channel.DeleteExchangeAsync("dest").ConfigureAwait(false); } } } diff --git a/projects/Unit/TestFieldTableFormatting.cs b/projects/Unit/TestFieldTableFormatting.cs index 8022c0eb34..9fa374ae24 100644 --- a/projects/Unit/TestFieldTableFormatting.cs +++ b/projects/Unit/TestFieldTableFormatting.cs @@ -40,7 +40,7 @@ namespace RabbitMQ.Client.Unit { [TestFixture] - class TestFieldTableFormatting : WireFormattingFixture + internal class TestFieldTableFormatting : WireFormattingFixture { [Test] public void TestStandardTypes() diff --git a/projects/Unit/TestFieldTableFormattingGeneric.cs b/projects/Unit/TestFieldTableFormattingGeneric.cs index b735db78df..ae299ca847 100644 --- a/projects/Unit/TestFieldTableFormattingGeneric.cs +++ b/projects/Unit/TestFieldTableFormattingGeneric.cs @@ -41,7 +41,7 @@ namespace RabbitMQ.Client.Unit { [TestFixture] - class TestFieldTableFormattingGeneric : WireFormattingFixture + internal class TestFieldTableFormattingGeneric : WireFormattingFixture { [Test] public void TestStandardTypes() diff --git a/projects/Unit/TestFloodPublishing.cs b/projects/Unit/TestFloodPublishing.cs index f8f5667740..529a979162 100644 --- a/projects/Unit/TestFloodPublishing.cs +++ b/projects/Unit/TestFloodPublishing.cs @@ -33,10 +33,10 @@ using RabbitMQ.Client.Events; using System; using System.Diagnostics; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { @@ -47,9 +47,9 @@ public class TestFloodPublishing private readonly TimeSpan _tenSeconds = TimeSpan.FromSeconds(10); [Test] - public void TestUnthrottledFloodPublishing() + public async Task TestUnthrottledFloodPublishing() { - var connFactory = new ConnectionFactory() + var connFactory = new ConnectionFactory { RequestedHeartbeat = TimeSpan.FromSeconds(60), AutomaticRecoveryEnabled = false @@ -58,7 +58,7 @@ public void TestUnthrottledFloodPublishing() var closeWatch = new Stopwatch(); using (IConnection conn = connFactory.CreateConnection()) { - using (IModel model = conn.CreateModel()) + await using (IChannel channel = await conn.CreateChannelAsync().ConfigureAwait(false)) { conn.ConnectionShutdown += (_, args) => { @@ -82,7 +82,7 @@ public void TestUnthrottledFloodPublishing() } } - model.BasicPublish("", "", null, _body); + await channel.PublishMessageAsync("", "", null, _body); } } finally @@ -100,7 +100,7 @@ public void TestUnthrottledFloodPublishing() } [Test] - public void TestMultithreadFloodPublishing() + public async Task TestMultithreadFloodPublishing() { string testName = TestContext.CurrentContext.Test.FullName; string message = string.Format("Hello from test {0}", testName); @@ -109,7 +109,7 @@ public void TestMultithreadFloodPublishing() int receivedCount = 0; AutoResetEvent autoResetEvent = new AutoResetEvent(false); - var cf = new ConnectionFactory() + var cf = new ConnectionFactory { RequestedHeartbeat = TimeSpan.FromSeconds(60), AutomaticRecoveryEnabled = false @@ -117,39 +117,37 @@ public void TestMultithreadFloodPublishing() using (IConnection c = cf.CreateConnection()) { - string queueName = null; - using (IModel m = c.CreateModel()) + string queueName; + await using (IChannel channel = await c.CreateChannelAsync().ConfigureAwait(false)) { - QueueDeclareOk q = m.QueueDeclare(); - queueName = q.QueueName; + (queueName, _, _) = await channel.DeclareQueueAsync().ConfigureAwait(false); } - Task pub = Task.Run(() => + Task pub = Task.Run(async () => { - using (IModel m = c.CreateModel()) + await using (IChannel channel = await c.CreateChannelAsync().ConfigureAwait(false)) { - IBasicProperties bp = m.CreateBasicProperties(); for (int i = 0; i < publishCount; i++) { - m.BasicPublish(string.Empty, queueName, bp, sendBody); + await channel.PublishMessageAsync(string.Empty, queueName, null, sendBody).ConfigureAwait(false); } } }); - using (IModel consumerModel = c.CreateModel()) + await using (IChannel channel = await c.CreateChannelAsync().ConfigureAwait(false)) { - var consumer = new EventingBasicConsumer(consumerModel); + var consumer = new EventingBasicConsumer(channel); consumer.Received += (o, a) => { string receivedMessage = Encoding.UTF8.GetString(a.Body.ToArray()); Assert.AreEqual(message, receivedMessage); - Interlocked.Increment(ref receivedCount); + System.Threading.Interlocked.Increment(ref receivedCount); if (receivedCount == publishCount) { autoResetEvent.Set(); } }; - consumerModel.BasicConsume(queueName, true, consumer); + await channel.ActivateConsumerAsync(consumer, queueName, true).ConfigureAwait(false); Assert.IsTrue(pub.Wait(_tenSeconds)); Assert.IsTrue(autoResetEvent.WaitOne(_tenSeconds)); } diff --git a/projects/Unit/TestFrameFormatting.cs b/projects/Unit/TestFrameFormatting.cs index 4d3c6b2a02..d5f85d26f7 100644 --- a/projects/Unit/TestFrameFormatting.cs +++ b/projects/Unit/TestFrameFormatting.cs @@ -38,7 +38,7 @@ namespace RabbitMQ.Client.Unit { [TestFixture] - class TestFrameFormatting : WireFormattingFixture + internal class TestFrameFormatting : WireFormattingFixture { [Test] public void HeartbeatFrame() diff --git a/projects/Unit/TestHeartbeats.cs b/projects/Unit/TestHeartbeats.cs index 9933b37c93..4f9c2decc8 100644 --- a/projects/Unit/TestHeartbeats.cs +++ b/projects/Unit/TestHeartbeats.cs @@ -45,7 +45,7 @@ internal class TestHeartbeats : IntegrationFixture [Test, Category("LongRunning"), MaxTimeAttribute(35000)] public void TestThatHeartbeatWriterUsesConfigurableInterval() { - var cf = new ConnectionFactory() + var cf = new ConnectionFactory { RequestedHeartbeat = _heartbeatTimeout, AutomaticRecoveryEnabled = false @@ -62,7 +62,7 @@ public void TestThatHeartbeatWriterWithTLSEnabled() return; } - var cf = new ConnectionFactory() + var cf = new ConnectionFactory { RequestedHeartbeat = _heartbeatTimeout, AutomaticRecoveryEnabled = false @@ -85,7 +85,7 @@ public void TestThatHeartbeatWriterWithTLSEnabled() RunSingleConnectionTest(cf); } - [Test, Category("LongRunning"), MaxTimeAttribute(90000)] + [Test, Category("LongRunning"), MaxTime(90000)] public void TestHundredsOfConnectionsWithRandomHeartbeatInterval() { var rnd = new Random(); @@ -95,14 +95,13 @@ public void TestHundredsOfConnectionsWithRandomHeartbeatInterval() for (int i = 0; i < 200; i++) { ushort n = Convert.ToUInt16(rnd.Next(2, 6)); - var cf = new ConnectionFactory() + var cf = new ConnectionFactory { RequestedHeartbeat = TimeSpan.FromSeconds(n), AutomaticRecoveryEnabled = false }; IConnection conn = cf.CreateConnection(); xs.Add(conn); - IModel ch = conn.CreateModel(); conn.ConnectionShutdown += (sender, evt) => { @@ -121,7 +120,6 @@ public void TestHundredsOfConnectionsWithRandomHeartbeatInterval() protected void RunSingleConnectionTest(ConnectionFactory cf) { IConnection conn = cf.CreateConnection(); - IModel ch = conn.CreateModel(); bool wasShutdown = false; conn.ConnectionShutdown += (sender, evt) => @@ -148,14 +146,13 @@ private void CheckInitiator(ShutdownEventArgs evt) if (InitiatedByPeerOrLibrary(evt)) { Console.WriteLine(((Exception)evt.Cause).StackTrace); - string s = string.Format("Shutdown: {0}, initiated by: {1}", - evt, evt.Initiator); + string s = $"Shutdown: {evt}, initiated by: {evt.Initiator}"; Console.WriteLine(s); Assert.Fail(s); } } - private bool LongRunningTestsEnabled() + private static bool LongRunningTestsEnabled() { string s = Environment.GetEnvironmentVariable("RABBITMQ_LONG_RUNNING_TESTS"); if (s is null || s.Equals("")) diff --git a/projects/Unit/TestIEndpointResolverExtensions.cs b/projects/Unit/TestIEndpointResolverExtensions.cs index 28b921fee5..7da847c86a 100644 --- a/projects/Unit/TestIEndpointResolverExtensions.cs +++ b/projects/Unit/TestIEndpointResolverExtensions.cs @@ -36,7 +36,7 @@ namespace RabbitMQ.Client.Unit { - public class TestEndpointResolver : IEndpointResolver + internal class TestEndpointResolver : IEndpointResolver { private readonly IEnumerable _endpoints; public TestEndpointResolver (IEnumerable endpoints) @@ -50,7 +50,7 @@ public IEnumerable All() } } - class TestEndpointException : Exception + internal class TestEndpointException : Exception { public TestEndpointException(string message) : base(message) { diff --git a/projects/Unit/TestInitialConnection.cs b/projects/Unit/TestInitialConnection.cs index b3e35f2fe7..765172ac49 100644 --- a/projects/Unit/TestInitialConnection.cs +++ b/projects/Unit/TestInitialConnection.cs @@ -43,7 +43,7 @@ public class TestInitialConnection : IntegrationFixture [Test] public void TestBasicConnectionRecoveryWithHostnameList() { - Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List() { "127.0.0.1", "localhost" }); + Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "127.0.0.1", "localhost" }); Assert.IsTrue(c.IsOpen); c.Close(); } @@ -51,7 +51,7 @@ public void TestBasicConnectionRecoveryWithHostnameList() [Test] public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts() { - Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List() { "191.72.44.22", "127.0.0.1", "localhost" }); + Framing.Impl.AutorecoveringConnection c = CreateAutorecoveringConnection(new List { "191.72.44.22", "127.0.0.1", "localhost" }); Assert.IsTrue(c.IsOpen); c.Close(); } @@ -60,7 +60,7 @@ public void TestBasicConnectionRecoveryWithHostnameListAndUnreachableHosts() public void TestBasicConnectionRecoveryWithHostnameListWithOnlyUnreachableHosts() { Assert.Throws(() => { - CreateAutorecoveringConnection(new List() { + CreateAutorecoveringConnection(new List { "191.72.44.22", "145.23.22.18", "192.255.255.255" diff --git a/projects/Unit/TestInvalidAck.cs b/projects/Unit/TestInvalidAck.cs index 340ecf26d3..6078ce6e57 100644 --- a/projects/Unit/TestInvalidAck.cs +++ b/projects/Unit/TestInvalidAck.cs @@ -30,29 +30,29 @@ //--------------------------------------------------------------------------- using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestInvalidAck : IntegrationFixture { - + public class TestInvalidAck : IntegrationFixture + { [Test] - public void TestAckWithUnknownConsumerTagAndMultipleFalse() + public async Task TestAckWithUnknownConsumerTagAndMultipleFalse() { - object o = new object(); + using var latch = new ManualResetEventSlim(false); bool shutdownFired = false; ShutdownEventArgs shutdownArgs = null; - _model.ModelShutdown += (s, args) => + _channel.Shutdown += args => { shutdownFired = true; shutdownArgs = args; - Monitor.PulseAll(o); + latch.Set(); }; - _model.BasicAck(123456, false); - WaitOn(o); + await _channel.AckMessageAsync(123456, false).ConfigureAwait(false); + Wait(latch); Assert.IsTrue(shutdownFired); AssertPreconditionFailed(shutdownArgs); } diff --git a/projects/Unit/TestMainLoop.cs b/projects/Unit/TestMainLoop.cs index 2d1d2820de..0b8a0536a6 100644 --- a/projects/Unit/TestMainLoop.cs +++ b/projects/Unit/TestMainLoop.cs @@ -30,20 +30,20 @@ //--------------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; - -using RabbitMQ.Client.Events; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestMainLoop : IntegrationFixture { - + public class TestMainLoop : IntegrationFixture + { private class FaultyConsumer : DefaultBasicConsumer { - public FaultyConsumer(IModel model) : base(model) {} + public FaultyConsumer(IChannel channel) : base(channel) {} public override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, @@ -58,24 +58,25 @@ public override void HandleBasicDeliver(string consumerTag, } [Test] - public void TestCloseWithFaultyConsumer() + public async Task TestCloseWithFaultyConsumer() { ConnectionFactory connFactory = new ConnectionFactory(); IConnection c = connFactory.CreateConnection(); - IModel m = _conn.CreateModel(); - object o = new object(); + IChannel ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + using var latch = new ManualResetEventSlim(false); string q = GenerateQueueName(); - m.QueueDeclare(q, false, false, false, null); + await _channel.DeclareQueueAsync(q, false, false, false); - CallbackExceptionEventArgs ea = null; - m.CallbackException += (_, evt) => { + Dictionary ea = null; + ch.UnhandledExceptionOccurred += (_, evt) => { ea = evt; c.Close(); - Monitor.PulseAll(o); + latch.Set(); }; - m.BasicConsume(q, true, new FaultyConsumer(_model)); - m.BasicPublish("", q, null, _encoding.GetBytes("message")); - WaitOn(o); + + await ch.ActivateConsumerAsync(new FaultyConsumer(ch), q, true).ConfigureAwait(false); + await ch.PublishMessageAsync("", q, null, _encoding.GetBytes("message")).ConfigureAwait(false); + Wait(latch); Assert.IsNotNull(ea); Assert.AreEqual(c.IsOpen, false); diff --git a/projects/Unit/TestMessageCount.cs b/projects/Unit/TestMessageCount.cs index 173fd4e201..bc5f47bbfc 100644 --- a/projects/Unit/TestMessageCount.cs +++ b/projects/Unit/TestMessageCount.cs @@ -30,24 +30,25 @@ //--------------------------------------------------------------------------- using System; - +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { internal class TestMessageCount : IntegrationFixture { [Test] - public void TestMessageCountMethod() + public async Task TestMessageCountMethod() { - _model.ConfirmSelect(); + await _channel.ActivatePublishTagsAsync().ConfigureAwait(false); string q = GenerateQueueName(); - _model.QueueDeclare(queue: q, durable: false, exclusive: true, autoDelete: false, arguments: null); - Assert.AreEqual(0, _model.MessageCount(q)); + await _channel.DeclareQueueAsync(q, false, true, false).ConfigureAwait(false); + Assert.AreEqual(0, await _channel.GetQueueMessageCountAsync(q)); - _model.BasicPublish(exchange: "", routingKey: q, basicProperties: null, body: _encoding.GetBytes("msg")); - _model.WaitForConfirms(TimeSpan.FromSeconds(2)); - Assert.AreEqual(1, _model.MessageCount(q)); + await _channel.PublishMessageAsync("", q, null, _encoding.GetBytes("msg")).ConfigureAwait(false); + _channel.WaitForConfirms(TimeSpan.FromSeconds(2)); + Assert.AreEqual(1, await _channel.GetQueueMessageCountAsync(q)); } } } diff --git a/projects/Unit/TestMethodArgumentCodec.cs b/projects/Unit/TestMethodArgumentCodec.cs index 6ed3c40227..131b9612c6 100644 --- a/projects/Unit/TestMethodArgumentCodec.cs +++ b/projects/Unit/TestMethodArgumentCodec.cs @@ -37,6 +37,7 @@ using RabbitMQ.Client.Impl; using RabbitMQ.Util; +using Unit; namespace RabbitMQ.Client.Unit { diff --git a/projects/Unit/TestNetworkByteOrderSerialization.cs b/projects/Unit/TestNetworkByteOrderSerialization.cs index 9171bb0ecf..db16f8dd3e 100644 --- a/projects/Unit/TestNetworkByteOrderSerialization.cs +++ b/projects/Unit/TestNetworkByteOrderSerialization.cs @@ -34,11 +34,12 @@ using NUnit.Framework; using RabbitMQ.Util; +using Unit; namespace RabbitMQ.Client.Unit { [TestFixture] - class TestNetworkByteOrderSerialization + public class TestNetworkByteOrderSerialization { public void Check(byte[] actual, byte[] expected) { diff --git a/projects/Unit/TestNowait.cs b/projects/Unit/TestNowait.cs index c1e13c1413..ffc1ec9b4e 100644 --- a/projects/Unit/TestNowait.cs +++ b/projects/Unit/TestNowait.cs @@ -29,82 +29,91 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestNoWait : IntegrationFixture { + public class TestNoWait : IntegrationFixture + { [Test] - public void TestQueueDeclareNoWait() + public async Task TestQueueDeclareNoWait() { string q = GenerateQueueName(); - _model.QueueDeclareNoWait(q, false, true, false, null); - _model.QueueDeclarePassive(q); + await _channel.DeclareQueueWithoutConfirmationAsync(q, false, true, false).ConfigureAwait(false); + await _channel.DeclareQueuePassiveAsync(q).ConfigureAwait(false); } [Test] - public void TestQueueBindNoWait() + public async Task TestQueueBindNoWait() { string q = GenerateQueueName(); - _model.QueueDeclareNoWait(q, false, true, false, null); - _model.QueueBindNoWait(q, "amq.fanout", "", null); + await _channel.DeclareQueueWithoutConfirmationAsync(q, false, true, false).ConfigureAwait(false); + await _channel.BindQueueAsync(q, "amq.fanout", "", waitForConfirmation:false).ConfigureAwait(false); } [Test] - public void TestQueueDeleteNoWait() + public async Task TestQueueDeleteNoWait() { string q = GenerateQueueName(); - _model.QueueDeclareNoWait(q, false, true, false, null); - _model.QueueDeleteNoWait(q, false, false); + await _channel.DeclareQueueWithoutConfirmationAsync(q, false, true, false).ConfigureAwait(false); + await _channel.DeleteQueueWithoutConfirmationAsync(q).ConfigureAwait(false); } [Test] - public void TestExchangeDeclareNoWait() + public async Task TestExchangeDeclareNoWait() { string x = GenerateExchangeName(); try { - _model.ExchangeDeclareNoWait(x, "fanout", false, true, null); - _model.ExchangeDeclarePassive(x); - } finally { - _model.ExchangeDelete(x); + await _channel.DeclareExchangeAsync(x, "fanout", false, true, waitForConfirmation:false).ConfigureAwait(false); + await _channel.DeclareExchangePassiveAsync(x).ConfigureAwait(false); + } + finally + { + await _channel.DeleteExchangeAsync(x).ConfigureAwait(false); } } [Test] - public void TestExchangeBindNoWait() + public async Task TestExchangeBindNoWait() { string x = GenerateExchangeName(); try { - _model.ExchangeDeclareNoWait(x, "fanout", false, true, null); - _model.ExchangeBindNoWait(x, "amq.fanout", "", null); - } finally { - _model.ExchangeDelete(x); + await _channel.DeclareExchangeAsync(x, "fanout", false, true, waitForConfirmation:false).ConfigureAwait(false); + await _channel.BindExchangeAsync(x, "amq.fanout", "", waitForConfirmation:false).ConfigureAwait(false); + } + finally + { + await _channel.DeleteExchangeAsync(x).ConfigureAwait(false); } } [Test] - public void TestExchangeUnbindNoWait() + public async Task TestExchangeUnbindNoWait() { string x = GenerateExchangeName(); try { - _model.ExchangeDeclare(x, "fanout", false, true, null); - _model.ExchangeBind(x, "amq.fanout", "", null); - _model.ExchangeUnbindNoWait(x, "amq.fanout", "", null); - } finally { - _model.ExchangeDelete(x); + await _channel.DeclareExchangeAsync(x, "fanout", false, true).ConfigureAwait(false); + await _channel.BindExchangeAsync(x, "amq.fanout", "").ConfigureAwait(false); + await _channel.UnbindExchangeAsync(x, "amq.fanout", "", waitForConfirmation:false).ConfigureAwait(false); + } + finally + { + await _channel.DeleteExchangeAsync(x); } } [Test] - public void TestExchangeDeleteNoWait() + public async Task TestExchangeDeleteNoWait() { string x = GenerateExchangeName(); - _model.ExchangeDeclareNoWait(x, "fanout", false, true, null); - _model.ExchangeDeleteNoWait(x, false); + await _channel.DeclareExchangeAsync(x, "fanout", false, true, waitForConfirmation:false).ConfigureAwait(false); + await _channel.DeleteExchangeAsync(x, waitForConfirmation:false).ConfigureAwait(false); } } } diff --git a/projects/Unit/TestPassiveDeclare.cs b/projects/Unit/TestPassiveDeclare.cs index 4269a1c53c..c003a8d47a 100644 --- a/projects/Unit/TestPassiveDeclare.cs +++ b/projects/Unit/TestPassiveDeclare.cs @@ -32,7 +32,7 @@ using System; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Exceptions; namespace RabbitMQ.Client.Unit @@ -43,15 +43,13 @@ public class TestPassiveDeclare : IntegrationFixture [Test] public void TestPassiveExchangeDeclareWhenExchangeDoesNotExist() { - Assert.Throws(Is.InstanceOf(), - () => _model.ExchangeDeclarePassive(Guid.NewGuid().ToString())); + Assert.ThrowsAsync(Is.InstanceOf(), () => _channel.DeclareExchangePassiveAsync(Guid.NewGuid().ToString()).AsTask()); } [Test] public void TestPassiveQueueDeclareWhenQueueDoesNotExist() { - Assert.Throws(Is.InstanceOf(), - () => _model.QueueDeclarePassive(Guid.NewGuid().ToString())); + Assert.ThrowsAsync(Is.InstanceOf(), () => _channel.DeclareQueuePassiveAsync(Guid.NewGuid().ToString()).AsTask()); } } } diff --git a/projects/Unit/TestPropertiesClone.cs b/projects/Unit/TestPropertiesClone.cs index d83cb3c2e1..740f7a4b30 100644 --- a/projects/Unit/TestPropertiesClone.cs +++ b/projects/Unit/TestPropertiesClone.cs @@ -33,7 +33,6 @@ using NUnit.Framework; -using RabbitMQ.Client; using RabbitMQ.Client.Impl; namespace RabbitMQ.Client.Unit diff --git a/projects/Unit/TestPublishSharedModel.cs b/projects/Unit/TestPublishSharedChannel.cs similarity index 72% rename from projects/Unit/TestPublishSharedModel.cs rename to projects/Unit/TestPublishSharedChannel.cs index f066a85817..1c6e71d061 100644 --- a/projects/Unit/TestPublishSharedModel.cs +++ b/projects/Unit/TestPublishSharedChannel.cs @@ -33,15 +33,16 @@ using System; using System.Threading; using System.Threading.Tasks; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestPublishSharedModel + public class TestPublishSharedChannel { - private const string ExchangeName = "TestPublishSharedModel_Ex"; - private const string QueueName = "TestPublishSharedModel_Queue"; - private const string PublishKey = "TestPublishSharedModel_RoutePub"; + private const string ExchangeName = "TestPublishSharedChannel_Ex"; + private const string QueueName = "TestPublishSharedChannel_Queue"; + private const string PublishKey = "TestPublishSharedChannel_RoutePub"; private const int Loops = 20; private const int Repeats = 1000; @@ -50,7 +51,7 @@ public class TestPublishSharedModel private Exception _raisedException; [Test] - public async Task MultiThreadPublishOnSharedModel() + public async Task MultiThreadPublishOnSharedChannel() { // Arrange var connFactory = new ConnectionFactory @@ -69,15 +70,15 @@ public async Task MultiThreadPublishOnSharedModel() } }; - using (IModel model = conn.CreateModel()) + await using (IChannel channel = await conn.CreateChannelAsync().ConfigureAwait(false)) { - model.ExchangeDeclare(ExchangeName, "topic", durable: false, autoDelete: true); - model.QueueDeclare(QueueName, false, false, true, null); - model.QueueBind(QueueName, ExchangeName, PublishKey, null); + await channel.DeclareExchangeAsync(ExchangeName, "topic", durable: false, autoDelete: true); + await channel.DeclareQueueWithoutConfirmationAsync(QueueName, false, false, true); + await channel.BindQueueAsync(QueueName, ExchangeName, PublishKey); // Act - var pubTask = Task.Run(() => NewFunction(model)); - var pubTask2 = Task.Run(() => NewFunction(model)); + var pubTask = Task.Run(() => PublishMessagesAsync(channel)); + var pubTask2 = Task.Run(() => PublishMessagesAsync(channel)); await Task.WhenAll(pubTask, pubTask2); } @@ -86,7 +87,7 @@ public async Task MultiThreadPublishOnSharedModel() // Assert Assert.Null(_raisedException); - void NewFunction(IModel model) + async Task PublishMessagesAsync(IChannel channel) { try { @@ -94,7 +95,7 @@ void NewFunction(IModel model) { for (int j = 0; j < Repeats; j++) { - model.BasicPublish(ExchangeName, PublishKey, false, null, _body); + await channel.PublishMessageAsync(ExchangeName, PublishKey, null, _body).ConfigureAwait(false); } Thread.Sleep(1); diff --git a/projects/Unit/TestPublishValidation.cs b/projects/Unit/TestPublishValidation.cs index 4d5a95cee2..18c4196cc2 100644 --- a/projects/Unit/TestPublishValidation.cs +++ b/projects/Unit/TestPublishValidation.cs @@ -30,20 +30,20 @@ //--------------------------------------------------------------------------- using System; - +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { [TestFixture] public class TestPublishValidation : IntegrationFixture { - [Test] - public void TestNullRoutingKeyIsRejected() + public async Task TestNullRoutingKeyIsRejected() { - IModel ch = _conn.CreateModel(); - Assert.Throws(typeof(ArgumentNullException), () => ch.BasicPublish("", null, null, _encoding.GetBytes("msg"))); + IChannel ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + Assert.ThrowsAsync(typeof(ArgumentNullException), async () => await ch.PublishMessageAsync("", null, null, _encoding.GetBytes("msg")).ConfigureAwait(false)); } } } diff --git a/projects/Unit/TestPublisherConfirms.cs b/projects/Unit/TestPublisherConfirms.cs index 28f9a5ecec..e4fe55442c 100644 --- a/projects/Unit/TestPublisherConfirms.cs +++ b/projects/Unit/TestPublisherConfirms.cs @@ -30,11 +30,11 @@ //--------------------------------------------------------------------------- using System; +using System.Reflection; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; - -using RabbitMQ.Client.Impl; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { @@ -44,66 +44,68 @@ public class TestPublisherConfirms : IntegrationFixture private const string QueueName = "RabbitMQ.Client.Unit.TestPublisherConfirms"; [Test] - public void TestWaitForConfirmsWithoutTimeout() + public Task TestWaitForConfirmsWithoutTimeout() { - TestWaitForConfirms(200, (ch) => + return TestWaitForConfirmsAsync(200, ch => { Assert.IsTrue(ch.WaitForConfirms()); }); } [Test] - public void TestWaitForConfirmsWithTimeout() + public Task TestWaitForConfirmsWithTimeout() { - TestWaitForConfirms(200, (ch) => + return TestWaitForConfirmsAsync(200, ch => { Assert.IsTrue(ch.WaitForConfirms(TimeSpan.FromSeconds(4))); }); } [Test] - public void TestWaitForConfirmsWithTimeout_AllMessagesAcked_WaitingHasTimedout_ReturnTrue() + public Task TestWaitForConfirmsWithTimeout_AllMessagesAcked_WaitingHasTimedout_ReturnTrue() { - TestWaitForConfirms(200, (ch) => + return TestWaitForConfirmsAsync(200, ch => { Assert.IsTrue(ch.WaitForConfirms(TimeSpan.FromMilliseconds(1))); }); } [Test] - public void TestWaitForConfirmsWithTimeout_MessageNacked_WaitingHasTimedout_ReturnFalse() + public Task TestWaitForConfirmsWithTimeout_MessageNacked_WaitingHasTimedout_ReturnFalse() { - TestWaitForConfirms(200, (ch) => + return TestWaitForConfirmsAsync(200, ch => { - BasicGetResult message = ch.BasicGet(QueueName, false); + SingleMessageRetrieval message = ch.RetrieveSingleMessageAsync(QueueName, false).AsTask().GetAwaiter().GetResult(); - var fullModel = ch as IFullModel; - fullModel.HandleBasicNack(message.DeliveryTag, false, false); + // Fake a nack retrieval + typeof(Channel) + .GetMethod("HandleBasicNack", BindingFlags.Instance | BindingFlags.NonPublic) + .Invoke(((AutorecoveringChannel)ch).NonDisposedDelegate, new object []{ message.DeliveryTag, false }); Assert.IsFalse(ch.WaitForConfirms(TimeSpan.FromMilliseconds(1))); }); } [Test] - public void TestWaitForConfirmsWithEvents() + public async Task TestWaitForConfirmsWithEvents() { - IModel ch = _conn.CreateModel(); - ch.ConfirmSelect(); + IChannel ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + await ch.ActivatePublishTagsAsync().ConfigureAwait(false); - ch.QueueDeclare(QueueName); + await ch.DeclareQueueAsync(QueueName).ConfigureAwait(false); int n = 200; // number of event handler invocations int c = 0; - ch.BasicAcks += (_, args) => + ch.PublishTagAcknowledged += (_, __, ___) => { - Interlocked.Increment(ref c); + System.Threading.Interlocked.Increment(ref c); }; try { for (int i = 0; i < n; i++) { - ch.BasicPublish("", QueueName, null, _encoding.GetBytes("msg")); + await ch.PublishMessageAsync("", QueueName, null, _encoding.GetBytes("msg")).ConfigureAwait(false); } Thread.Sleep(TimeSpan.FromSeconds(1)); ch.WaitForConfirms(TimeSpan.FromSeconds(5)); @@ -116,21 +118,20 @@ public void TestWaitForConfirmsWithEvents() } finally { - ch.QueueDelete(QueueName); - ch.Close(); + await ch.DeleteQueueAsync(QueueName).ConfigureAwait(false); + await ch.CloseAsync().ConfigureAwait(false); } } - protected void TestWaitForConfirms(int numberOfMessagesToPublish, Action fn) + protected async Task TestWaitForConfirmsAsync(int numberOfMessagesToPublish, Action fn) { - IModel ch = _conn.CreateModel(); - ch.ConfirmSelect(); - - ch.QueueDeclare(QueueName); + var ch = await _conn.CreateChannelAsync().ConfigureAwait(false); + await ch.ActivatePublishTagsAsync().ConfigureAwait(false); + await ch.DeclareQueueAsync(QueueName).ConfigureAwait(false); for (int i = 0; i < numberOfMessagesToPublish; i++) { - ch.BasicPublish("", QueueName, null, _encoding.GetBytes("msg")); + await ch.PublishMessageAsync("", QueueName, null, _encoding.GetBytes("msg")).ConfigureAwait(false); } try @@ -139,8 +140,8 @@ protected void TestWaitForConfirms(int numberOfMessagesToPublish, Action } finally { - ch.QueueDelete(QueueName); - ch.Close(); + await ch.DeleteQueueAsync(QueueName).ConfigureAwait(false); + await ch.CloseAsync().ConfigureAwait(false); } } } diff --git a/projects/Unit/TestQueueDeclare.cs b/projects/Unit/TestQueueDeclare.cs index e33638874a..d81689df5b 100644 --- a/projects/Unit/TestQueueDeclare.cs +++ b/projects/Unit/TestQueueDeclare.cs @@ -32,7 +32,7 @@ using System; using System.Collections.Generic; using System.Threading; - +using System.Threading.Tasks; using NUnit.Framework; namespace RabbitMQ.Client.Unit @@ -42,7 +42,7 @@ public class TestQueueDeclare : IntegrationFixture { [Test] [Category("RequireSMP")] - public void TestConcurrentQueueDeclare() + public Task TestConcurrentQueueDeclare() { string q = GenerateQueueName(); Random rnd = new Random(); @@ -58,7 +58,7 @@ public void TestConcurrentQueueDeclare() // sleep for a random amount of time to increase the chances // of thread interleaving. MK. Thread.Sleep(rnd.Next(5, 50)); - _model.QueueDeclare(q, false, false, false, null); + _channel.DeclareQueueAsync(q, false, false, false).AsTask().GetAwaiter().GetResult(); } catch (System.NotSupportedException e) { nse = e; @@ -74,7 +74,7 @@ public void TestConcurrentQueueDeclare() } Assert.IsNull(nse); - _model.QueueDelete(q); + return _channel.DeleteQueueAsync(q).AsTask(); } } } diff --git a/projects/Unit/TestRecoverAfterCancel.cs b/projects/Unit/TestRecoverAfterCancel.cs index 0ce45e6f78..6eef397268 100644 --- a/projects/Unit/TestRecoverAfterCancel.cs +++ b/projects/Unit/TestRecoverAfterCancel.cs @@ -29,13 +29,11 @@ // Copyright (c) 2007-2020 VMware, Inc. All rights reserved. //--------------------------------------------------------------------------- -using System; using System.Text; - +using System.Threading.Tasks; using NUnit.Framework; - +using RabbitMQ.Client.client.impl.Channel; using RabbitMQ.Client.Events; -using RabbitMQ.Client.Impl; using RabbitMQ.Util; #pragma warning disable 0618 @@ -45,68 +43,48 @@ namespace RabbitMQ.Client.Unit [TestFixture] public class TestRecoverAfterCancel { - IConnection _connection; - IModel _channel; - string _queue; - int _callbackCount; + private IConnection _connection; + private IChannel _channel; + private string _queue; - public int ModelNumber(IModel model) - { - return ((ModelBase)model).Session.ChannelNumber; - } - - [SetUp] public void Connect() + [SetUp] + public async Task Connect() { _connection = new ConnectionFactory().CreateConnection(); - _channel = _connection.CreateModel(); - _queue = _channel.QueueDeclare("", false, true, false, null); + _channel = await _connection.CreateChannelAsync().ConfigureAwait(false); + _queue = (await _channel.DeclareQueueAsync("", false, true, false).ConfigureAwait(false)).QueueName; } - [TearDown] public void Disconnect() + [TearDown] + public void Disconnect() { _connection.Abort(); } [Test] - public void TestRecoverAfterCancel_() + public async Task TestRecoverAfterCancel_() { - UTF8Encoding enc = new UTF8Encoding(); - _channel.BasicPublish("", _queue, null, enc.GetBytes("message")); + await _channel.PublishMessageAsync("", _queue, null, Encoding.UTF8.GetBytes("message")).ConfigureAwait(false); EventingBasicConsumer Consumer = new EventingBasicConsumer(_channel); SharedQueue<(bool Redelivered, byte[] Body)> EventQueue = new SharedQueue<(bool Redelivered, byte[] Body)>(); // Making sure we copy the delivery body since it could be disposed at any time. Consumer.Received += (_, e) => EventQueue.Enqueue((e.Redelivered, e.Body.ToArray())); - string CTag = _channel.BasicConsume(_queue, false, Consumer); + string CTag = await _channel.ActivateConsumerAsync(Consumer, _queue, false).ConfigureAwait(false); (bool Redelivered, byte[] Body) Event = EventQueue.Dequeue(); - _channel.BasicCancel(CTag); - _channel.BasicRecover(true); + await _channel.CancelConsumerAsync(CTag).ConfigureAwait(false); + await _channel.ResendUnackedMessages(true); EventingBasicConsumer Consumer2 = new EventingBasicConsumer(_channel); SharedQueue<(bool Redelivered, byte[] Body)> EventQueue2 = new SharedQueue<(bool Redelivered, byte[] Body)>(); // Making sure we copy the delivery body since it could be disposed at any time. Consumer2.Received += (_, e) => EventQueue2.Enqueue((e.Redelivered, e.Body.ToArray())); - _channel.BasicConsume(_queue, false, Consumer2); + await _channel.ActivateConsumerAsync(Consumer2, _queue, false).ConfigureAwait(false); (bool Redelivered, byte[] Body) Event2 = EventQueue2.Dequeue(); CollectionAssert.AreEqual(Event.Body, Event2.Body); Assert.IsFalse(Event.Redelivered); Assert.IsTrue(Event2.Redelivered); } - - [Test] - public void TestRecoverCallback() - { - _callbackCount = 0; - _channel.BasicRecoverOk += IncrCallback; - _channel.BasicRecover(true); - Assert.AreEqual(1, _callbackCount); - } - - void IncrCallback(object sender, EventArgs args) - { - _callbackCount++; - } - } } diff --git a/projects/Unit/TestRpcContinuationQueue.cs b/projects/Unit/TestRpcContinuationQueue.cs index 2530ce109e..2838064092 100644 --- a/projects/Unit/TestRpcContinuationQueue.cs +++ b/projects/Unit/TestRpcContinuationQueue.cs @@ -69,7 +69,7 @@ public void TestRpcContinuationQueueEnqueue2() var inputContinuation = new SimpleBlockingRpcContinuation(); var inputContinuation1 = new SimpleBlockingRpcContinuation(); queue.Enqueue(inputContinuation); - Assert.Throws(typeof(NotSupportedException), () => + Assert.Throws(typeof(NotSupportedException), () => { queue.Enqueue(inputContinuation1); }); diff --git a/projects/Unit/TestSharedQueue.cs b/projects/Unit/TestSharedQueue.cs index 99bc1e34b8..e7972bcde0 100644 --- a/projects/Unit/TestSharedQueue.cs +++ b/projects/Unit/TestSharedQueue.cs @@ -40,7 +40,7 @@ namespace RabbitMQ.Client.Unit { [TestFixture] - class TestSharedQueue : TimingFixture + internal class TestSharedQueue : TimingFixture { //wrapper to work around C#'s lack of local volatiles public class VolatileInt diff --git a/projects/Unit/TestSsl.cs b/projects/Unit/TestSsl.cs index cc86182163..5c02599462 100644 --- a/projects/Unit/TestSsl.cs +++ b/projects/Unit/TestSsl.cs @@ -32,78 +32,76 @@ using System; using System.Net.Security; using System.Security.Authentication; - +using System.Threading.Tasks; using NUnit.Framework; +using RabbitMQ.Client.client.impl.Channel; namespace RabbitMQ.Client.Unit { [TestFixture] public class TestSsl { - public void SendReceive(ConnectionFactory cf) + public async Task SendReceiveAsync(ConnectionFactory cf) { using (IConnection conn = cf.CreateConnection()) + await using (IChannel ch = await conn.CreateChannelAsync().ConfigureAwait(false)) { - IModel ch = conn.CreateModel(); - - ch.ExchangeDeclare("Exchange_TestSslEndPoint", ExchangeType.Direct); - string qName = ch.QueueDeclare(); - ch.QueueBind(qName, "Exchange_TestSslEndPoint", "Key_TestSslEndpoint", null); + await ch.DeclareExchangeAsync("Exchange_TestSslEndPoint", ExchangeType.Direct).ConfigureAwait(false); + string qName = (await ch.DeclareQueueAsync().ConfigureAwait(false)).QueueName; + await ch.BindQueueAsync(qName, "Exchange_TestSslEndPoint", "Key_TestSslEndpoint").ConfigureAwait(false); string message = "Hello C# SSL Client World"; byte[] msgBytes = System.Text.Encoding.UTF8.GetBytes(message); - ch.BasicPublish("Exchange_TestSslEndPoint", "Key_TestSslEndpoint", null, msgBytes); + await ch.PublishMessageAsync("Exchange_TestSslEndPoint", "Key_TestSslEndpoint", null, msgBytes); - bool autoAck = false; - BasicGetResult result = ch.BasicGet(qName, autoAck); - byte[] body = result.Body.ToArray(); - string resultMessage = System.Text.Encoding.UTF8.GetString(body); + SingleMessageRetrieval? result = await ch.RetrieveSingleMessageAsync(qName, false).ConfigureAwait(false); + string resultMessage = System.Text.Encoding.UTF8.GetString(result?.Body.ToArray() ?? Array.Empty()); Assert.AreEqual(message, resultMessage); } } [Test] - public void TestServerVerifiedIgnoringNameMismatch() + public Task TestServerVerifiedIgnoringNameMismatch() { string sslDir = IntegrationFixture.CertificatesDirectory(); if (null == sslDir) { Console.WriteLine("SSL_CERT_DIR is not configured, skipping test"); - return; + return Task.CompletedTask; } ConnectionFactory cf = new ConnectionFactory(); cf.Ssl.ServerName = "*"; cf.Ssl.AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch; cf.Ssl.Enabled = true; - SendReceive(cf); + return SendReceiveAsync(cf); } [Test] - public void TestServerVerified() + public Task TestServerVerified() { string sslDir = IntegrationFixture.CertificatesDirectory(); if (null == sslDir) { Console.WriteLine("SSL_CERT_DIR is not configured, skipping test"); - return; + return Task.CompletedTask; } ConnectionFactory cf = new ConnectionFactory(); cf.Ssl.ServerName = System.Net.Dns.GetHostName(); cf.Ssl.Enabled = true; - SendReceive(cf); + return SendReceiveAsync(cf); } [Test] - public void TestClientAndServerVerified() + public Task TestClientAndServerVerified() { string sslDir = IntegrationFixture.CertificatesDirectory(); if (null == sslDir) { Console.WriteLine("SSL_CERT_DIR is not configured, skipping test"); - return; + return Task.CompletedTask; } ConnectionFactory cf = new ConnectionFactory(); @@ -114,23 +112,23 @@ public void TestClientAndServerVerified() Assert.IsNotNull(p12Password, "missing PASSWORD env var"); cf.Ssl.CertPassphrase = p12Password; cf.Ssl.Enabled = true; - SendReceive(cf); + return SendReceiveAsync(cf); } // rabbitmq/rabbitmq-dotnet-client#46, also #44 and #45 [Test] - public void TestNoClientCertificate() + public Task TestNoClientCertificate() { string sslDir = IntegrationFixture.CertificatesDirectory(); if (null == sslDir) { Console.WriteLine("SSL_CERT_DIR is not configured, skipping test"); - return; + return Task.CompletedTask; } ConnectionFactory cf = new ConnectionFactory { - Ssl = new SslOption() + Ssl = new SslOption { CertPath = null, Enabled = true, @@ -138,10 +136,9 @@ public void TestNoClientCertificate() }; cf.Ssl.Version = SslProtocols.None; - cf.Ssl.AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNotAvailable | - SslPolicyErrors.RemoteCertificateNameMismatch; + cf.Ssl.AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNotAvailable | SslPolicyErrors.RemoteCertificateNameMismatch; - SendReceive(cf); + return SendReceiveAsync(cf); } } } diff --git a/projects/Unit/TestUpdateSecret.cs b/projects/Unit/TestUpdateSecret.cs index 047df2f228..dcd1ae3147 100644 --- a/projects/Unit/TestUpdateSecret.cs +++ b/projects/Unit/TestUpdateSecret.cs @@ -37,8 +37,8 @@ namespace RabbitMQ.Client.Unit { [TestFixture] - public class TestUpdateSecret : IntegrationFixture { - + public class TestUpdateSecret : IntegrationFixture + { [Test] public void TestUpdatingConnectionSecret() { diff --git a/projects/Unit/WireFormattingFixture.cs b/projects/Unit/WireFormattingFixture.cs index dda5facae0..c667f5694e 100644 --- a/projects/Unit/WireFormattingFixture.cs +++ b/projects/Unit/WireFormattingFixture.cs @@ -30,15 +30,14 @@ //--------------------------------------------------------------------------- using System; -using System.IO; using NUnit.Framework; -using RabbitMQ.Util; +using Unit; namespace RabbitMQ.Client.Unit { - class WireFormattingFixture + internal class WireFormattingFixture { public void Check(byte[] actual, byte[] expected) {