forked from dotnet/msbuild
/
SchedulableRequest.cs
710 lines (616 loc) · 30.7 KB
/
SchedulableRequest.cs
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
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using Microsoft.Build.Shared;
using Microsoft.Build.Execution;
namespace Microsoft.Build.BackEnd
{
/// <summary>
/// The state enumeration for SchedulableRequests.
/// </summary>
internal enum SchedulableRequestState
{
/// <summary>
/// This request has been submitted but has never been scheduled so it has executed no tasks and does not currently have an
/// entry residing on any node. There may be multiple requests with the same global request id in this state.
/// </summary>
Unscheduled,
/// <summary>
/// This request may continue executing. It already has an entry on a node. There may only ever be one request with a given
/// global request id in this state.
/// </summary>
Ready,
/// <summary>
/// This request is currently executing tasks on its node. In this case it will be the only task executing on the node -
/// all other tasks are either Ready or Blocked. There may only ever be one request with a given global request id in this state.
/// </summary>
Executing,
/// <summary>
/// This request is currently blocked on one or more requests which must complete before it may continue. There may only ever be one
/// request with a given global request id in this state.
/// </summary>
Blocked,
/// <summary>
/// This request has yielded control of the node while it is running a long-running out-of-process program. Any number of tasks on a
/// node may be in the yielding state.
/// </summary>
Yielding,
/// <summary>
/// This request has completed and removed from the system.
/// </summary>
Completed
}
/// <summary>
/// A representation of a BuildRequest and associated data used by the Scheduler to track work being done by the build system.
/// SchedulableRequests implicitly form a directed acyclic graph showing the blocking/blocked relationship between the requests
/// known to the system at any given time. These associations are updated by the BlockByRequest, UnblockWithResult and ResumeExecution
/// methods. These methods, along with Complete, cause state changes which the SchedulingData object will record. That data can be
/// queried to determine the state of any request or node in the system.
/// </summary>
internal class SchedulableRequest
{
/// <summary>
/// The request collection to which this belongs.
/// </summary>
private SchedulingData _schedulingData;
/// <summary>
/// The current state.
/// </summary>
private SchedulableRequestState _state;
/// <summary>
/// The node to which this request is assigned.
/// </summary>
private int _assignedNodeId;
/// <summary>
/// The BuildRequest this class represents.
/// </summary>
private BuildRequest _request;
/// <summary>
/// The schedulable request which issued this request.
/// </summary>
private SchedulableRequest _parent;
/// <summary>
/// The list of targets which were actively building at the time we were blocked.
/// </summary>
private string[] _activeTargetsWhenBlocked;
/// <summary>
/// The requests which must complete before we can continue executing. Indexed by global request id and node request id.
/// Each global request id may have multiple requests which map to it, but they will have separate node request ids.
/// </summary>
private Dictionary<BlockingRequestKey, SchedulableRequest> _requestsWeAreBlockedBy;
/// <summary>
/// The requests which cannot continue until we have finished executing.
/// </summary>
private HashSet<SchedulableRequest> _requestsWeAreBlocking;
/// <summary>
/// The time this request was created.
/// </summary>
private DateTime _creationTime;
/// <summary>
/// The time this request started building.
/// </summary>
private DateTime _startTime;
/// <summary>
/// The time this request was completed.
/// </summary>
private DateTime _endTime;
/// <summary>
/// Records of the amount of time spent in each of the states.
/// </summary>
private Dictionary<SchedulableRequestState, ScheduleTimeRecord> _timeRecords;
/// <summary>
/// Constructor.
/// </summary>
public SchedulableRequest(SchedulingData collection, BuildRequest request, SchedulableRequest parent)
{
ErrorUtilities.VerifyThrowArgumentNull(collection, nameof(collection));
ErrorUtilities.VerifyThrowArgumentNull(request, nameof(request));
ErrorUtilities.VerifyThrow((parent == null) || (parent._schedulingData == collection), "Parent request does not belong to the same collection.");
_schedulingData = collection;
_request = request;
_parent = parent;
_assignedNodeId = -1;
_requestsWeAreBlockedBy = new Dictionary<BlockingRequestKey, SchedulableRequest>();
_requestsWeAreBlocking = new HashSet<SchedulableRequest>();
_timeRecords = new Dictionary<SchedulableRequestState, ScheduleTimeRecord>(5);
_timeRecords[SchedulableRequestState.Unscheduled] = new ScheduleTimeRecord();
_timeRecords[SchedulableRequestState.Blocked] = new ScheduleTimeRecord();
_timeRecords[SchedulableRequestState.Yielding] = new ScheduleTimeRecord();
_timeRecords[SchedulableRequestState.Executing] = new ScheduleTimeRecord();
_timeRecords[SchedulableRequestState.Ready] = new ScheduleTimeRecord();
_timeRecords[SchedulableRequestState.Completed] = new ScheduleTimeRecord();
ChangeToState(SchedulableRequestState.Unscheduled);
}
/// <summary>
/// The current state of the request.
/// </summary>
public SchedulableRequestState State
{
get { return _state; }
}
/// <summary>
/// The underlying BuildRequest.
/// </summary>
public BuildRequest BuildRequest
{
get { return _request; }
}
/// <summary>
/// The request which issued this request.
/// </summary>
public SchedulableRequest Parent
{
get { return _parent; }
}
/// <summary>
/// Returns the node to which this request is assigned.
/// </summary>
public int AssignedNode
{
get { return _assignedNodeId; }
}
/// <summary>
/// The set of active targets.
/// </summary>
public IEnumerable<string> ActiveTargets
{
get
{
VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Yielding, SchedulableRequestState.Blocked, SchedulableRequestState.Executing });
return _activeTargetsWhenBlocked;
}
}
/// <summary>
/// The target we are blocked on
/// </summary>
public string BlockingTarget { get; private set; }
/// <summary>
/// Gets a count of the requests we are blocked by.
/// </summary>
public int RequestsWeAreBlockedByCount
{
get
{
return _requestsWeAreBlockedBy.Count;
}
}
/// <summary>
/// Gets the set of requests for which we require results before we may proceed.
/// </summary>
public IEnumerable<SchedulableRequest> RequestsWeAreBlockedBy
{
get
{
return _requestsWeAreBlockedBy.Values;
}
}
/// <summary>
/// Gets a count of the requests we are blocking.
/// </summary>
public int RequestsWeAreBlockingCount
{
get
{
return _requestsWeAreBlocking.Count;
}
}
/// <summary>
/// Gets the set of requests which cannot proceed because they are waiting for results from us.
/// </summary>
public IEnumerable<SchedulableRequest> RequestsWeAreBlocking
{
get
{
return _requestsWeAreBlocking;
}
}
/// <summary>
/// The time this request was created.
/// </summary>
public DateTime CreationTime
{
get
{
return _creationTime;
}
set
{
ErrorUtilities.VerifyThrow(_creationTime == DateTime.MinValue, "Cannot set CreationTime twice.");
_creationTime = value;
}
}
/// <summary>
/// The time this request started building.
/// </summary>
public DateTime StartTime
{
get
{
return _startTime;
}
set
{
ErrorUtilities.VerifyThrow(_startTime == DateTime.MinValue, "Cannot set StartTime twice.");
_startTime = value;
}
}
/// <summary>
/// The time this request was completed.
/// </summary>
public DateTime EndTime
{
get
{
return _endTime;
}
set
{
ErrorUtilities.VerifyThrow(_endTime == DateTime.MinValue, "Cannot set EndTime twice.");
_endTime = value;
}
}
/// <summary>
/// Gets the amount of time we spent in the specified state.
/// </summary>
public TimeSpan GetTimeSpentInState(SchedulableRequestState desiredState)
{
return _timeRecords[desiredState].AccumulatedTime;
}
/// <summary>
/// Inticates the request is yielding the node.
/// </summary>
public void Yield(string[] activeTargets)
{
VerifyState(SchedulableRequestState.Executing);
ErrorUtilities.VerifyThrowArgumentNull(activeTargets, nameof(activeTargets));
_activeTargetsWhenBlocked = activeTargets;
ChangeToState(SchedulableRequestState.Yielding);
}
/// <summary>
/// Indicates the request is ready to reacquire the node.
/// </summary>
public void Reacquire()
{
VerifyState(SchedulableRequestState.Yielding);
_activeTargetsWhenBlocked = null;
ChangeToState(SchedulableRequestState.Ready);
}
/// <summary>
/// Marks this request as being blocked by the specified request. Establishes the correct relationships between the requests.
/// </summary>
/// <param name="blockingRequest">The request which is blocking this one.</param>
/// <param name="activeTargets">The list of targets this request was currently building at the time it became blocked.</param>
/// <param name="blockingTarget">Target that we are blocked on which is being built by <paramref name="blockingRequest"/></param>
public void BlockByRequest(SchedulableRequest blockingRequest, string[] activeTargets, string blockingTarget = null)
{
VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Blocked, SchedulableRequestState.Executing });
ErrorUtilities.VerifyThrowArgumentNull(blockingRequest, nameof(blockingRequest));
ErrorUtilities.VerifyThrowArgumentNull(activeTargets, nameof(activeTargets));
ErrorUtilities.VerifyThrow(BlockingTarget == null, "Cannot block again if we're already blocked on a target");
// Note that the blocking request will typically be our parent UNLESS it is a request we blocked on because it was executing a target we wanted to execute.
// Thus, we do not assert the parent-child relationship here.
BlockingRequestKey key = new BlockingRequestKey(blockingRequest.BuildRequest);
ErrorUtilities.VerifyThrow(!_requestsWeAreBlockedBy.ContainsKey(key), "We are already blocked by this request.");
ErrorUtilities.VerifyThrow(!blockingRequest._requestsWeAreBlocking.Contains(this), "The blocking request thinks it is already blocking us.");
// This method is only called when a request reports that it is blocked on other requests. If the request is being blocked by a brand new
// request, that request will be unscheduled. If this request is blocked by an in-progress request which was executing a target it needed
// to also execute, then that request is not unscheduled (because it was running on the node) and it is not executing (because this condition
// can only occur against requests which are executing on the same node and since the request which called this method is the one currently
// executing on that node, that means the request it is blocked by must either be itself blocked or ready.)
blockingRequest.VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Yielding, SchedulableRequestState.Blocked, SchedulableRequestState.Ready, SchedulableRequestState.Unscheduled });
// Update our list of active targets. This has to be done before we detect circular dependencies because we use this information to detect
// re-entrancy circular dependencies.
_activeTargetsWhenBlocked = activeTargets;
BlockingTarget = blockingTarget;
DetectCircularDependency(blockingRequest);
_requestsWeAreBlockedBy[key] = blockingRequest;
blockingRequest._requestsWeAreBlocking.Add(this);
ChangeToState(SchedulableRequestState.Blocked);
}
/// <summary>
/// Indicates that there are partial results (project producing the result is still running) which can be used to unblock this request. Updates the relationships between requests.
/// </summary>
public void UnblockWithPartialResultForBlockingTarget(BuildResult result)
{
VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Blocked, SchedulableRequestState.Unscheduled });
ErrorUtilities.VerifyThrowArgumentNull(result, nameof(result));
BlockingRequestKey key = new BlockingRequestKey(result);
DisconnectRequestWeAreBlockedBy(key);
BlockingTarget = null;
}
/// <summary>
/// Indicates that there are results which can be used to unblock this request. Updates the relationships between requests.
/// </summary>
public void UnblockWithResult(BuildResult result)
{
VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Blocked, SchedulableRequestState.Unscheduled });
ErrorUtilities.VerifyThrowArgumentNull(result, nameof(result));
BlockingRequestKey key = new BlockingRequestKey(result);
DisconnectRequestWeAreBlockedBy(key);
_activeTargetsWhenBlocked = null;
BlockingTarget = null;
}
/// <summary>
/// Resumes execution of the request on the specified node.
/// </summary>
public void ResumeExecution(int nodeId)
{
ErrorUtilities.VerifyThrow(_assignedNodeId == Scheduler.InvalidNodeId || _assignedNodeId == nodeId, "Request must always resume on the same node on which it was started.");
VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Ready, SchedulableRequestState.Unscheduled });
ErrorUtilities.VerifyThrow((_state == SchedulableRequestState.Ready) || !_schedulingData.IsRequestScheduled(this), "Another instance of request {0} is already scheduled.", _request.GlobalRequestId);
ErrorUtilities.VerifyThrow(!_schedulingData.IsNodeWorking(nodeId), "Cannot resume execution of request {0} because node {1} is already working.", _request.GlobalRequestId, nodeId);
int requiredNodeId = _schedulingData.GetAssignedNodeForRequestConfiguration(_request.ConfigurationId);
ErrorUtilities.VerifyThrow(requiredNodeId == Scheduler.InvalidNodeId || requiredNodeId == nodeId, "Request {0} cannot be assigned to node {1} because its configuration is already assigned to node {2}", _request.GlobalRequestId, nodeId, requiredNodeId);
_assignedNodeId = nodeId;
ChangeToState(SchedulableRequestState.Executing);
}
/// <summary>
/// Completes this request.
/// </summary>
public void Complete(BuildResult result)
{
VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Ready, SchedulableRequestState.Executing, SchedulableRequestState.Unscheduled });
ErrorUtilities.VerifyThrow(_state != SchedulableRequestState.Ready || result.CircularDependency, "Request can only be Completed from the Ready state if the result indicates a circular dependency occurred.");
ErrorUtilities.VerifyThrow(_requestsWeAreBlockedBy.Count == 0, "We can't be complete if we are still blocked on requests.");
// Any requests we were blocking we will no longer be blocking.
List<SchedulableRequest> requestsToUnblock = new List<SchedulableRequest>(_requestsWeAreBlocking);
foreach (SchedulableRequest requestWeAreBlocking in requestsToUnblock)
{
requestWeAreBlocking.UnblockWithResult(result);
}
ChangeToState(SchedulableRequestState.Completed);
}
/// <summary>
/// Removes an unscheduled request.
/// </summary>
public void Delete()
{
VerifyState(SchedulableRequestState.Unscheduled);
ErrorUtilities.VerifyThrow(_requestsWeAreBlockedBy.Count == 0, "We are blocked by requests.");
ErrorUtilities.VerifyThrow(_requestsWeAreBlocking.Count == 0, "We are blocking by requests.");
ChangeToState(SchedulableRequestState.Completed);
}
/// <summary>
/// Verifies that the current state is as expected.
/// </summary>
public void VerifyState(SchedulableRequestState requiredState)
{
ErrorUtilities.VerifyThrow(_state == requiredState, "Request {0} expected to be in state {1} but state is actually {2}", _request.GlobalRequestId, requiredState, _state);
}
/// <summary>
/// Verifies that the current state is as expected.
/// </summary>
public void VerifyOneOfStates(SchedulableRequestState[] requiredStates)
{
foreach (SchedulableRequestState requiredState in requiredStates)
{
if (_state == requiredState)
{
return;
}
}
ErrorUtilities.ThrowInternalError("State {0} is not one of the expected states.", _state);
}
/// <summary>
/// Change to the specified state. Update internal counters.
/// </summary>
private void ChangeToState(SchedulableRequestState newState)
{
DateTime currentTime = DateTime.UtcNow;
_timeRecords[_state].EndState(currentTime);
_timeRecords[newState].StartState(currentTime);
if (_state != newState)
{
SchedulableRequestState previousState = _state;
_state = newState;
_schedulingData.UpdateFromState(this, previousState);
}
}
/// <summary>
/// Detects a circular dependency. Throws a CircularDependencyException if one exists. Circular dependencies can occur
/// under the following conditions:
/// 1. If the blocking request's global request ID appears in the ancestor chain (Direct).
/// 2. If a request appears in the ancestor chain and has a different global request ID but has an active target that
/// matches one of the targets specified in the blocking request (Direct).
/// 3. If the blocking request exists elsewhere as a blocked request with the same global request ID, and one of its children
/// (recursively) matches this request's global request ID (Indirect).
/// 4. If the blocking request's configuration is part of another request elsewhere which is also blocked, and that request
/// is building targets this blocking request is building, and one of that blocked request's children (recursively)
/// matches this request's global request ID (Indirect).
/// </summary>
private void DetectCircularDependency(SchedulableRequest blockingRequest)
{
DetectDirectCircularDependency(blockingRequest);
DetectIndirectCircularDependency(blockingRequest);
}
/// <summary>
/// Detects a circular dependency where the request which is about to block us is already blocked by us, usually as a result
/// of it having been previously scheduled in a multiproc scenario, but before this request was able to execute.
/// </summary>
/// <remarks>
/// Let A be 'this' project and B be 'blockingRequest' (the request which is going to block A.)
/// An indirect circular dependency exists if there is a dependency path from B to A. If there is no
/// existing blocked request B' with the same global request id as B, then there can be no path from B to A because B is a brand new
/// request with no other dependencies. If there is an existing blocked request B' with the same global request ID as B, then we
/// walk the set of dependencies recursively searching for A. If A is found, we have a circular dependency.
/// </remarks>
private void DetectIndirectCircularDependency(SchedulableRequest blockingRequest)
{
// If there is already a blocked request which has the same configuration id as the blocking request and that blocked request is (recursively)
// waiting on this request, then that is an indirect circular dependency.
SchedulableRequest alternateRequest = _schedulingData.GetBlockedRequestIfAny(blockingRequest.BuildRequest.GlobalRequestId);
if (alternateRequest == null)
{
return;
}
Stack<SchedulableRequest> requestsToEvaluate = new Stack<SchedulableRequest>(16);
HashSet<SchedulableRequest> evaluatedRequests = new HashSet<SchedulableRequest>();
requestsToEvaluate.Push(alternateRequest);
while (requestsToEvaluate.Count > 0)
{
SchedulableRequest requestToEvaluate = requestsToEvaluate.Pop();
// If we make it to a child which is us, then it's a circular dependency.
if (requestToEvaluate.BuildRequest.GlobalRequestId == this.BuildRequest.GlobalRequestId)
{
ThrowIndirectCircularDependency(blockingRequest, requestToEvaluate);
}
evaluatedRequests.Add(requestToEvaluate);
// If the request is not scheduled, it's possible that is because it's been scheduled elsewhere and is blocked.
// Follow that path if it exists.
if (requestToEvaluate.State == SchedulableRequestState.Unscheduled)
{
requestToEvaluate = _schedulingData.GetBlockedRequestIfAny(requestToEvaluate.BuildRequest.GlobalRequestId);
// If there was no scheduled request to evaluate, move on.
if (requestToEvaluate == null || evaluatedRequests.Contains(requestToEvaluate))
{
continue;
}
}
// This request didn't cause a circular dependency, check its children.
foreach (SchedulableRequest childRequest in requestToEvaluate.RequestsWeAreBlockedBy)
{
if (!evaluatedRequests.Contains(childRequest))
{
requestsToEvaluate.Push(childRequest);
}
}
}
}
/// <summary>
/// Build our ancestor list then throw the circular dependency error.
/// </summary>
private void ThrowIndirectCircularDependency(SchedulableRequest blockingRequest, SchedulableRequest requestToEvaluate)
{
// We found a request which has the same global request ID as us in a chain which leads from the (already blocked) request
// which is trying to block us. Calculate its list of ancestors by walking up the parent list.
List<SchedulableRequest> ancestors = new List<SchedulableRequest>(16);
while (requestToEvaluate.Parent != null)
{
ancestors.Add(requestToEvaluate.Parent);
requestToEvaluate = requestToEvaluate.Parent;
}
ancestors.Reverse(); // Because the list should be in the order from root to child.
CleanupForCircularDependencyAndThrow(blockingRequest, ancestors);
}
/// <summary>
/// Detects a circular dependency where the blocking request is in our direct ancestor chain.
/// </summary>
private void DetectDirectCircularDependency(SchedulableRequest blockingRequest)
{
// A circular dependency occurs when this project (or any of its ancestors) has the same global request id as the
// blocking request.
List<SchedulableRequest> ancestors = new List<SchedulableRequest>(16);
SchedulableRequest currentRequest = this;
do
{
ancestors.Add(currentRequest);
if (currentRequest.BuildRequest.GlobalRequestId == blockingRequest.BuildRequest.GlobalRequestId)
{
// We are directly conflicting with an instance of ourselves.
CleanupForCircularDependencyAndThrow(blockingRequest, ancestors);
}
currentRequest = currentRequest.Parent;
}
while (currentRequest != null);
}
/// <summary>
/// Removes associations with all blocking requests and throws an exception.
/// </summary>
private void CleanupForCircularDependencyAndThrow(SchedulableRequest requestCausingFailure, List<SchedulableRequest> ancestors)
{
if (_requestsWeAreBlockedBy.Count != 0)
{
List<SchedulableRequest> tempRequests = new List<SchedulableRequest>(_requestsWeAreBlockedBy.Values);
foreach (SchedulableRequest requestWeAreBlockedBy in tempRequests)
{
BlockingRequestKey key = new BlockingRequestKey(requestWeAreBlockedBy.BuildRequest);
DisconnectRequestWeAreBlockedBy(key);
}
}
else
{
ChangeToState(SchedulableRequestState.Ready);
}
_activeTargetsWhenBlocked = null;
// The blocking request itself is no longer valid if it was unscheduled.
if (requestCausingFailure.State == SchedulableRequestState.Unscheduled)
{
requestCausingFailure.Delete();
}
throw new SchedulerCircularDependencyException(requestCausingFailure.BuildRequest, ancestors);
}
/// <summary>
/// Removes the association between this request and the one we are blocked by.
/// </summary>
internal void DisconnectRequestWeAreBlockedBy(BlockingRequestKey blockingRequestKey)
{
ErrorUtilities.VerifyThrow(_requestsWeAreBlockedBy.TryGetValue(blockingRequestKey, out SchedulableRequest unblockingRequest), "We are not blocked by the specified request.");
ErrorUtilities.VerifyThrow(unblockingRequest._requestsWeAreBlocking.Contains(this), "The request unblocking us doesn't think it is blocking us.");
_requestsWeAreBlockedBy.Remove(blockingRequestKey);
unblockingRequest._requestsWeAreBlocking.Remove(this);
// If the request we are blocked by also happens to be unscheduled, remove it as well so we don't try to run it later. This is
// because circular dependency errors cause us to fail all outstanding requests on the current request. See BuildRequsetEntry.ReportResult.
if (unblockingRequest.State == SchedulableRequestState.Unscheduled)
{
unblockingRequest.Delete();
}
if (_requestsWeAreBlockedBy.Count == 0)
{
ChangeToState(SchedulableRequestState.Ready);
}
}
/// <summary>
/// A key for blocking requests combining the global request and node request ids.
/// </summary>
internal class BlockingRequestKey
{
/// <summary>
/// The global request id.
/// </summary>
private int _globalRequestId;
/// <summary>
/// The request id known to the node.
/// </summary>
private int _nodeRequestId;
/// <summary>
/// Constructor over a request.
/// </summary>
public BlockingRequestKey(BuildRequest request)
{
_globalRequestId = request.GlobalRequestId;
_nodeRequestId = request.NodeRequestId;
}
/// <summary>
/// Constructor over a result.
/// </summary>
public BlockingRequestKey(BuildResult result)
{
_globalRequestId = result.GlobalRequestId;
_nodeRequestId = result.NodeRequestId;
}
/// <summary>
/// Equals override.
/// </summary>
public override bool Equals(object obj)
{
if (obj != null)
{
BlockingRequestKey other = obj as BlockingRequestKey;
if (other != null)
{
return (other._globalRequestId == _globalRequestId) && (other._nodeRequestId == _nodeRequestId);
}
}
return base.Equals(obj);
}
/// <summary>
/// GetHashCode override.
/// </summary>
public override int GetHashCode()
{
return _globalRequestId ^ _nodeRequestId;
}
}
}
}