diff --git a/src/Connection.cs b/src/Connection.cs index 7f19b77b..63249264 100644 --- a/src/Connection.cs +++ b/src/Connection.cs @@ -121,6 +121,16 @@ public partial class Connection : AmqpObject uint remoteMaxFrameSize; ITransport writer; HeartBeat heartBeat; + Open deferredOpen; + + // When false, the local Open is not pipelined with the protocol header but is + // deferred until the peer's protocol header is received. Listener connections + // set this to avoid coalescing the header and Open into a single TCP segment, + // which some clients (e.g. the Node rhea library) fail to parse. + internal virtual bool PipelineOpen + { + get { return true; } + } Connection(Address address, ushort channelMax, uint maxFrameSize) { @@ -280,8 +290,17 @@ internal void Init(IBufferManager bufferManager, AmqpSettings amqpSettings, IAsy } this.SendHeader(); - this.SendOpen(open); - this.state = ConnectionState.OpenPipe; + if (this.PipelineOpen) + { + this.SendOpen(open); + this.state = ConnectionState.OpenPipe; + } + else + { + // Defer the Open until the peer's protocol header arrives (see PipelineOpen). + this.deferredOpen = open; + this.state = ConnectionState.HeaderSent; + } } static ConnectionFactory connectionFactory; @@ -517,6 +536,14 @@ protected override bool OnClose(Error error) { newState = ConnectionState.OpenClosePipe; } + else if (this.state == ConnectionState.HeaderSent) + { + // Non-pipelined Open was deferred and never sent; send it now so the + // peer sees a well-formed Open/Close sequence once its header arrives. + this.SendOpen(this.deferredOpen); + this.deferredOpen = null; + newState = ConnectionState.OpenClosePipe; + } else if (state == ConnectionState.OpenSent) { newState = ConnectionState.ClosePipe; @@ -764,6 +791,15 @@ internal bool OnHeader(ProtocolHeader header) { this.state = ConnectionState.OpenSent; } + else if (this.state == ConnectionState.HeaderSent) + { + // Deferred (non-pipelined) Open: now that the peer's header has + // arrived, send our Open as a separate write so it is not coalesced + // with the protocol header. + this.SendOpen(this.deferredOpen); + this.deferredOpen = null; + this.state = ConnectionState.OpenSent; + } else if (this.state == ConnectionState.OpenClosePipe) { this.state = ConnectionState.ClosePipe; diff --git a/src/Listener/ListenerConnection.cs b/src/Listener/ListenerConnection.cs index 631fab76..d297e92e 100644 --- a/src/Listener/ListenerConnection.cs +++ b/src/Listener/ListenerConnection.cs @@ -37,6 +37,14 @@ internal ListenerConnection(ConnectionListener listener, Address address, IHandl this.listener = listener; } + // Defer the local Open until the client's protocol header is received. Pipelining + // the header and Open into one TCP segment breaks some clients (Node rhea), which + // intermittently fail to parse the coalesced bytes and hang until their open timeout. + internal override bool PipelineOpen + { + get { return false; } + } + /// /// Gets a IPrincipal object for the connection. If the value is null, /// the connection is not authenticated.