@@ -32,7 +32,15 @@ public NamedPipeServer(
32
32
CancellationToken cancellationToken ,
33
33
bool skipUnknownMessages )
34
34
{
35
- _namedPipeServerStream = new ( pipeName , PipeDirection . InOut , maxNumberOfServerInstances , PipeTransmissionMode . Byte , PipeOptions . Asynchronous | PipeOptions . CurrentUserOnly ) ;
35
+ _namedPipeServerStream = new NamedPipeServerStream (
36
+ pipeName ,
37
+ PipeDirection . InOut ,
38
+ maxNumberOfServerInstances ,
39
+ PipeTransmissionMode . Byte ,
40
+ PipeOptions . Asynchronous | PipeOptions . CurrentUserOnly ,
41
+ inBufferSize : 0 ,
42
+ outBufferSize : 0 ) ;
43
+
36
44
_callback = callback ;
37
45
_cancellationToken = cancellationToken ;
38
46
_skipUnknownMessages = skipUnknownMessages ;
@@ -67,51 +75,69 @@ public async Task WaitConnectionAsync(CancellationToken cancellationToken)
67
75
/// </summary>
68
76
private async Task InternalLoopAsync ( CancellationToken cancellationToken )
69
77
{
70
- int currentMessageSize = 0 ;
71
- int missingBytesToReadOfWholeMessage = 0 ;
78
+ // This is an indicator when reading from the pipe whether we are at the start of a new message (i.e, we should read 4 bytes as message size)
79
+ // Note that the implementation assumes no overlapping messages in the pipe.
80
+ // The flow goes like:
81
+ // 1. MTP sends a request (and acquires lock).
82
+ // 2. SDK reads the request.
83
+ // 3. SDK sends a response.
84
+ // 4. MTP reads the response (and releases lock).
85
+ // This means that no two requests can be in the pipe at the same time.
86
+ bool isStartOfNewMessage = true ;
87
+ int remainingBytesToReadOfWholeMessage = 0 ;
72
88
while ( ! cancellationToken . IsCancellationRequested )
73
89
{
74
- int missingBytesToReadOfCurrentChunk = 0 ;
75
- int currentReadIndex = 0 ;
76
- int currentReadBytes = await _namedPipeServerStream . ReadAsync ( _readBuffer . AsMemory ( currentReadIndex , _readBuffer . Length ) , cancellationToken ) ;
77
- if ( currentReadBytes == 0 )
90
+ // If we are at the start of a new message, we need to read at least the message size.
91
+ int currentReadBytes = isStartOfNewMessage
92
+ ? await _namedPipeServerStream . ReadAtLeastAsync ( _readBuffer , minimumBytes : sizeof ( int ) , throwOnEndOfStream : false , cancellationToken )
93
+ : await _namedPipeServerStream . ReadAsync ( _readBuffer , cancellationToken ) ;
94
+
95
+ if ( currentReadBytes == 0 || ( isStartOfNewMessage && currentReadBytes < sizeof ( int ) ) )
78
96
{
79
97
// The client has disconnected
80
98
return ;
81
99
}
82
100
83
- // Reset the current chunk size
84
- missingBytesToReadOfCurrentChunk = currentReadBytes ;
101
+ // The local remainingBytesToProcess tracks the remaining bytes of what we have read from the pipe but not yet processed.
102
+ // At the beginning here, it contains everything we have read from the pipe.
103
+ // As we are processing the data in it, we continue to slice it.
104
+ Memory < byte > remainingBytesToProcess = _readBuffer . AsMemory ( 0 , currentReadBytes ) ;
85
105
86
- // If currentRequestSize is 0 , we need to read the message size
87
- if ( currentMessageSize == 0 )
106
+ // If the current read is the start of a new message , we need to read the message size first.
107
+ if ( isStartOfNewMessage )
88
108
{
89
109
// We need to read the message size, first 4 bytes
90
- if ( currentReadBytes < sizeof ( int ) )
91
- {
92
- throw new UnreachableException ( CliCommandStrings . DotnetTestPipeIncompleteSize ) ;
93
- }
110
+ remainingBytesToReadOfWholeMessage = BitConverter . ToInt32 ( remainingBytesToProcess . Span ) ;
111
+
112
+ // Now that we have read the size, we slice the remainingBytesToProcess.
113
+ remainingBytesToProcess = remainingBytesToProcess . Slice ( sizeof ( int ) ) ;
94
114
95
- currentMessageSize = BitConverter . ToInt32 ( _readBuffer , 0 ) ;
96
- missingBytesToReadOfCurrentChunk = currentReadBytes - sizeof ( int ) ;
97
- missingBytesToReadOfWholeMessage = currentMessageSize ;
98
- currentReadIndex = sizeof ( int ) ;
115
+ // Now that we have read the size, we are no longer at the start of a new message.
116
+ // If the current chunk ended up to be the full message, we will set this back to true later.
117
+ isStartOfNewMessage = false ;
99
118
}
100
119
101
- if ( missingBytesToReadOfCurrentChunk > 0 )
120
+ // We read the rest of the message.
121
+ // Note that this assumes that no messages are overlapping in the pipe.
122
+ if ( remainingBytesToProcess . Length > 0 )
102
123
{
103
124
// We need to read the rest of the message
104
- await _messageBuffer . WriteAsync ( _readBuffer . AsMemory ( currentReadIndex , missingBytesToReadOfCurrentChunk ) , cancellationToken ) ;
105
- missingBytesToReadOfWholeMessage -= missingBytesToReadOfCurrentChunk ;
125
+ await _messageBuffer . WriteAsync ( remainingBytesToProcess , cancellationToken ) ;
126
+ remainingBytesToReadOfWholeMessage -= remainingBytesToProcess . Length ;
127
+
128
+ // At this point, we have read everything in the remainingBytesToProcess.
129
+ // Note that while remainingBytesToProcess isn't accessed after this point, we still maintain the
130
+ // invariant that it tracks what we have read from the pipe but not yet processed.
131
+ remainingBytesToProcess = Memory < byte > . Empty ;
106
132
}
107
133
108
- if ( missingBytesToReadOfWholeMessage < 0 )
134
+ if ( remainingBytesToReadOfWholeMessage < 0 )
109
135
{
110
136
throw new UnreachableException ( CliCommandStrings . DotnetTestPipeOverlapping ) ;
111
137
}
112
138
113
139
// If we have read all the message, we can deserialize it
114
- if ( missingBytesToReadOfWholeMessage == 0 )
140
+ if ( remainingBytesToReadOfWholeMessage == 0 )
115
141
{
116
142
// Deserialize the message
117
143
_messageBuffer . Position = 0 ;
@@ -147,12 +173,19 @@ private async Task InternalLoopAsync(CancellationToken cancellationToken)
147
173
148
174
// Write the message size
149
175
byte [ ] bytes = _sizeOfIntArray ;
150
- BitConverter . TryWriteBytes ( bytes , sizeOfTheWholeMessage ) ;
176
+ if ( ! BitConverter . TryWriteBytes ( bytes , sizeOfTheWholeMessage ) )
177
+ {
178
+ throw new UnreachableException ( ) ;
179
+ }
180
+
151
181
await _messageBuffer . WriteAsync ( bytes , cancellationToken ) ;
152
182
153
183
// Write the serializer id
154
184
bytes = _sizeOfIntArray ;
155
- BitConverter . TryWriteBytes ( bytes , responseNamedPipeSerializer . Id ) ;
185
+ if ( ! BitConverter . TryWriteBytes ( bytes , responseNamedPipeSerializer . Id ) )
186
+ {
187
+ throw new UnreachableException ( ) ;
188
+ }
156
189
157
190
await _messageBuffer . WriteAsync ( bytes . AsMemory ( 0 , sizeof ( int ) ) , cancellationToken ) ;
158
191
@@ -164,10 +197,6 @@ private async Task InternalLoopAsync(CancellationToken cancellationToken)
164
197
{
165
198
await _namedPipeServerStream . WriteAsync ( _messageBuffer . GetBuffer ( ) . AsMemory ( 0 , ( int ) _messageBuffer . Position ) , cancellationToken ) ;
166
199
await _namedPipeServerStream . FlushAsync ( cancellationToken ) ;
167
- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
168
- {
169
- _namedPipeServerStream . WaitForPipeDrain ( ) ;
170
- }
171
200
}
172
201
finally
173
202
{
@@ -177,8 +206,8 @@ private async Task InternalLoopAsync(CancellationToken cancellationToken)
177
206
}
178
207
179
208
// Reset the control variables
180
- currentMessageSize = 0 ;
181
- missingBytesToReadOfWholeMessage = 0 ;
209
+ isStartOfNewMessage = true ;
210
+ remainingBytesToReadOfWholeMessage = 0 ;
182
211
}
183
212
}
184
213
}
0 commit comments