forked from dotnet/msbuild
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNodeProviderInProc.cs
More file actions
472 lines (399 loc) · 16.5 KB
/
Copy pathNodeProviderInProc.cs
File metadata and controls
472 lines (399 loc) · 16.5 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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
// 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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
#if FEATURE_THREAD_CULTURE
using BuildParameters = Microsoft.Build.Execution.BuildParameters;
#endif
using NodeEngineShutdownReason = Microsoft.Build.Execution.NodeEngineShutdownReason;
#nullable disable
namespace Microsoft.Build.BackEnd
{
/// <summary>
/// An implementation of a node provider for in-proc nodes.
/// </summary>
internal class NodeProviderInProc : INodeProvider, INodePacketFactory, IDisposable
{
#region Private Data
/// <summary>
/// The invalid in-proc node id
/// </summary>
private const int InvalidInProcNodeId = 0;
/// <summary>
/// Flag indicating we have disposed.
/// </summary>
private bool _disposed = false;
/// <summary>
/// Value used to ensure multiple in-proc nodes which save the operating environment are not created.
/// </summary>
private static Semaphore InProcNodeOwningOperatingEnvironment;
/// <summary>
/// The component host.
/// </summary>
private IBuildComponentHost _componentHost;
/// <summary>
/// The in-proc node.
/// </summary>
private INode _inProcNode;
/// <summary>
/// The in-proc node endpoint.
/// </summary>
private INodeEndpoint _inProcNodeEndpoint;
/// <summary>
/// The packet factory used to route packets from the node.
/// </summary>
private INodePacketFactory _packetFactory;
/// <summary>
/// The in-proc node thread.
/// </summary>
private Thread _inProcNodeThread;
/// <summary>
/// Event which is raised when the in-proc endpoint is connected.
/// </summary>
private AutoResetEvent _endpointConnectedEvent;
/// <summary>
/// The ID of the in-proc node.
/// </summary>
private int _inProcNodeId = InvalidInProcNodeId;
/// <summary>
/// Check to allow the inproc node to have exclusive ownership of the operating environment
/// </summary>
private bool _exclusiveOperatingEnvironment = false;
#endregion
#region Constructor
/// <summary>
/// Initializes the node provider.
/// </summary>
public NodeProviderInProc()
{
_endpointConnectedEvent = new AutoResetEvent(false);
}
#endregion
/// <summary>
/// Finalizes an instance of the <see cref="NodeProviderInProc"/> class.
/// </summary>
~NodeProviderInProc()
{
Dispose(false /* disposing */);
}
/// <summary>
/// Returns the type of nodes managed by this provider.
/// </summary>
public NodeProviderType ProviderType
{
get { return NodeProviderType.InProc; }
}
/// <summary>
/// Returns the number of nodes available to create on this provider.
/// </summary>
public int AvailableNodes
{
get
{
if (_inProcNodeId != InvalidInProcNodeId)
{
return 0;
}
return 1;
}
}
#region IBuildComponent Members
/// <summary>
/// Sets the build component host.
/// </summary>
/// <param name="host">The component host.</param>
public void InitializeComponent(IBuildComponentHost host)
{
_componentHost = host;
}
/// <summary>
/// Shuts down this component.
/// </summary>
public void ShutdownComponent()
{
_componentHost = null;
_inProcNode = null;
}
#endregion
#region INodeProvider Members
/// <summary>
/// Sends data to the specified node.
/// </summary>
/// <param name="nodeId">The node to which data should be sent.</param>
/// <param name="packet">The data to send.</param>
public void SendData(int nodeId, INodePacket packet)
{
ErrorUtilities.VerifyThrowArgumentOutOfRange(nodeId == _inProcNodeId, "node");
ErrorUtilities.VerifyThrowArgumentNull(packet);
if (_inProcNode == null)
{
return;
}
_inProcNodeEndpoint.SendData(packet);
}
/// <summary>
/// Causes all connected nodes to be shut down.
/// </summary>
/// <param name="enableReuse">Flag indicating if the nodes should prepare for reuse.</param>
public void ShutdownConnectedNodes(bool enableReuse)
{
if (_inProcNode != null)
{
_inProcNodeEndpoint.SendData(new NodeBuildComplete(enableReuse));
}
}
/// <summary>
/// Causes all nodes to be shut down permanently - for InProc nodes it is the same as ShutdownConnectedNodes
/// with enableReuse = false
/// </summary>
public void ShutdownAllNodes()
{
ShutdownConnectedNodes(false /* no node reuse */);
}
public IList<NodeInfo> CreateNodes(int nextNodeId, INodePacketFactory factory, Func<NodeInfo, NodeConfiguration> configurationFactory, int numberOfNodesToCreate)
{
var nodes = new List<NodeInfo>(numberOfNodesToCreate);
for (int i = 0; i < numberOfNodesToCreate; i++)
{
int nodeId = nextNodeId + i;
NodeInfo nodeInfo = new(nodeId, ProviderType);
if (!CreateNode(nodeId, factory, configurationFactory(nodeInfo)))
{
// If it fails let it return what we have created so far so caller can somehow acquire missing nodes.
break;
}
nodes.Add(nodeInfo);
}
return nodes;
}
/// <summary>
/// Requests that a node be created on the specified machine.
/// </summary>
/// <param name="nodeId">The id of the node to create.</param>
/// <param name="factory">The factory to use to create packets from this node.</param>
/// <param name="configuration">The configuration for the node.</param>
private bool CreateNode(int nodeId, INodePacketFactory factory, NodeConfiguration configuration)
{
ErrorUtilities.VerifyThrow(nodeId != InvalidInProcNodeId, "Cannot create in-proc node.");
// Attempt to get the operating environment semaphore if requested.
if (_componentHost.BuildParameters.SaveOperatingEnvironment)
{
// We can only create additional in-proc nodes if we have decided not to save the operating environment. This is the global
// DTAR case in Visual Studio, but other clients might enable this as well under certain special circumstances.
if (Environment.GetEnvironmentVariable("MSBUILDINPROCENVCHECK") == "1")
{
_exclusiveOperatingEnvironment = true;
}
if (_exclusiveOperatingEnvironment)
{
if (InProcNodeOwningOperatingEnvironment == null)
{
InProcNodeOwningOperatingEnvironment = new Semaphore(1, 1);
}
if (!InProcNodeOwningOperatingEnvironment.WaitOne(0))
{
// Can't take the operating environment.
return false;
}
}
}
// If it doesn't already exist, create it.
if (_inProcNode == null)
{
if (!InstantiateNode(factory))
{
return false;
}
}
_inProcNodeEndpoint.SendData(configuration);
_inProcNodeId = nodeId;
return true;
}
#endregion
#region INodePacketFactory Members
/// <summary>
/// Registers a packet handler. Not used in the in-proc node.
/// </summary>
public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler)
{
// Not used
ErrorUtilities.ThrowInternalErrorUnreachable();
}
/// <summary>
/// Unregisters a packet handler. Not used in the in-proc node.
/// </summary>
public void UnregisterPacketHandler(NodePacketType packetType)
{
// Not used
ErrorUtilities.ThrowInternalErrorUnreachable();
}
/// <summary>
/// Deserializes and routes a packet. Not used in the in-proc node.
/// </summary>
public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator)
{
// Not used
ErrorUtilities.ThrowInternalErrorUnreachable();
}
/// <summary>
/// Routes a packet.
/// </summary>
/// <param name="nodeId">The id of the node from which the packet is being routed.</param>
/// <param name="packet">The packet to route.</param>
public void RoutePacket(int nodeId, INodePacket packet)
{
INodePacketFactory factory = _packetFactory;
if (_inProcNodeId != InvalidInProcNodeId)
{
// If this was a shutdown packet, we are done with the node. Release all context associated with it. Do this here, rather
// than after we route the packet, because otherwise callbacks to the NodeManager to determine if we have available nodes
// will report that the in-proc node is still in use when it has actually shut down.
int savedInProcNodeId = _inProcNodeId;
if (packet.Type == NodePacketType.NodeShutdown)
{
_inProcNodeId = InvalidInProcNodeId;
// Release the operating environment semaphore if we were holding it.
if ((_componentHost.BuildParameters.SaveOperatingEnvironment) &&
(InProcNodeOwningOperatingEnvironment != null))
{
InProcNodeOwningOperatingEnvironment.Release();
InProcNodeOwningOperatingEnvironment.Dispose();
InProcNodeOwningOperatingEnvironment = null;
}
if (!_componentHost.BuildParameters.EnableNodeReuse)
{
_inProcNode = null;
_inProcNodeEndpoint = null;
_inProcNodeThread = null;
_packetFactory = null;
}
}
// Route the packet back to the NodeManager.
factory.RoutePacket(savedInProcNodeId, packet);
}
}
#endregion
/// <summary>
/// IDisposable implementation
/// </summary>
public void Dispose()
{
Dispose(true /* disposing */);
GC.SuppressFinalize(this);
}
/// <summary>
/// Factory for component creation.
/// </summary>
internal static IBuildComponent CreateComponent(BuildComponentType type)
{
ErrorUtilities.VerifyThrow(type == BuildComponentType.InProcNodeProvider, "Cannot create component of type {0}", type);
return new NodeProviderInProc();
}
#region Private Methods
/// <summary>
/// Creates a new in-proc node.
/// </summary>
private bool InstantiateNode(INodePacketFactory factory)
{
ErrorUtilities.VerifyThrow(_inProcNode == null, "In Proc node already instantiated.");
ErrorUtilities.VerifyThrow(_inProcNodeEndpoint == null, "In Proc node endpoint already instantiated.");
NodeEndpointInProc.EndpointPair endpoints = NodeEndpointInProc.CreateInProcEndpoints(NodeEndpointInProc.EndpointMode.Synchronous, _componentHost);
_inProcNodeEndpoint = endpoints.ManagerEndpoint;
_inProcNodeEndpoint.OnLinkStatusChanged += new LinkStatusChangedDelegate(InProcNodeEndpoint_OnLinkStatusChanged);
_packetFactory = factory;
_inProcNode = new InProcNode(_componentHost, endpoints.NodeEndpoint);
#if FEATURE_THREAD_CULTURE
_inProcNodeThread = new Thread(InProcNodeThreadProc, BuildParameters.ThreadStackSize);
#else
CultureInfo culture = _componentHost.BuildParameters.Culture;
CultureInfo uiCulture = _componentHost.BuildParameters.UICulture;
_inProcNodeThread = new Thread(() =>
{
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = uiCulture;
InProcNodeThreadProc();
});
#endif
_inProcNodeThread.Name = $"In-proc Node ({_componentHost.Name})";
_inProcNodeThread.IsBackground = true;
#if FEATURE_THREAD_CULTURE
_inProcNodeThread.CurrentCulture = _componentHost.BuildParameters.Culture;
_inProcNodeThread.CurrentUICulture = _componentHost.BuildParameters.UICulture;
#endif
_inProcNodeThread.Start();
_inProcNodeEndpoint.Connect(this);
int connectionTimeout = CommunicationsUtilities.NodeConnectionTimeout;
bool connected = _endpointConnectedEvent.WaitOne(connectionTimeout);
ErrorUtilities.VerifyThrow(connected, "In-proc node failed to start up within {0}ms", connectionTimeout);
return true;
}
/// <summary>
/// Thread proc which runs the in-proc node.
/// </summary>
private void InProcNodeThreadProc()
{
Exception e;
NodeEngineShutdownReason reason = _inProcNode.Run(out e);
InProcNodeShutdown(reason, e);
}
/// <summary>
/// Callback invoked when the link status of the endpoint has changed.
/// </summary>
/// <param name="endpoint">The endpoint whose status has changed.</param>
/// <param name="status">The new link status.</param>
private void InProcNodeEndpoint_OnLinkStatusChanged(INodeEndpoint endpoint, LinkStatus status)
{
if (status == LinkStatus.Active)
{
// We don't verify this outside of the 'if' because we don't care about the link going down, which will occur
// after we have cleared the inProcNodeEndpoint due to shutting down the node.
ErrorUtilities.VerifyThrow(endpoint == _inProcNodeEndpoint, "Received link status event for a node other than our peer.");
_endpointConnectedEvent.Set();
}
}
/// <summary>
/// Callback invoked when the endpoint shuts down.
/// </summary>
/// <param name="reason">The reason the endpoint is shutting down.</param>
/// <param name="e">Any exception which was raised that caused the endpoint to shut down.</param>
private void InProcNodeShutdown(NodeEngineShutdownReason reason, Exception e)
{
switch (reason)
{
case NodeEngineShutdownReason.BuildComplete:
case NodeEngineShutdownReason.BuildCompleteReuse:
case NodeEngineShutdownReason.Error:
break;
case NodeEngineShutdownReason.ConnectionFailed:
ErrorUtilities.ThrowInternalError("Unexpected shutdown code {0} received.", reason);
break;
}
}
/// <summary>
/// Dispose implementation.
/// </summary>
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_endpointConnectedEvent != null)
{
_endpointConnectedEvent.Dispose();
_endpointConnectedEvent = null;
}
}
_disposed = true;
}
}
// The process here is the same as in the main node.
public IEnumerable<Process> GetProcesses() => throw new NotImplementedException();
#endregion
}
}