/// <summary> /// Event handler for the BuildEngine's OnNewRequest event. /// </summary> private void OnNewRequest(BuildRequestBlocker blocker) { if (_nodeEndpoint.LinkStatus == LinkStatus.Active) { _nodeEndpoint.SendData(blocker); } }
public void TestSimpleRequestWithCachedResultsFail() { CreateConfiguration(1, "foo.proj"); BuildRequest request = CreateBuildRequest(1, 1, new string[] { "foo" }); BuildResult result = CacheBuildResult(request, "foo", TestUtilities.GetStopWithErrorResult()); BuildRequestBlocker blocker = new BuildRequestBlocker(request.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.Equal(2, response.Count); // First response tells the parent of the results. Assert.Equal(ScheduleActionType.ReportResults, response[0].Action); Assert.True(ResultsCache_Tests.AreResultsIdentical(result, response[0].Unblocker.Result)); // Second response tells the parent to continue. Assert.Equal(ScheduleActionType.ResumeExecution, response[1].Action); Assert.Null(response[1].Unblocker.Result); }
public void TestThreeRequestsWithOneFailure() { CreateConfiguration(1, "foo.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }); BuildRequest request2 = CreateBuildRequest(2, 1, new string[] { "bar" }); BuildResult result2 = CacheBuildResult(request2, "bar", TestUtilities.GetStopWithErrorResult()); BuildRequest request3 = CreateBuildRequest(3, 1, new string[] { "baz" }); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2, request3 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.Equal(2, response.Count); Assert.True(ResultsCache_Tests.AreResultsIdentical(result2, response[0].Unblocker.Result)); Assert.Equal(ScheduleActionType.ScheduleWithConfiguration, response[1].Action); }
public void TestMultipleRequestsWithSomeResults() { CreateConfiguration(1, "foo.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }); CreateConfiguration(2, "bar.proj"); BuildRequest request2 = CreateBuildRequest(2, 2, new string[] { "bar" }); BuildResult result2 = CacheBuildResult(request2, "bar", TestUtilities.GetSuccessResult()); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.AreEqual(2, response.Count); Assert.AreEqual(ScheduleActionType.ReportResults, response[0].Action); Assert.IsTrue(ResultsCache_Tests.AreResultsIdentical(result2, response[0].Unblocker.Result)); Assert.AreEqual(ScheduleActionType.ScheduleWithConfiguration, response[1].Action); Assert.AreEqual(request1, response[1].BuildRequest); }
public void TestNoNewNodesCreatedForMultipleRequestsWithSameConfiguration() { _host.BuildParameters.MaxNodeCount = 3; CreateConfiguration(1, "foo.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }); BuildRequest request2 = CreateBuildRequest(2, 1, new string[] { "bar" }); BuildRequest request3 = CreateBuildRequest(3, 1, new string[] { "baz" }); BuildRequest request4 = CreateBuildRequest(4, 1, new string[] { "qux" }); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2, request3, request4 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.Equal(1, response.Count); Assert.Equal(ScheduleActionType.ScheduleWithConfiguration, response[0].Action); Assert.Equal(request1, response[0].BuildRequest); }
public void TestMaxNodeCountNodesNotExceededWithSomeOOPRequests2() { _host.BuildParameters.MaxNodeCount = 3; CreateConfiguration(1, "foo.proj"); CreateConfiguration(2, "bar.proj"); CreateConfiguration(3, "baz.proj"); CreateConfiguration(4, "quz.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequest request2 = CreateBuildRequest(2, 2, new string[] { "bar" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequest request3 = CreateBuildRequest(3, 3, new string[] { "baz" }); BuildRequest request4 = CreateBuildRequest(4, 4, new string[] { "qux" }); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2, request3, request4 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.Equal(2, response.Count); Assert.Equal(ScheduleActionType.ScheduleWithConfiguration, response[0].Action); Assert.Equal(request3, response[0].BuildRequest); Assert.Equal(ScheduleActionType.CreateNode, response[1].Action); Assert.Equal(NodeAffinity.OutOfProc, response[1].RequiredNodeType); Assert.Equal(2, response[1].NumberOfNodesToCreate); }
/// <summary> /// Raises the OnNewRequest event. /// </summary> /// <param name="blocker">Information about what is blocking the current request.</param> private void RaiseRequestBlocked(BuildRequestBlocker blocker) { RequestBlockedDelegate requestBlocked = OnRequestBlocked; if (null != requestBlocked) { requestBlocked(blocker); } }
public void TestMultipleRequestsWithAllResults() { CreateConfiguration(1, "foo.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }); BuildResult result1 = CacheBuildResult(request1, "foo", TestUtilities.GetSuccessResult()); CreateConfiguration(2, "bar.proj"); BuildRequest request2 = CreateBuildRequest(2, 2, new string[] { "bar" }); BuildResult result2 = CacheBuildResult(request2, "bar", TestUtilities.GetSuccessResult()); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.Equal(3, response.Count); // First two are the results which were cached. Assert.Equal(ScheduleActionType.ReportResults, response[0].Action); Assert.True(ResultsCache_Tests.AreResultsIdentical(result1, response[0].Unblocker.Result)); Assert.Equal(ScheduleActionType.ReportResults, response[1].Action); Assert.True(ResultsCache_Tests.AreResultsIdentical(result2, response[1].Unblocker.Result)); // Last response is to continue the parent. Assert.Equal(ScheduleActionType.ResumeExecution, response[2].Action); Assert.Equal(request1.ParentGlobalRequestId, response[2].Unblocker.BlockedRequestId); Assert.Null(response[2].Unblocker.Result); }
/// <summary> /// Sends a build request to the Build Manager for scheduling /// </summary> /// <param name="blocker">The information about why the request is blocked.</param> private void IssueBuildRequest(BuildRequestBlocker blocker) { ErrorUtilities.VerifyThrowArgumentNull(blocker, "blocker"); if (blocker.BuildRequests == null) { // This is the case when we aren't blocking on new requests, but rather an in-progress request which is executing a target for which we need results. TraceEngine("Blocking global request {0} on global request {1} because it is already executing target {2}", blocker.BlockedRequestId, blocker.BlockingRequestId, blocker.BlockingTarget); } else { foreach (BuildRequest blockingRequest in blocker.BuildRequests) { TraceEngine("Sending node request {0} (configuration {1}) with parent {2} to Build Manager", blockingRequest.NodeRequestId, blockingRequest.ConfigurationId, blocker.BlockedRequestId); } } RaiseRequestBlocked(blocker); }
/// <summary> /// Reports a configuration response to the request, allowing it to satisfy outstanding requests. /// <seealso cref="BuildRequestConfigurationResponse"/> /// </summary> /// <param name="response">The configuration response.</param> /// <remarks> /// Called by the Node. Non-overlapping with other calls from the Node. /// </remarks> public void ReportConfigurationResponse(BuildRequestConfigurationResponse response) { QueueAction( () => { ErrorUtilities.VerifyThrow(_status != BuildRequestEngineStatus.Shutdown && _status != BuildRequestEngineStatus.Uninitialized, "Engine loop not yet started, status is {0}.", _status); TraceEngine("Received configuration response for node config {0}, now global config {1}.", response.NodeConfigurationId, response.GlobalConfigurationId); ErrorUtilities.VerifyThrow(_componentHost != null, "No host object set"); // Remove the unresolved configuration entry from the unresolved cache. BuildRequestConfiguration config = _unresolvedConfigurations[response.NodeConfigurationId]; _unresolvedConfigurations.RemoveConfiguration(response.NodeConfigurationId); // Add the configuration to the resolved cache unless it already exists there. This will be // the case in single-proc mode as we share the global cache with the Build Manager. IConfigCache globalConfigurations = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache); if (!globalConfigurations.HasConfiguration(response.GlobalConfigurationId)) { config.ConfigurationId = response.GlobalConfigurationId; config.ResultsNodeId = response.ResultsNodeId; globalConfigurations.AddConfiguration(config); } // Evaluate the current list of requests and tell any waiting requests about our new configuration update. // If any requests can now issue build requests, do so. IResultsCache resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); List<BuildRequestBlocker> blockersToIssue = new List<BuildRequestBlocker>(); foreach (BuildRequestEntry currentEntry in _requests) { List<BuildRequest> requestsToIssue = new List<BuildRequest>(); if (currentEntry.State == BuildRequestEntryState.Waiting) { // Resolve the configuration id and get the list of requests to be issued, if any. bool issueRequests = currentEntry.ResolveConfigurationRequest(response.NodeConfigurationId, response.GlobalConfigurationId); // If we had any requests which are now ready to be issued, do so. if (issueRequests) { IEnumerable<BuildRequest> resolvedRequests = currentEntry.GetRequestsToIssueIfReady(); foreach (BuildRequest request in resolvedRequests) { // If we have results already in the cache for this request, give them to the // entry now. ResultsCacheResponse cacheResponse = resultsCache.SatisfyRequest(request, config.ProjectInitialTargets, config.ProjectDefaultTargets, config.GetAfterTargetsForDefaultTargets(request), skippedResultsAreOK: false); if (cacheResponse.Type == ResultsCacheResponseType.Satisfied) { // We have a result, give it back to this request. currentEntry.ReportResult(cacheResponse.Results); } else { requestsToIssue.Add(request); } } } } if (requestsToIssue.Count != 0) { BuildRequestBlocker blocker = new BuildRequestBlocker(currentEntry.Request.GlobalRequestId, currentEntry.GetActiveTargets(), requestsToIssue.ToArray()); blockersToIssue.Add(blocker); } } // Issue all of the outstanding build requests. foreach (BuildRequestBlocker blocker in blockersToIssue) { // Issue the build request IssueBuildRequest(blocker); } }, isLastTask: false); }
/// <summary> /// This method is responsible for evaluating whether we have enough information to make the request of the Build Manager, /// or if we need to obtain additional configuration information. It then issues either configuration /// requests or build requests, or both as needed. /// </summary> /// <param name="issuingEntry">The BuildRequestEntry which is making the request</param> /// <param name="newRequests">The array of "child" build requests to be issued.</param> /// <remarks> /// When we receive a build request, we first have to determine if we already have a configuration which matches the /// one used by the request. We do this because everywhere we deal with requests and results beyond this function, we /// use configuration ids, which are assigned once by the Build Manager and are global to the system. If we do /// not have a global configuration id, we can't check to see if we already have build results for the request, so we /// cannot send the request out. Thus, first we determine the configuration id. /// /// Assuming we don't have the global configuration id locally, we will send the configuration to the Build Manager. /// It will look up or assign the global configuration id and send it back to us. /// /// Once we have the global configuration id, we can then look up results locally. If we have enough results to fulfill /// the request, we give them back to the request, otherwise we have to forward the request to the Build Mangager /// for scheduling. /// </remarks> private void IssueBuildRequests(BuildRequestEntry issuingEntry, FullyQualifiedBuildRequest[] newRequests) { ErrorUtilities.VerifyThrow(_componentHost != null, "No host object set"); // For each request, we need to determine if we have a local configuration in the // configuration cache. If we do, we can issue the build request immediately. // Otherwise, we need to ask the Build Manager for configuration IDs and issue those requests // later. IConfigCache globalConfigCache = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache); // We are going to potentially issue several requests. We don't want the state of the request being modified by // other threads while this occurs, so we lock the request for the duration. This lock is the same lock // used by the BuildRequestEntry itself to lock each of its data-modifying methods, effectively preventing // any other thread from modifying the BuildRequestEntry while we hold it. This mechanism also means that it // is not necessary for other threads to take the global lock explicitly if they are just doing single operations // to the entry rather than a series of them. lock (issuingEntry.GlobalLock) { List<BuildResult> existingResultsToReport = new List<BuildResult>(); HashSet<NGen<int>> unresolvedConfigurationsAdded = new HashSet<NGen<int>>(); foreach (FullyQualifiedBuildRequest request in newRequests) { // Do we have a matching configuration? BuildRequestConfiguration matchingConfig = globalConfigCache.GetMatchingConfiguration(request.Config); BuildRequest newRequest = null; if (matchingConfig == null) { // No configuration locally, are we already waiting for it? matchingConfig = _unresolvedConfigurations.GetMatchingConfiguration(request.Config); if (matchingConfig == null) { // Not waiting for it request.Config.ConfigurationId = GetNextUnresolvedConfigurationId(); _unresolvedConfigurations.AddConfiguration(request.Config); unresolvedConfigurationsAdded.Add(request.Config.ConfigurationId); } else { request.Config.ConfigurationId = matchingConfig.ConfigurationId; } // Whether we are already waiting for a configuration or we need to wait for another one // we will add this request as waiting for a configuration. As new configuration resolutions // come in, we will check our requests which are waiting for configurations move them to // waiting for results. It is important that we tell the issuing request to wait for a result // prior to issuing any necessary configuration request so that we don't get into a state where // we receive the configuration response before we enter the wait state. newRequest = new BuildRequest(issuingEntry.Request.SubmissionId, GetNextBuildRequestId(), request.Config.ConfigurationId, request.Targets, issuingEntry.Request.HostServices, issuingEntry.Request.BuildEventContext, issuingEntry.Request); issuingEntry.WaitForResult(newRequest); if (matchingConfig == null) { // Issue the config resolution request TraceEngine("Request {0}({1}) (nr {2}) is waiting on configuration {3} (IBR)", issuingEntry.Request.GlobalRequestId, issuingEntry.Request.ConfigurationId, issuingEntry.Request.NodeRequestId, request.Config.ConfigurationId); issuingEntry.WaitForConfiguration(request.Config); } } else { // We have a configuration, see if we already have results locally. newRequest = new BuildRequest(issuingEntry.Request.SubmissionId, GetNextBuildRequestId(), matchingConfig.ConfigurationId, request.Targets, issuingEntry.Request.HostServices, issuingEntry.Request.BuildEventContext, issuingEntry.Request); IResultsCache resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); ResultsCacheResponse response = resultsCache.SatisfyRequest(newRequest, matchingConfig.ProjectInitialTargets, matchingConfig.ProjectDefaultTargets, matchingConfig.GetAfterTargetsForDefaultTargets(newRequest), skippedResultsAreOK: false); if (response.Type == ResultsCacheResponseType.Satisfied) { // We have a result, give it back to this request. issuingEntry.WaitForResult(newRequest); // Log the fact that we handled this from the cache. _nodeLoggingContext.LogRequestHandledFromCache(newRequest, _configCache[newRequest.ConfigurationId], response.Results); // Can't report the result directly here, because that could cause the request to go from // Waiting to Ready. existingResultsToReport.Add(response.Results); } else { // No result, to wait for it. issuingEntry.WaitForResult(newRequest); } } } // If we have any results we had to report, do so now. foreach (BuildResult existingResult in existingResultsToReport) { issuingEntry.ReportResult(existingResult); } // Issue any configuration requests we may still need. List<BuildRequestConfiguration> unresolvedConfigurationsToIssue = issuingEntry.GetUnresolvedConfigurationsToIssue(); if (unresolvedConfigurationsToIssue != null) { foreach (BuildRequestConfiguration unresolvedConfigurationToIssue in unresolvedConfigurationsToIssue) { unresolvedConfigurationsAdded.Remove(unresolvedConfigurationToIssue.ConfigurationId); IssueConfigurationRequest(unresolvedConfigurationToIssue); } } // Remove any configurations we ended up not waiting for, otherwise future requests will think we are still waiting for them // and will never get submitted. foreach (int unresolvedConfigurationId in unresolvedConfigurationsAdded) { _unresolvedConfigurations.RemoveConfiguration(unresolvedConfigurationId); } // Finally, if we can issue build requests, do so. List<BuildRequest> requestsToIssue = issuingEntry.GetRequestsToIssueIfReady(); if (requestsToIssue != null) { BuildRequestBlocker blocker = new BuildRequestBlocker(issuingEntry.Request.GlobalRequestId, issuingEntry.GetActiveTargets(), requestsToIssue.ToArray()); IssueBuildRequest(blocker); } if (issuingEntry.State == BuildRequestEntryState.Ready) { ErrorUtilities.VerifyThrow((requestsToIssue == null) || (requestsToIssue.Count == 0), "Entry shouldn't be ready if we also issued requests."); ActivateBuildRequest(issuingEntry); } } }
/// <summary> /// Gets called by the build request engine when there is a new build request (engine callback) /// </summary> /// <param name="request"></param> private void RequestEngine_OnNewRequest(BuildRequestBlocker blocker) { if (this.testDataProvider != null) { foreach (BuildRequest request in blocker.BuildRequests) { this.testDataProvider.NewRequest = request; } } }
/// <summary> /// Callback for event raised when a new build request is generated by an MSBuild callback /// </summary> /// <param name="request">The new build request</param> private void Engine_NewRequest(BuildRequestBlocker blocker) { _newRequest_Request = blocker; _newRequestEvent.Set(); }
public void TestChildRequest() { CreateConfiguration(1, "foo.proj"); BuildRequest request = CreateBuildRequest(1, 1, new string[] { "foo" }); BuildRequestBlocker blocker = new BuildRequestBlocker(-1, new string[] { }, new BuildRequest[] { request }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); CreateConfiguration(2, "bar.proj"); BuildRequest childRequest = CreateBuildRequest(2, 2, new string[] { "foo" }, request); BuildResult childResult = CacheBuildResult(childRequest, "foo", TestUtilities.GetSuccessResult()); blocker = new BuildRequestBlocker(0, new string[] { "foo" }, new BuildRequest[] { childRequest }); response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.Equal(2, response.Count); // The first response will be to report the results back to the node. Assert.Equal(ScheduleActionType.ReportResults, response[0].Action); Assert.True(ResultsCache_Tests.AreResultsIdentical(childResult, response[0].Unblocker.Result)); // The second response will be to continue building the original request. Assert.Equal(ScheduleActionType.ResumeExecution, response[1].Action); Assert.Null(response[1].Unblocker.Result); }
public void TestMultipleRequests() { CreateConfiguration(1, "foo.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }); BuildRequest request2 = CreateBuildRequest(2, 1, new string[] { "bar" }); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.Equal(1, response.Count); Assert.Equal(ScheduleActionType.ScheduleWithConfiguration, response[0].Action); Assert.Equal(request1, response[0].BuildRequest); }
/// <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); }
public void TestOutOfProcNodeCreatedWhenAffinityIsOutOfProc() { CreateConfiguration(1, "foo.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequest request2 = CreateBuildRequest(2, 1, new string[] { "bar" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); // Parent request is blocked by the fact that both child requests require the out-of-proc node that doesn't // exist yet. Assert.Equal(1, response.Count); Assert.Equal(ScheduleActionType.CreateNode, response[0].Action); Assert.Equal(NodeAffinity.OutOfProc, response[0].RequiredNodeType); Assert.Equal(1, response[0].NumberOfNodesToCreate); }
/// <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); } } } } }
public void TestMaxNodeCountOOPNodesCreatedForOOPAffinitizedRequests() { _host.BuildParameters.MaxNodeCount = 3; CreateConfiguration(1, "foo.proj"); CreateConfiguration(2, "bar.proj"); CreateConfiguration(3, "baz.proj"); CreateConfiguration(4, "quz.proj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequest request2 = CreateBuildRequest(2, 2, new string[] { "bar" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequest request3 = CreateBuildRequest(3, 3, new string[] { "baz" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequest request4 = CreateBuildRequest(4, 4, new string[] { "qux" }, NodeAffinity.OutOfProc, _defaultParentRequest); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2, request3, request4 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); // Parent request is blocked by the fact that both child requests require the out-of-proc node that doesn't // exist yet. Assert.Equal(1, response.Count); Assert.Equal(ScheduleActionType.CreateNode, response[0].Action); Assert.Equal(NodeAffinity.OutOfProc, response[0].RequiredNodeType); Assert.Equal(3, response[0].NumberOfNodesToCreate); }
/// <summary> /// Reports that the specified request has become blocked and cannot proceed. /// </summary> public IEnumerable<ScheduleResponse> ReportRequestBlocked(int nodeId, BuildRequestBlocker blocker) { _schedulingData.EventTime = DateTime.UtcNow; List<ScheduleResponse> responses = new List<ScheduleResponse>(); // Get the parent, if any SchedulableRequest parentRequest = null; if (blocker.BlockedRequestId != BuildRequest.InvalidGlobalRequestId) { if (blocker.YieldAction == YieldAction.Reacquire) { parentRequest = _schedulingData.GetYieldingRequest(blocker.BlockedRequestId); } else { parentRequest = _schedulingData.GetExecutingRequest(blocker.BlockedRequestId); } } try { // We are blocked either on new requests (top-level or MSBuild task) or on an in-progress request that is // building a target we want to build. if (blocker.YieldAction != YieldAction.None) { TraceScheduler("Request {0} on node {1} is performing yield action {2}.", blocker.BlockedRequestId, nodeId, blocker.YieldAction); HandleYieldAction(parentRequest, blocker); } else if ((blocker.BlockingRequestId == blocker.BlockedRequestId) && blocker.BlockingRequestId != BuildRequest.InvalidGlobalRequestId) { // We are blocked waiting for a transfer of results. HandleRequestBlockedOnResultsTransfer(parentRequest, responses); } else if (blocker.BlockingRequestId != BuildRequest.InvalidGlobalRequestId) { // We are blocked by a request executing a target for which we need results. try { HandleRequestBlockedOnInProgressTarget(parentRequest, blocker); } catch (SchedulerCircularDependencyException ex) { TraceScheduler("Circular dependency caused by request {0}({1}) (nr {2}), parent {3}({4}) (nr {5})", ex.Request.GlobalRequestId, ex.Request.ConfigurationId, ex.Request.NodeRequestId, parentRequest.BuildRequest.GlobalRequestId, parentRequest.BuildRequest.ConfigurationId, parentRequest.BuildRequest.NodeRequestId); responses.Add(ScheduleResponse.CreateCircularDependencyResponse(nodeId, parentRequest.BuildRequest, ex.Request)); } } else { // We are blocked by new requests, either top-level or MSBuild task. HandleRequestBlockedByNewRequests(parentRequest, blocker, responses); } } catch (SchedulerCircularDependencyException ex) { TraceScheduler("Circular dependency caused by request {0}({1}) (nr {2}), parent {3}({4}) (nr {5})", ex.Request.GlobalRequestId, ex.Request.ConfigurationId, ex.Request.NodeRequestId, parentRequest.BuildRequest.GlobalRequestId, parentRequest.BuildRequest.ConfigurationId, parentRequest.BuildRequest.NodeRequestId); responses.Add(ScheduleResponse.CreateCircularDependencyResponse(nodeId, parentRequest.BuildRequest, ex.Request)); } // Now see if we can schedule requests somewhere since we // a) have a new request; and // b) have a node which is now waiting and not doing anything. ScheduleUnassignedRequests(responses); return responses; }
public void TestTraversalAffinityIsInProc() { _host.BuildParameters.MaxNodeCount = 3; CreateConfiguration(1, "dirs.proj"); CreateConfiguration(2, "abc.metaproj"); BuildRequest request1 = CreateBuildRequest(1, 1, new string[] { "foo" }, _defaultParentRequest); BuildRequest request2 = CreateBuildRequest(2, 2, new string[] { "bar" }, _defaultParentRequest); BuildRequestBlocker blocker = new BuildRequestBlocker(request1.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request1, request2 }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); // There will be no request to create a new node, because both of the above requests are traversals, // which have an affinity of "inproc", and the inproc node already exists. Assert.Equal(1, response.Count); Assert.Equal(ScheduleActionType.ScheduleWithConfiguration, response[0].Action); Assert.Equal(request1, response[0].BuildRequest); }
/// <summary> /// Updates the state of a request based on its desire to yield or reacquire control of its node. /// </summary> private void HandleYieldAction(SchedulableRequest parentRequest, BuildRequestBlocker blocker) { if (blocker.YieldAction == YieldAction.Yield) { // Mark the request blocked. parentRequest.Yield(blocker.TargetsInProgress); } else { // Mark the request ready. parentRequest.Reacquire(); } }
public void TestResult() { CreateConfiguration(1, "foo.proj"); BuildRequest request = CreateBuildRequest(1, 1); BuildRequestBlocker blocker = new BuildRequestBlocker(request.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); BuildResult result = CreateBuildResult(request, "foo", TestUtilities.GetSuccessResult()); response = new List<ScheduleResponse>(_scheduler.ReportResult(1, result)); Assert.Equal(2, response.Count); // First response is reporting the results for this request to the parent Assert.Equal(ScheduleActionType.ReportResults, response[0].Action); // Second response is continuing execution of the parent Assert.Equal(ScheduleActionType.ResumeExecution, response[1].Action); Assert.Equal(request.ParentGlobalRequestId, response[1].Unblocker.BlockedRequestId); }
public void TestSimpleRequest() { CreateConfiguration(1, "foo.proj"); BuildRequest request = CreateBuildRequest(1, 1); BuildRequestBlocker blocker = new BuildRequestBlocker(request.ParentGlobalRequestId, new string[] { }, new BuildRequest[] { request }); List<ScheduleResponse> response = new List<ScheduleResponse>(_scheduler.ReportRequestBlocked(1, blocker)); Assert.AreEqual(1, response.Count); Assert.AreEqual(ScheduleActionType.ScheduleWithConfiguration, response[0].Action); Assert.AreEqual(request, response[0].BuildRequest); }