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);
+
+ ///