AMQP 0-9-1 Errata

Here we list errors and ambiguities in the 0-9-1 specification and provide our interpretations and corrections.

1 Use of "consumer"

The word "consumer" is used fairly indiscriminately. In general, we've interpreted it to mean "client", except in situations in which it clearly refers specifically to a triple {connection, channel, consumer-tag}.

An example of the first, generic meaning is in channel.flow: "This method asks the peer to pause or restart the flow of content data sent by a consumer."

An example of the second, specific use is in basic.consume: "Request exclusive consumer access, meaning only this consumer can access the queue."

(This should really be cleaned up in subsequent revisions of the specification.)

2 basic.recover with requeue=false

The stanza for basic.recover says

"This method asks the server to redeliver all unacknowledged messages on a specified channel. Zero or more messages may be redelivered."

and

"If [requeue] is zero, the message will be redelivered to the original recipient. If this bit is 1, the server will attempt to requeue the message, potentially then delivering it to an alternative subscriber."

These don't account for

RabbitMQ doesn't try to redeliver messages that were sent as a response to basic.get, and doesn't attempt to find out if a consumer has been cancelled.

(Probably the best solution to this under-specification is to remove the requeue flag and just always requeue things.)

3 Field types

Tony to the AMQ dev list:

The changes from 0-9's table field type definitions (S,I,D,T,F,V) to
0-9-1's type definitions (t,b,B,U,u,I,i,L,l,f,d,D,s,S,A,T,F,V) are not,
as far as I can tell, the changes that were discussed in the 0-9-1 SIG.

The types mentioned in the final spec do not line up with any of the
variants discussed in the 0-9-1 SIG. In particular, they are
gratuitously INCOMPATIBLE with the Qpid-java 0-9 field type
extensions, which are also implemented by the RabbitMQ client libraries
and the RabbitMQ broker.

The final word I can find on the subject by trawling my archive is a
message from John O'Hara (attached). Unless there was subsequent
discussion that I've missed, I would have expected the final version of
the 0-9-1 spec to at least be based on the list John provided.

Here's a tabular summary of the state of things:

  0-9   0-9-1   Qpid/Rabbit  Type               Remarks
---------------------------------------------------------------------------
        t       t            Boolean
        b       b            Signed 8-bit
        B                    Unsigned 8-bit
        U       s            Signed 16-bit      (A1)
        u                    Unsigned 16-bit
  I     I       I            Signed 32-bit
        i                    Unsigned 32-bit
        L       l            Signed 64-bit      (B)
        l                    Unsigned 64-bit
        f       f            32-bit float
        d       d            64-bit float
  D     D       D            Decimal
        s                    Short string       (A2)
  S     S       S            Long string
        A       A            Array              (C)
  T     T       T            Timestamp (u64)
  F     F       F            Nested Table
  V     V       V            Void
                x            Byte array         (D)

Remarks:

 A1, A2: Notice how the types CONFLICT here. In Qpid and Rabbit,
         's' means a signed 16-bit integer; in 0-9-1, it means a
         short string.

 B: Notice how the signednesses CONFLICT here. In Qpid and Rabbit,
    'l' means a signed 64-bit integer; in 0-9-1, it means an unsigned
    64-bit integer.

 C: I cannot find any discussion about the addition of this. Is my
    archive missing a few messages?

 D: John [O'Hara]objected to this when he proposed his list. I believe it to
    be vital: byte arrays are not strings. Furthermore, Qpid and
    Rabbit already have code deployed that uses this type specifier.

RabbitMQ continues to use the tags in the third column.

Other notes:

4 field-array

The protocol grammar has a production for field-array, and it appears on the right-hand-side of a production for field-value.

field-array = long-int *field-value

There is no explanation elsewhere for this, as there is for field-table. In particular, there is nothing explaining what the long-int value represents. RabbitMQ uses the total length in bytes of the encoded field-values, similarly to field-table; it also treats it as a long-uint, since that is what field-table uses (on the supposition that "long-int" is a typo).

5 Exclusive and auto-delete deletion

AMQP 0-8 and 0-9-1 both have rules regarding deletion of exclusive and auto-delete queues. However, they do not specify whether this happens synchronously; i.e., before the broker responds to its peer.

(Actually 0-8 said that the server should wait a "polite amount of time").

RabbitMQ now deletes auto-delete and queues synchronously with basic.cancel, channel close, and connection close, and exclusive queues synchronously with the latter two.

6 Queue and exchange "equivalent" and "pre-existence" rules

"If [passive] not set and the queue exists, the server MUST check that the existing queue has the same values for durable, exclusive, auto-delete, and arguments fields. The server MUST respond with Declare-Ok if the requested queue matches these fields, and MUST raise a channel exception if not."

and similarly for exchange declaration.

The rules fail to specify the exact error code. RabbitMQ uses 'precondition-failed'.

Furthermore, the exchange.declare specs contain a separate "typed" rule concerning the re-declaration with a different type. That rule is superfluous, since it subsumed by the equivalence rules, and specifies a connection error of 'not-allowed'. The rule should be removed.

7 Exclusive rule (amqp0-9-1.xml:1503)

"One client declares an exclusive queue. A second client on a different connection attempts to declare, bind, consume, purge, delete, or declare a queue of the same name."

"declare" appears twice, and basic.get and queue.unbind not at all (basic.cancel is local to a channel anyway). RabbitMQ extends the exclusivity to queue.declare (including passive declare), queue.bind, queue.unbind, queue.purge, queue.delete, basic.consume, and basic.get.

8 Exclusive and other errors

In general, RabbitMQ privileges exclusivity ahead of other things; i.e., it checks that first, and will send a resource-locked even if there are other problems, like non-equivalence.

9 Passive and no-wait (amqp0-9-1.xml:1214,1423)

".. A declare with both passive and no-wait has no effect."

However, passively declaring a queue which exists and is exclusive to another connection is explicitly prohibited in the exclusive rule (amqp0-9-1.xml:1486), and passively declaring a queue or exchange that doesn't exist should send a channel exception. RabbitMQ ignores the sentence quoted above; i.e., it will send a channel exception if a queue exclusive to another connection, or a non-existent queue or exchange, is passively declared even with no-wait.

10 Special case value for channel-max and frame-max

The spec for the channel-max field in connection.tune says "Zero indicates no specified limit" and there is a similar special case for frame-max; but, in tune-ok, the "upper-limit" rule says: "If the client specifies a channel max [/frame-max]that is higher than the value provided by the server, the server MUST close the connection without attempting a negotiated close".

This is ambiguous if the server sends zero in tune: if the rule is taken literally, the client is not allowed to send any value other than zero in tune-ok, so it can't negotiate down the limit.

RabbitMQ doesn't put a limit on channel-max, and treats any number in tune-ok as valid. It does put a limit on frame-max, and checks that the value sent in tune-ok is less than or equal.

11 frame-max, method and content header frames

The specification states that frame-max (negotiated in connection.tune and connection.tune-ok) gives an upper limit to total frame size; that is, including the frame header and frame-end octet. However, there's no way to split methods or content headers across multiple frames. RabbitMQ currently ignores the frame-max for methods and content headers.

12 Heartbeat format

The grammar has the production

heartbeat = %d8 %d0 %d0 frame-end

which doesn't fit the frame format. RabbitMQ, Qpid and OpenAMQ all send eight bytes, following the frame format:

frame-type (octet) = %d8 channel (short) = %d0 %d0 payload (long) = %d0 %d0 %d0 %d0 frame-end (octet) = %xCE

13 Heartbeat monitoring

The spec says:

The client should start sending heartbeats after receiving a Connection.Tune
method, and start monitoring heartbeats after receiving Connection.Open.

but of course, the client sends Connection.Open.

14 Default exchange

The spec is a bit vague on the default exchange: it says

The server MUST NOT allow clients to access the default exchange except
by specifying an empty exchange name in the Queue.Bind and content Publish
methods.

However, 0-10 spec makes things a bit more rigid:

The default exchange MUST NOT be accessible to the client except by
specifying an empty exchange name in a content publish command (such as
message.transfer). That is, the server must not let clients explicitly
bind, unbind, delete, or make any other reference to this exchange.

RabbitMQ prevents all access to the default exchange other than publishing. This includes exchange redeclaration and queue.bind redeclarations to queues. For exchange-to-exchange bindings, neither the source or the destination are permitted to be the default exchange.

15 EBNF Grammar

The EBNF grammar (p. 32) is broken: 8*OCTET should be 8 OCTET, according to the syntax description 8*OCTET means AT LEAST 8 octets (see bullet point 'The Operator "*" ...').

16 Strings vs bytes

The spec is confused about utf-8 strings vs. raw bytes. E.g. what's a "character"? A valid code point? An octet? What does equality mean? Same bytes? Same in some normal form? How is mal-formed utf-8 handled? The grammar on p. 31 suggests strings are just raw bytes (i.e. can contain invalid utf-8), but other parts of the spec seem to contradict that (e.g. 4.2.5.3, p. 35)

17 Missing 312 ("No route") channel exception

Section 1.2 ought to define an exception 312 "No route", which used to exist in 0-9 and is what RabbitMQ sends back with 'basic.return' when a 'mandatory' message cannot be delivered to any queue.

18 502 channel exception

Section 1.1 under queue-name says "If the client did not declare a queue, and the method needs a queue name, this will result in a 502 (syntax error) channel exception". This is in fact a connection exception.

19 auto-delete vs exclusive

Section 4.5 says "When the server closes a connection, it deletes any auto-delete owned by that connection." That's not what auto-delete means - this should presumably say "...deletes any exclusive queues owned...".

20 Durability of amq.* exchanges

The spec doesn't say if the standard exchanges should be durable or not. RabbitMQ declares them as durable. Unfortunately, the Qpid Java Client forcibly declares them as non durable (through N levels of abstractions), thus breaking interoperability almost completely.

21 Rejecting messages with an unknown delivery tag

The spec doesn't say what should happen when a client sends a basic.reject with an unknown delivery tag. The server should probably handle this in the same way as for basic.ack, namely a raise a precondition_failed error.

22 Heartbeat errors

In section 4.2.7 the spec says "A peer that receives an invalid heartbeat frame MUST raise a connection exception with reply code 501 (frame error)." It is unclear how a heartbeat frame can be identified as such and be invalid simultaneously.

23 Required server-properties

The xml spec for connection.start states that the server-properties field "SHOULD contain at least these fields: 'host', specifying the server host name or address ...". The 'host' field was added to this list in 0-9; it is not present in 0-8. It is the only field whose value isn't freely chosen by the implementation. Furthermore, it is not clear at all what information should be included here and what a client could learn from that. The hostname may not identify a broker uniquely. And it may not be DNS resolvable. And the presence of firewalls and proxies makes the same true of the IP address. Additionally, including this information may reveal details of the brokers network infrastructure to clients that ought to remain hidden.

Therefore this field should not be required. RabbitMQ does not supply it.

24 'nowait' on all synchronous commands

All synchronous commands should have a 'nowait' flag that makes them asynchronous, unless there are specific circumstances where that doesn't make sense. Currently falling foul of this are: 'queue.unbind', 'basic.qos', 'basic.recover' (which instead has a sync and async variant), 'tx.select', 'tx.commit', 'tx.rollback'. Of these, 'queue.unbind' is the most glaring departure from the established pattern.

25 Deprecation of auto-delete exchanges

The 'auto-delete' flag on 'exchange.declare' got deprecated in 0-9-1. Auto-delete exchanges are actually quite useful, so this flag should be restored.

Also note that the spec still refers to auto-delete exchanges: "Exchanges may be durable, temporary, or auto-deleted." (p. 26).

RabbitMQ supports auto-delete exchanges.

26 Deprecation of internal exchanges

The 'internal' flag on 'exchange.declare' got deprecated in 0-9-1. Internal exchanges (which 0-8 defines as exchanges used for internal wiring to which clients are not allowed to publish directly) are actually quite useful, particular in combination with extensions such as exchange-to-exchange bindings. So this flag should be restored.

Also note that the basic publish rule (02) still makes reference to internal exchanges: "If the exchange was declared as an internal exchange..."

27 Basic content field explanation references queues

The XML comments on the basic content fields state that "delivery-mode" is for "for queues that implement persistence". That is misleading (in that it mentions queues) and circular. The delivery-mode controls whether messages survive a broker restart.

Similarly, the comments state that "priority" is "for queues that implement priorities". Priorities can be taken into account by a broker in all stages of message processing, not just when enqueuing.

And on a cosmetic note, the vertical alignment of the basic content definitions is slightly off.

28 basic.recover synchronicity

Although basic.recover is introduced as a synchronous version of basic.recover-async, it is not marked as synchronous in the XML spec.

29 Heartbeat frametype inconsistency

The PDF specifies the heartbeat frametype as 4. The XML specifies it as 8.

30 durable vs exclusive

The spec does not define what happens if a client tries to declare a queue which is both durable and exclusive. RabbitMQ will treat a request to declare such a queue as a request for an exclusive transient queue.