/// <summary> /// Marks the request as being blocked by new requests whose results we must get before we can proceed. /// </summary> private void HandleRequestBlockedByNewRequests(SchedulableRequest parentRequest, BuildRequestBlocker blocker, List<ScheduleResponse> responses) { ErrorUtilities.VerifyThrowArgumentNull(blocker, "blocker"); ErrorUtilities.VerifyThrowArgumentNull(responses, "responses"); // The request is waiting on new requests. bool abortRequestBatch = false; Stack<BuildRequest> requestsToAdd = new Stack<BuildRequest>(blocker.BuildRequests.Length); foreach (BuildRequest request in blocker.BuildRequests) { // Assign a global request id to this request. if (request.GlobalRequestId == BuildRequest.InvalidGlobalRequestId) { AssignGlobalRequestId(request); } int nodeForResults = (parentRequest == null) ? InvalidNodeId : parentRequest.AssignedNode; TraceScheduler("Received request {0} (node request {1}) with parent {2} from node {3}", request.GlobalRequestId, request.NodeRequestId, request.ParentGlobalRequestId, nodeForResults); // First, determine if we have already built this request and have results for it. If we do, we prepare the responses for it // directly here. We COULD simply report these as blocking the parent request and let the scheduler pick them up later when the parent // comes back up as schedulable, but we prefer to send the results back immediately so this request can (potentially) continue uninterrupted. ScheduleResponse response = TrySatisfyRequestFromCache(nodeForResults, request, skippedResultsAreOK: false); if (null != response) { TraceScheduler("Request {0} (node request {1}) satisfied from the cache.", request.GlobalRequestId, request.NodeRequestId); // BuildResult result = (response.Action == ScheduleActionType.Unblock) ? response.Unblocker.Results[0] : response.BuildResult; LogRequestHandledFromCache(request, response.BuildResult); responses.Add(response); // If we wish to implement an algorithm where the first failing request aborts the remaining request, check for // overall result being failure rather than just circular dependency. Sync with BasicScheduler.ReportResult and // BuildRequestEntry.ReportResult. if (response.BuildResult.CircularDependency) { abortRequestBatch = true; } } else { // Ensure there is no affinity mismatch between this request and a previous request of the same configuration. NodeAffinity requestAffinity = GetNodeAffinityForRequest(request); NodeAffinity existingRequestAffinity = NodeAffinity.Any; if (requestAffinity != NodeAffinity.Any) { bool affinityMismatch = false; int assignedNodeId = _schedulingData.GetAssignedNodeForRequestConfiguration(request.ConfigurationId); if (assignedNodeId != Scheduler.InvalidNodeId) { if (!_availableNodes[assignedNodeId].CanServiceRequestWithAffinity(GetNodeAffinityForRequest(request))) { // This request's configuration has already been assigned to a node which cannot service this affinity. if (_schedulingData.GetRequestsAssignedToConfigurationCount(request.ConfigurationId) == 0) { // If there are no other requests already scheduled for that configuration, we can safely reassign. _schedulingData.UnassignNodeForRequestConfiguration(request.ConfigurationId); } else { existingRequestAffinity = (_availableNodes[assignedNodeId].ProviderType == NodeProviderType.InProc) ? NodeAffinity.InProc : NodeAffinity.OutOfProc; affinityMismatch = true; } } } else if (_schedulingData.GetRequestsAssignedToConfigurationCount(request.ConfigurationId) > 0) { // Would any other existing requests for this configuration mismatch? foreach (SchedulableRequest existingRequest in _schedulingData.GetRequestsAssignedToConfiguration(request.ConfigurationId)) { existingRequestAffinity = GetNodeAffinityForRequest(existingRequest.BuildRequest); if (existingRequestAffinity != NodeAffinity.Any && existingRequestAffinity != requestAffinity) { // The existing request has an affinity which doesn't match this one, so this one could never be scheduled. affinityMismatch = true; break; } } } if (affinityMismatch) { BuildResult result = new BuildResult(request, new InvalidOperationException(ResourceUtilities.FormatResourceString("AffinityConflict", requestAffinity, existingRequestAffinity))); response = GetResponseForResult(nodeForResults, request, result); responses.Add(response); continue; } } // Now add the requests so they would naturally be picked up by the scheduler in the order they were issued, // but before any other requests in the list. This makes us prefer a depth-first traversal. requestsToAdd.Push(request); } } // Now add any unassigned build requests. if (!abortRequestBatch) { if (requestsToAdd.Count == 0) { // All of the results are being reported directly from the cache (from above), so this request can continue on its merry way. if (parentRequest != null) { // responses.Add(new ScheduleResponse(parentRequest.AssignedNode, new BuildRequestUnblocker(parentRequest.BuildRequest.GlobalRequestId))); responses.Add(ScheduleResponse.CreateResumeExecutionResponse(parentRequest.AssignedNode, parentRequest.BuildRequest.GlobalRequestId)); } } else { while (requestsToAdd.Count > 0) { BuildRequest requestToAdd = requestsToAdd.Pop(); SchedulableRequest blockingRequest = _schedulingData.CreateRequest(requestToAdd, parentRequest); if (parentRequest != null) { parentRequest.BlockByRequest(blockingRequest, blocker.TargetsInProgress); } } } } }
/// <summary> /// Marks the request as being blocked by another request which is currently building a target whose results we need to proceed. /// </summary> private void HandleRequestBlockedOnInProgressTarget(SchedulableRequest blockedRequest, BuildRequestBlocker blocker) { ErrorUtilities.VerifyThrowArgumentNull(blockedRequest, "blockedRequest"); ErrorUtilities.VerifyThrowArgumentNull(blocker, "blocker"); // We are blocked on an in-progress request building a target whose results we need. SchedulableRequest blockingRequest = _schedulingData.GetScheduledRequest(blocker.BlockingRequestId); // The request we blocked on couldn't have been executing (because we are) so it must either be yielding (which is ok because // it isn't modifying its own state, just running a background process), ready, or still blocked. blockingRequest.VerifyOneOfStates(new SchedulableRequestState[] { SchedulableRequestState.Yielding, SchedulableRequestState.Ready, SchedulableRequestState.Blocked }); blockedRequest.BlockByRequest(blockingRequest, blocker.TargetsInProgress); }