31
31
32
32
using System ;
33
33
using System . Buffers ;
34
+ using System . Diagnostics ;
34
35
using System . IO ;
36
+ using System . IO . Pipelines ;
35
37
using System . Net . Sockets ;
36
38
using System . Runtime . CompilerServices ;
37
39
using System . Runtime . ExceptionServices ;
40
+ using System . Threading . Tasks ;
38
41
39
42
using RabbitMQ . Client . Exceptions ;
40
43
using RabbitMQ . Client . Framing . Impl ;
@@ -130,14 +133,14 @@ internal static class Heartbeat
130
133
131
134
/// <summary>
132
135
/// Compiler trick to directly refer to static data in the assembly, see here: https://github.com/dotnet/roslyn/pull/24621
133
- /// </summary>
134
- private static ReadOnlySpan < byte > Payload => new byte [ ]
135
- {
136
- Constants . FrameHeartbeat ,
137
- 0 , 0 , // channel
138
- 0 , 0 , 0 , 0 , // payload length
139
- Constants . FrameEnd
140
- } ;
136
+ /// A heartbeat frame has the following layout:
137
+ /// +--------------------+------------------+-----------------+--------------------------+
138
+ /// | Frame Type(1 byte) | Channel(2 bytes) | Length(4 bytes) | End Frame Marker(1 byte) |
139
+ /// +--------------------+------------------+-----------------+--------------------------+
140
+ /// | 0x08 | 0x0000 | 0x00000000 | 0xCE |
141
+ /// +--------------------+------------------+-----------------+--------------------------+
142
+ ///</summary>
143
+ private static ReadOnlySpan < byte > Payload => new byte [ ] { Constants . FrameHeartbeat , 0 , 0 , 0 , 0 , 0 , 0 , Constants . FrameEnd } ;
141
144
142
145
public static Memory < byte > GetHeartbeatFrame ( )
143
146
{
@@ -201,7 +204,7 @@ private static int GetBodyFrameCount(int maxPayloadBytes, int length)
201
204
}
202
205
}
203
206
204
- internal readonly ref struct InboundFrame
207
+ internal readonly struct InboundFrame
205
208
{
206
209
public readonly FrameType Type ;
207
210
public readonly int Channel ;
@@ -216,22 +219,24 @@ private InboundFrame(FrameType type, int channel, ReadOnlyMemory<byte> payload,
216
219
_rentedArray = rentedArray ;
217
220
}
218
221
219
- private static void ProcessProtocolHeader ( Stream reader , ReadOnlySpan < byte > frameHeader )
222
+ private static void ProcessProtocolHeader ( ReadOnlySequence < byte > buffer )
220
223
{
221
224
try
222
225
{
223
- if ( frameHeader [ 0 ] != 'M' || frameHeader [ 1 ] != 'Q' || frameHeader [ 2 ] != 'P' )
226
+ if ( buffer . Length < 8 )
224
227
{
225
- throw new MalformedFrameException ( "Invalid AMQP protocol header from server" ) ;
228
+ throw new EndOfStreamException ( ) ;
226
229
}
227
230
228
- int serverMinor = reader . ReadByte ( ) ;
229
- if ( serverMinor == - 1 )
231
+ Span < byte > tempSpan = stackalloc byte [ 8 ] ;
232
+ buffer . Slice ( 0 , 8 ) . CopyTo ( tempSpan ) ;
233
+
234
+ if ( tempSpan [ 1 ] != 'M' || tempSpan [ 2 ] != 'Q' || tempSpan [ 3 ] != 'P' )
230
235
{
231
- throw new EndOfStreamException ( ) ;
236
+ throw new MalformedFrameException ( "Invalid AMQP protocol header from server" ) ;
232
237
}
233
238
234
- throw new PacketNotRecognizedException ( frameHeader [ 3 ] , frameHeader [ 4 ] , frameHeader [ 5 ] , serverMinor ) ;
239
+ throw new PacketNotRecognizedException ( tempSpan [ 4 ] , tempSpan [ 5 ] , tempSpan [ 6 ] , tempSpan [ 7 ] ) ;
235
240
}
236
241
catch ( EndOfStreamException )
237
242
{
@@ -246,38 +251,59 @@ private static void ProcessProtocolHeader(Stream reader, ReadOnlySpan<byte> fram
246
251
}
247
252
}
248
253
249
- internal static InboundFrame ReadFrom ( Stream reader , byte [ ] frameHeaderBuffer , uint maxMessageSize )
254
+ internal static async ValueTask < InboundFrame > ReadFromPipe ( PipeReader reader , uint maxMessageSize )
250
255
{
251
- try
256
+ // Try a synchronous read first, then go async
257
+ if ( ! reader . TryRead ( out ReadResult result ) )
252
258
{
253
- ReadFromStream ( reader , frameHeaderBuffer , frameHeaderBuffer . Length ) ;
259
+ result = await reader . ReadAsync ( ) . ConfigureAwait ( false ) ;
254
260
}
255
- catch ( IOException ioe )
261
+
262
+ ReadOnlySequence < byte > buffer = result . Buffer ;
263
+
264
+ if ( result . IsCompleted || buffer . Length == 0 )
256
265
{
257
- // If it's a WSAETIMEDOUT SocketException, unwrap it.
258
- // This might happen when the limit of half-open connections is
259
- // reached.
260
- if ( ioe ? . InnerException is SocketException exception && exception . SocketErrorCode == SocketError . TimedOut )
261
- {
262
- ExceptionDispatchInfo . Capture ( exception ) . Throw ( ) ;
263
- }
264
- else
266
+ throw new EndOfStreamException ( "Pipe is completed." ) ;
267
+ }
268
+
269
+ byte firstByte = buffer . First . Span [ 0 ] ;
270
+ if ( firstByte == 'A' )
271
+ {
272
+ ProcessProtocolHeader ( buffer ) ;
273
+ }
274
+
275
+ InboundFrame frame ;
276
+
277
+ while ( ! TryReadFrame ( ref buffer , maxMessageSize , out frame ) )
278
+ {
279
+ reader . AdvanceTo ( buffer . Start , buffer . End ) ;
280
+
281
+ // No need to try a synchronous read since we have an incomplete frame anyway, so we'll always need to go async
282
+ result = await reader . ReadAsync ( ) . ConfigureAwait ( false ) ;
283
+
284
+ if ( result . IsCompleted || buffer . Length == 0 )
265
285
{
266
- throw ;
286
+ throw new EndOfStreamException ( "Pipe is completed." ) ;
267
287
}
288
+
289
+ buffer = result . Buffer ;
268
290
}
269
291
270
- byte firstByte = frameHeaderBuffer [ 0 ] ;
271
- if ( firstByte == 'A' )
292
+ reader . AdvanceTo ( buffer . Start ) ;
293
+ return frame ;
294
+ }
295
+
296
+ internal static bool TryReadFrame ( ref ReadOnlySequence < byte > buffer , uint maxMessageSize , out InboundFrame frame )
297
+ {
298
+ if ( buffer . Length < 7 )
272
299
{
273
- // Probably an AMQP protocol header, otherwise meaningless
274
- ProcessProtocolHeader ( reader , frameHeaderBuffer . AsSpan ( 1 , 6 ) ) ;
300
+ frame = default ;
301
+ return false ;
275
302
}
276
303
277
- FrameType type = ( FrameType ) firstByte ;
278
- var frameHeaderSpan = new ReadOnlySpan < byte > ( frameHeaderBuffer , 1 , 6 ) ;
279
- int channel = NetworkOrderDeserializer . ReadUInt16 ( frameHeaderSpan ) ;
280
- int payloadSize = NetworkOrderDeserializer . ReadInt32 ( frameHeaderSpan . Slice ( 2 , 4 ) ) ;
304
+ FrameType type = ( FrameType ) buffer . First . Span [ 0 ] ;
305
+ int channel = NetworkOrderDeserializer . ReadUInt16 ( buffer . Slice ( 1 ) ) ;
306
+ int payloadSize = NetworkOrderDeserializer . ReadInt32 ( buffer . Slice ( 3 ) ) ;
281
307
if ( ( maxMessageSize > 0 ) && ( payloadSize > maxMessageSize ) )
282
308
{
283
309
string msg = $ "Frame payload size '{ payloadSize } ' exceeds maximum of '{ maxMessageSize } ' bytes";
@@ -287,46 +313,29 @@ internal static InboundFrame ReadFrom(Stream reader, byte[] frameHeaderBuffer, u
287
313
const int EndMarkerLength = 1 ;
288
314
// Is returned by InboundFrame.ReturnPayload in Connection.MainLoopIteration
289
315
int readSize = payloadSize + EndMarkerLength ;
290
- byte [ ] payloadBytes = ArrayPool < byte > . Shared . Rent ( readSize ) ;
291
- try
292
- {
293
- ReadFromStream ( reader , payloadBytes , readSize ) ;
294
- }
295
- catch ( Exception )
296
- {
297
- // Early EOF.
298
- ArrayPool < byte > . Shared . Return ( payloadBytes ) ;
299
- throw new MalformedFrameException ( $ "Short frame - expected to read { readSize } bytes") ;
300
- }
301
316
302
- if ( payloadBytes [ payloadSize ] != Constants . FrameEnd )
317
+ if ( ( buffer . Length - 7 ) < readSize )
303
318
{
304
- ArrayPool < byte > . Shared . Return ( payloadBytes ) ;
305
- throw new MalformedFrameException ( $ "Bad frame end marker: { payloadBytes [ payloadSize ] } " ) ;
319
+ frame = default ;
320
+ return false ;
306
321
}
307
-
308
- RabbitMqClientEventSource . Log . DataReceived ( payloadSize + Framing . BaseFrameSize ) ;
309
- return new InboundFrame ( type , channel , new Memory < byte > ( payloadBytes , 0 , payloadSize ) , payloadBytes ) ;
310
- }
311
-
312
- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
313
- private static void ReadFromStream ( Stream reader , byte [ ] buffer , int toRead )
314
- {
315
- int bytesRead = 0 ;
316
- do
322
+ else
317
323
{
318
- int read = reader . Read ( buffer , bytesRead , toRead - bytesRead ) ;
319
- if ( read == 0 )
324
+ byte [ ] payloadBytes = ArrayPool < byte > . Shared . Rent ( readSize ) ;
325
+ ReadOnlySequence < byte > framePayload = buffer . Slice ( 7 , readSize ) ;
326
+ framePayload . CopyTo ( payloadBytes ) ;
327
+
328
+ if ( payloadBytes [ payloadSize ] != Constants . FrameEnd )
320
329
{
321
- ThrowEndOfStream ( ) ;
330
+ ArrayPool < byte > . Shared . Return ( payloadBytes ) ;
331
+ throw new MalformedFrameException ( $ "Bad frame end marker: { payloadBytes [ payloadSize ] } ") ;
322
332
}
323
333
324
- bytesRead += read ;
325
- } while ( bytesRead != toRead ) ;
326
-
327
- static void ThrowEndOfStream ( )
328
- {
329
- throw new EndOfStreamException ( "Reached the end of the stream. Possible authentication failure." ) ;
334
+ RabbitMqClientEventSource . Log . DataReceived ( payloadSize + Framing . BaseFrameSize ) ;
335
+ frame = new InboundFrame ( type , channel , new Memory < byte > ( payloadBytes , 0 , payloadSize ) , payloadBytes ) ;
336
+ // Advance the buffer
337
+ buffer = buffer . Slice ( 7 + readSize ) ;
338
+ return true ;
330
339
}
331
340
}
332
341
0 commit comments