Repository navigation
Expand file tree
/
Copy pathMSBuildClientPacketPump.cs
More file actions
333 lines (286 loc) · 13.2 KB
/
Copy pathMSBuildClientPacketPump.cs
File metadata and controls
333 lines (286 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
#if NET
using System.Threading.Tasks;
#endif
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
namespace Microsoft.Build.BackEnd.Client
{
internal sealed class MSBuildClientPacketPump : INodePacketHandler, INodePacketFactory, IDisposable
{
/// <summary>
/// The queue of packets we have received but which have not yet been processed.
/// </summary>
public ConcurrentQueue<INodePacket> ReceivedPacketsQueue { get; }
/// <summary>
/// Set when packet pump receive packets and put them to <see cref="ReceivedPacketsQueue"/>.
/// </summary>
public AutoResetEvent PacketReceivedEvent { get; }
/// <summary>
/// Set when the packet pump terminates.
/// </summary>
public ManualResetEvent PacketPumpCompleted { get; }
/// <summary>
/// Exception appeared when the packet pump unexpectedly terminates (due to connection problems or because of deserialization issues).
/// </summary>
public Exception? PacketPumpException { get; set; }
/// <summary>
/// Set when packet pump should shutdown.
/// </summary>
private readonly ManualResetEvent _packetPumpShutdownEvent;
/// <summary>
/// The packet factory.
/// </summary>
private readonly NodePacketFactory _packetFactory;
/// <summary>
/// The memory stream for a read buffer.
/// </summary>
private readonly MemoryStream _readBufferMemoryStream;
/// <summary>
/// The thread which runs the asynchronous packet pump
/// </summary>
private Thread? _packetPumpThread;
/// <summary>
/// The stream from where to read packets.
/// </summary>
private readonly Stream _stream;
/// <summary>
/// The binary translator for reading packets.
/// </summary>
private readonly ITranslator _binaryReadTranslator;
/// <summary>
/// True if this side is gracefully disconnecting.
/// In such case we have sent last packet to server side and we expect
/// it will soon broke pipe connection - unless client do it first.
/// </summary>
private bool _isServerDisconnecting;
public MSBuildClientPacketPump(Stream stream)
{
ErrorUtilities.VerifyThrowArgumentNull(stream);
_stream = stream;
_isServerDisconnecting = false;
_packetFactory = new NodePacketFactory();
ReceivedPacketsQueue = new ConcurrentQueue<INodePacket>();
PacketReceivedEvent = new AutoResetEvent(false);
PacketPumpCompleted = new ManualResetEvent(false);
_packetPumpShutdownEvent = new ManualResetEvent(false);
_readBufferMemoryStream = new MemoryStream();
_binaryReadTranslator = BinaryTranslator.GetReadTranslator(_readBufferMemoryStream, InterningBinaryReader.CreateSharedBuffer());
}
#region INodePacketFactory Members
/// <summary>
/// Registers a packet handler.
/// </summary>
/// <param name="packetType">The packet type for which the handler should be registered.</param>
/// <param name="factory">The factory used to create packets.</param>
/// <param name="handler">The handler for the packets.</param>
public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler)
{
_packetFactory.RegisterPacketHandler(packetType, factory, handler);
}
/// <summary>
/// Unregisters a packet handler.
/// </summary>
/// <param name="packetType">The type of packet for which the handler should be unregistered.</param>
public void UnregisterPacketHandler(NodePacketType packetType)
{
_packetFactory.UnregisterPacketHandler(packetType);
}
/// <summary>
/// Deserializes and routes a packer to the appropriate handler.
/// </summary>
/// <param name="nodeId">The node from which the packet was received.</param>
/// <param name="packetType">The packet type.</param>
/// <param name="translator">The translator to use as a source for packet data.</param>
public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator)
{
_packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator);
}
/// <summary>
/// Routes a packet to the appropriate handler.
/// </summary>
/// <param name="nodeId">The node id from which the packet was received.</param>
/// <param name="packet">The packet to route.</param>
public void RoutePacket(int nodeId, INodePacket packet)
{
_packetFactory.RoutePacket(nodeId, packet);
}
#endregion
#region INodePacketHandler Members
/// <summary>
/// Called when a packet has been received.
/// </summary>
/// <param name="node">The node from which the packet was received.</param>
/// <param name="packet">The packet.</param>
public void PacketReceived(int node, INodePacket packet)
{
ReceivedPacketsQueue.Enqueue(packet);
PacketReceivedEvent.Set();
}
#endregion
#region Packet Pump
/// <summary>
/// Initializes the packet pump thread.
/// </summary>
public void Start()
{
_packetPumpThread = new Thread(PacketPumpProc)
{
IsBackground = true,
Name = "MSBuild Client Packet Pump"
};
_packetPumpThread.Start();
}
/// <summary>
/// Stops the packet pump thread.
/// </summary>
public void Stop()
{
_packetPumpShutdownEvent.Set();
_packetPumpThread?.Join();
}
/// <summary>
/// This method handles the packet pump reading. It will terminate when the terminate event is
/// set.
/// </summary>
/// <remarks>
/// Instead of throwing an exception, puts it in <see cref="PacketPumpException"/> and raises event <see cref="PacketPumpCompleted"/>.
/// </remarks>
private void PacketPumpProc()
{
RunReadLoop(_stream, _packetPumpShutdownEvent);
}
private void RunReadLoop(Stream localStream, ManualResetEvent localPacketPumpShutdownEvent)
{
CommunicationsUtilities.Trace("Entering read loop.");
try
{
byte[] headerByte = new byte[5];
#if FEATURE_APM
IAsyncResult result = localStream.BeginRead(headerByte, 0, headerByte.Length, null, null);
#else
Task<int> readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length).AsTask();
#endif
bool continueReading = true;
do
{
// Ordering of the wait handles is important. The first signaled wait handle in the array
// will be returned by WaitAny if multiple wait handles are signaled. We prefer to have the
// terminate event triggered so that we cannot get into a situation where packets are being
// spammed to the client and it never gets an opportunity to shutdown.
WaitHandle[] handles =
[
localPacketPumpShutdownEvent,
#if FEATURE_APM
result.AsyncWaitHandle
#else
((IAsyncResult)readTask).AsyncWaitHandle
#endif
];
int waitId = WaitHandle.WaitAny(handles);
switch (waitId)
{
case 0:
// Fulfill the request for shutdown of the message pump.
CommunicationsUtilities.Trace("Shutdown message pump thread.");
continueReading = false;
break;
case 1:
{
// Client recieved a packet header. Read the rest of it.
int headerBytesRead = 0;
#if FEATURE_APM
headerBytesRead = localStream.EndRead(result);
#else
headerBytesRead = readTask.Result;
#endif
if ((headerBytesRead != headerByte.Length) && !localPacketPumpShutdownEvent.WaitOne(0))
{
// Incomplete read. Abort.
if (headerBytesRead == 0)
{
if (_isServerDisconnecting)
{
continueReading = false;
break;
}
ErrorUtilities.ThrowInternalError("Server disconnected abruptly");
}
else
{
ErrorUtilities.ThrowInternalError("Incomplete header read. {0} of {1} bytes read", headerBytesRead, headerByte.Length);
}
}
NodePacketType packetType = (NodePacketType)Enum.ToObject(typeof(NodePacketType), headerByte[0]);
int packetLength = BinaryPrimitives.ReadInt32LittleEndian(new Span<byte>(headerByte, 1, 4));
int packetBytesRead = 0;
_readBufferMemoryStream.Position = 0;
_readBufferMemoryStream.SetLength(packetLength);
byte[] packetData = _readBufferMemoryStream.GetBuffer();
while (packetBytesRead < packetLength)
{
int bytesRead = localStream.Read(packetData, packetBytesRead, packetLength - packetBytesRead);
if (bytesRead == 0)
{
// Incomplete read. Abort.
ErrorUtilities.ThrowInternalError("Incomplete packet read. {0} of {1} bytes read", packetBytesRead, packetLength);
}
packetBytesRead += bytesRead;
}
try
{
_packetFactory.DeserializeAndRoutePacket(0, packetType, _binaryReadTranslator);
}
catch
{
// Error while deserializing or handling packet. Logging additional info.
CommunicationsUtilities.Trace("Packet factory failed to receive package. Exception while deserializing packet {0}.", packetType);
throw;
}
if (packetType == NodePacketType.ServerNodeBuildResult)
{
continueReading = false;
}
else
{
// Start reading the next package header.
#if FEATURE_APM
result = localStream.BeginRead(headerByte, 0, headerByte.Length, null, null);
#else
readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length).AsTask();
#endif
}
}
break;
default:
ErrorUtilities.ThrowInternalError("WaitId {0} out of range.", waitId);
break;
}
}
while (continueReading);
}
catch (Exception ex)
{
CommunicationsUtilities.Trace("Exception occurred in the packet pump: {0}", ex);
PacketPumpException = ex;
}
CommunicationsUtilities.Trace("Ending read loop.");
PacketPumpCompleted.Set();
}
#endregion
public void Dispose() => Stop();
/// <summary>
/// Signalize that from now on we expect server will break connected named pipe.
/// </summary>
public void ServerWillDisconnect()
{
_isServerDisconnecting = true;
}
}
}