Skip to content

Commit 161b7fc

Browse files
author
Vlad Alexandru Ionescu
committed
merging bug23887 into default
2 parents 3120445 + faed96f commit 161b7fc

11 files changed

+212
-10
lines changed

docs/specs/amqp0-9-1.stripped.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
384384
</method>
385385
<method name="cancel" synchronous="1" index="30">
386386
<chassis name="server" implement="MUST"/>
387+
<chassis name="client" implement="SHOULD"/>
387388
<response name="cancel-ok"/>
388389
<field name="consumer-tag" domain="consumer-tag"/>
389390
<field name="no-wait" domain="no-wait"/>
390391
</method>
391392
<method name="cancel-ok" synchronous="1" index="31">
392393
<chassis name="client" implement="MUST"/>
394+
<chassis name="server" implement="MAY"/>
393395
<field name="consumer-tag" domain="consumer-tag"/>
394396
</method>
395397
<method name="publish" content="1" index="40">

docs/specs/amqp0-9-1.xml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
exchange.bind and exchange.bind-ok,
88
exchange.unbind and exchange.unbind-ok,
99
basic.nack
10-
and the ability for the Server to send basic.ack and basic.nack to the client.
10+
and the ability for the Server to send basic.ack, basic.nack and
11+
basic.cancel to the client.
1112
-->
1213

1314
<!--
@@ -2533,6 +2534,17 @@
25332534
messages, but it does mean the server will not send any more messages for
25342535
that consumer. The client may receive an arbitrary number of messages in
25352536
between sending the cancel method and receiving the cancel-ok reply.
2537+
2538+
It may also be sent from the server to the client in the event
2539+
of the consumer being unexpectedly cancelled (i.e. cancelled
2540+
for any reason other than the server receiving the
2541+
corresponding basic.cancel from the client). This allows
2542+
clients to be notified of the loss of consumers due to events
2543+
such as queue deletion. Note that as it is not a MUST for
2544+
clients to accept this method from the client, it is advisable
2545+
for the broker to be able to identify those clients that are
2546+
capable of accepting the method, through some means of
2547+
capability negotiation.
25362548
</doc>
25372549

25382550
<rule name = "01">
@@ -2546,6 +2558,7 @@
25462558
</rule>
25472559

25482560
<chassis name = "server" implement = "MUST" />
2561+
<chassis name = "client" implement = "SHOULD" />
25492562
<response name = "cancel-ok" />
25502563

25512564
<field name = "consumer-tag" domain = "consumer-tag" />
@@ -2557,6 +2570,7 @@
25572570
This method confirms that the cancellation was completed.
25582571
</doc>
25592572
<chassis name = "client" implement = "MUST" />
2573+
<chassis name = "server" implement = "MAY" />
25602574
<field name = "consumer-tag" domain = "consumer-tag" />
25612575
</method>
25622576

projects/client/RabbitMQ.Client/src/client/api/AmqpTcpEndpoint.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
using System;
4242
using System.Collections;
43+
using System.Text.RegularExpressions;
4344
using RabbitMQ.Client.Impl;
4445

4546
namespace RabbitMQ.Client
@@ -243,9 +244,24 @@ public override int GetHashCode()
243244
/// entire string is used as the hostname, and the port-number
244245
/// is set to -1 (meaning the default number for the protocol
245246
/// variant specified).
247+
/// Hostnames provided as IPv6 must appear in square brackets ([]).
246248
///</remarks>
247249
public static AmqpTcpEndpoint Parse(IProtocol protocol, string address) {
248-
int index = address.IndexOf(':');
250+
Match match = Regex.Match(address, @"^\s*\[([%:0-9A-Fa-f]+)\](:(.*))?\s*$");
251+
if (match.Success)
252+
{
253+
GroupCollection groups = match.Groups;
254+
int portNum = -1;
255+
if (groups[2].Success)
256+
{
257+
string portStr = groups[3].Value;
258+
portNum = (portStr.Length == 0) ? -1 : int.Parse(portStr);
259+
}
260+
return new AmqpTcpEndpoint(protocol,
261+
match.Groups[1].Value,
262+
portNum);
263+
}
264+
int index = address.LastIndexOf(':');
249265
if (index == -1) {
250266
return new AmqpTcpEndpoint(protocol, address, -1);
251267
} else {

projects/client/RabbitMQ.Client/src/client/api/DefaultBasicConsumer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ public virtual void HandleBasicCancelOk(string consumerTag)
131131
OnCancel();
132132
}
133133

134+
///<summary>Default implementation - calls OnCancel().</summary>
135+
public virtual void HandleBasicCancel(string consumerTag)
136+
{
137+
OnCancel();
138+
}
139+
134140
///<summary>Default implementation - sets ShutdownReason and
135141
///calls OnCancel().</summary>
136142
public virtual void HandleModelShutdown(IModel model, ShutdownEventArgs reason)

projects/client/RabbitMQ.Client/src/client/api/IBasicConsumer.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ public interface IBasicConsumer
7070
///consumer from the broker.</summary>
7171
void HandleBasicCancelOk(string consumerTag);
7272

73+
/// <summary>
74+
/// Called when the consumer is cancelled for reasons other than by a
75+
/// basicCancel: e.g. the queue has been deleted (either by this channel or
76+
/// by any other channel). See handleCancelOk for notification of consumer
77+
/// cancellation due to basicCancel.
78+
/// </summary>
79+
void HandleBasicCancel(string consumerTag);
80+
7381
///<summary>Called when the model shuts down.</summary>
7482
void HandleModelShutdown(IModel model, ShutdownEventArgs reason);
7583

projects/client/RabbitMQ.Client/src/client/api/IModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,8 @@ void HandleBasicDeliver(string consumerTag,
801801
[AmqpContentBodyMapping]
802802
byte[] body);
803803

804+
void HandleBasicCancel(string consumerTag, bool nowait);
805+
804806
///<summary>Handle incoming Basic.Return methods. Signals a
805807
///BasicReturnEvent.</summary>
806808
void HandleBasicReturn(ushort replyCode,

projects/client/RabbitMQ.Client/src/client/impl/ModelBase.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,29 @@ public void HandleBasicDeliver(string consumerTag,
556556
}
557557
}
558558

559+
public void HandleBasicCancel(string consumerTag, bool nowait)
560+
{
561+
IBasicConsumer consumer;
562+
lock (m_consumers)
563+
{
564+
consumer = (IBasicConsumer)m_consumers[consumerTag];
565+
m_consumers.Remove(consumerTag);
566+
}
567+
if (consumer == null)
568+
{
569+
consumer = DefaultConsumer;
570+
}
571+
572+
try {
573+
consumer.HandleBasicCancel(consumerTag);
574+
} catch (Exception e) {
575+
CallbackExceptionEventArgs args = new CallbackExceptionEventArgs(e);
576+
args.Detail["consumer"] = consumer;
577+
args.Detail["context"] = "HandleBasicCancel";
578+
OnCallbackException(args);
579+
}
580+
}
581+
559582
public void HandleBasicReturn(ushort replyCode,
560583
string replyText,
561584
string exchange,

projects/client/RabbitMQ.Client/src/client/impl/SocketFrameHandler_0_9.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,24 @@ public class SocketFrameHandler_0_9 : IFrameHandler
6060
public SocketFrameHandler_0_9(AmqpTcpEndpoint endpoint)
6161
{
6262
m_endpoint = endpoint;
63-
m_socket = new TcpClient();
64-
m_socket.Connect(endpoint.HostName, endpoint.Port);
63+
m_socket = null;
64+
if (Socket.OSSupportsIPv6)
65+
{
66+
try
67+
{
68+
m_socket = new TcpClient(AddressFamily.InterNetworkV6);
69+
m_socket.Connect(endpoint.HostName, endpoint.Port);
70+
}
71+
catch(SocketException)
72+
{
73+
m_socket = null;
74+
}
75+
}
76+
if (m_socket == null)
77+
{
78+
m_socket = new TcpClient(AddressFamily.InterNetwork);
79+
m_socket.Connect(endpoint.HostName, endpoint.Port);
80+
}
6581
// disable Nagle's algorithm, for more consistently low latency
6682
m_socket.NoDelay = true;
6783

@@ -88,12 +104,12 @@ public AmqpTcpEndpoint Endpoint
88104

89105
public int Timeout
90106
{
91-
set
92-
{
93-
if (m_socket.Connected)
94-
{
95-
m_socket.ReceiveTimeout = value;
96-
}
107+
set
108+
{
109+
if (m_socket.Connected)
110+
{
111+
m_socket.ReceiveTimeout = value;
112+
}
97113
}
98114
}
99115

projects/client/RabbitMQ.Client/src/client/impl/v0_9_1/ProtocolBase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public ProtocolBase() {
5151
Capabilities["publisher_confirms"] = true;
5252
Capabilities["exchange_exchange_bindings"] = true;
5353
Capabilities["basic.nack"] = true;
54+
Capabilities["consumer_cancel_notify"] = true;
5455
}
5556

5657
public override IFrameHandler CreateFrameHandler(AmqpTcpEndpoint endpoint) {

projects/client/Unit/src/unit/TestAmqpTcpEndpointParsing.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,23 @@ public void TestMultipleTwoMultipleCommas()
145145
Assert.AreEqual("other", es[1].HostName);
146146
Assert.AreEqual(2345, es[1].Port);
147147
}
148+
149+
[Test]
150+
public void TestIpv6WithPort()
151+
{
152+
AmqpTcpEndpoint e = AmqpTcpEndpoint.Parse(Protocols.DefaultProtocol, "[::1]:1234");
153+
Assert.AreEqual(Protocols.DefaultProtocol, e.Protocol);
154+
Assert.AreEqual("::1", e.HostName);
155+
Assert.AreEqual(1234, e.Port);
156+
}
157+
158+
[Test]
159+
public void TestIpv6WithoutPort()
160+
{
161+
AmqpTcpEndpoint e = AmqpTcpEndpoint.Parse(Protocols.DefaultProtocol, "[::1]");
162+
Assert.AreEqual(Protocols.DefaultProtocol, e.Protocol);
163+
Assert.AreEqual("::1", e.HostName);
164+
Assert.AreEqual(Protocols.DefaultProtocol.DefaultPort, e.Port);
165+
}
148166
}
149167
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// This source code is dual-licensed under the Apache License, version
2+
// 2.0, and the Mozilla Public License, version 1.1.
3+
//
4+
// The APL v2.0:
5+
//
6+
//---------------------------------------------------------------------------
7+
// Copyright (C) 2007-2011 VMware, Inc.
8+
//
9+
// Licensed under the Apache License, Version 2.0 (the "License");
10+
// you may not use this file except in compliance with the License.
11+
// You may obtain a copy of the License at
12+
//
13+
// http://www.apache.org/licenses/LICENSE-2.0
14+
//
15+
// Unless required by applicable law or agreed to in writing, software
16+
// distributed under the License is distributed on an "AS IS" BASIS,
17+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
// See the License for the specific language governing permissions and
19+
// limitations under the License.
20+
//---------------------------------------------------------------------------
21+
//
22+
// The MPL v1.1:
23+
//
24+
//---------------------------------------------------------------------------
25+
// The contents of this file are subject to the Mozilla Public License
26+
// Version 1.1 (the "License"); you may not use this file except in
27+
// compliance with the License. You may obtain a copy of the License
28+
// at http://www.mozilla.org/MPL/
29+
//
30+
// Software distributed under the License is distributed on an "AS IS"
31+
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
32+
// the License for the specific language governing rights and
33+
// limitations under the License.
34+
//
35+
// The Original Code is RabbitMQ.
36+
//
37+
// The Initial Developer of the Original Code is VMware, Inc.
38+
// Copyright (c) 2007-2011 VMware, Inc. All rights reserved.
39+
//---------------------------------------------------------------------------
40+
41+
using NUnit.Framework;
42+
43+
using System;
44+
using System.IO;
45+
using System.Text;
46+
using System.Collections;
47+
using System.Threading;
48+
49+
using RabbitMQ.Client;
50+
using RabbitMQ.Client.Impl;
51+
using RabbitMQ.Util;
52+
53+
namespace RabbitMQ.Client.Unit {
54+
[TestFixture]
55+
public class TestConsumerCancelNotify {
56+
57+
Object lockObject = new Object();
58+
bool notified = false;
59+
60+
[Test]
61+
public void TestConsumerCancelNotification() {
62+
string queue = "queue_consumer_notify";
63+
ConnectionFactory connFactory = new ConnectionFactory();
64+
IConnection conn = connFactory.CreateConnection();
65+
IModel chan = conn.CreateModel();
66+
chan.QueueDeclare(queue, false, true, false, null);
67+
IBasicConsumer consumer = new CancelNotificationConsumer(chan, this);
68+
chan.BasicConsume(queue, false, consumer);
69+
70+
chan.QueueDelete(queue);
71+
lock (lockObject) {
72+
if (!notified) {
73+
Monitor.Wait(lockObject);
74+
}
75+
Assert.IsTrue(notified);
76+
}
77+
}
78+
79+
public class CancelNotificationConsumer : QueueingBasicConsumer
80+
{
81+
TestConsumerCancelNotify testClass;
82+
83+
public CancelNotificationConsumer(IModel model, TestConsumerCancelNotify tc) : base(model) {
84+
this.testClass = tc;
85+
}
86+
87+
public override void HandleBasicCancel(string consumerTag) {
88+
lock (testClass.lockObject) {
89+
testClass.notified = true;
90+
Monitor.PulseAll(testClass.lockObject);
91+
}
92+
}
93+
}
94+
}
95+
}
96+

0 commit comments

Comments
 (0)