/// <summary> /// Retrieves a request which has been assigned to a node and is in the executing, blocked or ready states. /// </summary> public SchedulableRequest GetScheduledRequest(int globalRequestId) { SchedulableRequest returnValue = InternalGetScheduledRequestByGlobalRequestId(globalRequestId); ErrorUtilities.VerifyThrow(returnValue != null, "Global Request Id {0} has not been assigned and cannot be retrieved.", globalRequestId); return(returnValue); }
/// <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="blockerBlockingTarget">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, "blockingRequest"); ErrorUtilities.VerifyThrowArgumentNull(activeTargets, "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> /// Constructor. /// </summary> public SchedulingEvent(DateTime eventTime, SchedulableRequest request, SchedulableRequestState oldState, SchedulableRequestState newState) { _eventTime = eventTime; _request = request; _oldState = oldState; _newState = newState; }
/// <summary> /// Creates a new request and adds it to the system /// </summary> /// <remarks> /// New requests always go on the front of the queue, because we prefer to build the projects we just received first (depth first, absent /// any particular scheduling algorithm such as in the single-proc case.) /// </remarks> public SchedulableRequest CreateRequest(BuildRequest buildRequest, SchedulableRequest parent) { SchedulableRequest request = new SchedulableRequest(this, buildRequest, parent); request.CreationTime = EventTime; LinkedListNode <SchedulableRequest> requestNode = _unscheduledRequests.AddFirst(request); _unscheduledRequestNodesByRequest[request] = requestNode; // Update the configuration information. HashSet <SchedulableRequest> requests; if (!_configurationToRequests.TryGetValue(request.BuildRequest.ConfigurationId, out requests)) { requests = new HashSet <SchedulableRequest>(); _configurationToRequests[request.BuildRequest.ConfigurationId] = requests; } requests.Add(request); // Update the build hierarchy. if (!_buildHierarchy.ContainsKey(request)) { _buildHierarchy[request] = new List <SchedulableRequest>(8); } if (parent != null) { ErrorUtilities.VerifyThrow(_buildHierarchy.ContainsKey(parent), "Parent doesn't exist in build hierarchy for request {0}", request.BuildRequest.GlobalRequestId); _buildHierarchy[parent].Add(request); } return(request); }
/// <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> /// Gets the name of the plan file for a specified submission. /// </summary> private string GetPlanName(SchedulableRequest rootRequest) { if (rootRequest == null) { return(null); } return(_configCache[rootRequest.BuildRequest.ConfigurationId].ProjectFullPath + ".buildplan"); }
/// <summary> /// Reads a plan for the specified submission Id. /// </summary> public void ReadPlan(int submissionId, ILoggingService loggingService, BuildEventContext buildEventContext) { if (!BuildParameters.EnableBuildPlan) { return; } SchedulableRequest rootRequest = GetRootRequest(submissionId); if (rootRequest == null) { return; } string planName = GetPlanName(rootRequest); if (String.IsNullOrEmpty(planName)) { return; } if (!FileSystems.Default.FileExists(planName)) { return; } try { using (StreamReader file = new StreamReader(File.Open(planName, FileMode.Open))) { ReadTimes(file); ReadHierarchy(file); } if (_configIdToData.Count > 0) { AnalyzeData(); } } catch (IOException) { loggingService.LogCommentFromText(buildEventContext, MessageImportance.Low, ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CantReadBuildPlan", planName)); } catch (InvalidDataException) { loggingService.LogCommentFromText(buildEventContext, MessageImportance.Low, ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildPlanCorrupt", planName)); } catch (FormatException) { loggingService.LogCommentFromText(buildEventContext, MessageImportance.Low, ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildPlanCorrupt", planName)); } }
/// <summary> /// Verifies that the request is scheduled and in the expected state. /// </summary> private void ExpectScheduledRequestState(int globalRequestId, SchedulableRequestState state) { SchedulableRequest request = InternalGetScheduledRequestByGlobalRequestId(globalRequestId); if (request == null) { ErrorUtilities.ThrowInternalError("Request {0} was expected to be in state {1} but is not scheduled at all (it may be unscheduled or may be unknown to the system.)", globalRequestId, state); } else { request.VerifyState(state); } }
/// <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> /// Recursively accumulates the amount of time spent in each configuration. /// </summary> private void RecursiveAccumulateConfigurationTimes(SchedulableRequest request, Dictionary <int, double> accumulatedTimeByConfiguration) { double accumulatedTime; // NOTE: Do we want to count it each time the config appears in the hierarchy? This will inflate the // cost of frequently referenced configurations. accumulatedTimeByConfiguration.TryGetValue(request.BuildRequest.ConfigurationId, out accumulatedTime); accumulatedTimeByConfiguration[request.BuildRequest.ConfigurationId] = accumulatedTime + request.GetTimeSpentInState(SchedulableRequestState.Executing).TotalMilliseconds; foreach (SchedulableRequest childRequest in _schedulingData.GetRequestsByHierarchy(request)) { RecursiveAccumulateConfigurationTimes(childRequest, accumulatedTimeByConfiguration); } }
/// <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> /// Writes out all of the dependencies for a specified request, recursively. /// </summary> private void RecursiveWriteDependencies(StreamWriter file, SchedulableRequest request) { file.Write(request.BuildRequest.ConfigurationId); foreach (SchedulableRequest child in _schedulingData.GetRequestsByHierarchy(request)) { file.Write(" {0}", child.BuildRequest.ConfigurationId); } file.WriteLine(); foreach (SchedulableRequest child in _schedulingData.GetRequestsByHierarchy(request)) { RecursiveWriteDependencies(file, child); } }
/// <summary> /// Writes a plan for the specified submission id. /// </summary> public void WritePlan(int submissionId, ILoggingService loggingService, BuildEventContext buildEventContext) { if (!BuildParameters.EnableBuildPlan) { return; } SchedulableRequest rootRequest = GetRootRequest(submissionId); if (rootRequest == null) { return; } string planName = GetPlanName(rootRequest); if (String.IsNullOrEmpty(planName)) { return; } try { using (StreamWriter file = new StreamWriter(File.Open(planName, FileMode.Create))) { // Write the accumulated configuration times. Dictionary <int, double> accumulatedTimeByConfiguration = new Dictionary <int, double>(); RecursiveAccumulateConfigurationTimes(rootRequest, accumulatedTimeByConfiguration); List <int> configurationsInOrder = new List <int>(accumulatedTimeByConfiguration.Keys); configurationsInOrder.Sort(); foreach (int configId in configurationsInOrder) { file.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", configId, accumulatedTimeByConfiguration[configId], _configCache[configId].ProjectFullPath)); } file.WriteLine(); // Write out the dependency information. RecursiveWriteDependencies(file, rootRequest); } } catch (IOException) { loggingService.LogCommentFromText(buildEventContext, MessageImportance.Low, ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CantWriteBuildPlan", planName)); } }
/// <summary> /// Retrieves a set of build requests which have the specified parent. If root is null, this will retrieve all of the /// top-level requests. /// </summary> public IEnumerable <SchedulableRequest> GetRequestsByHierarchy(SchedulableRequest root) { if (root == null) { // Retrieve all requests which are roots of the tree. List <SchedulableRequest> roots = new List <SchedulableRequest>(); foreach (SchedulableRequest key in _buildHierarchy.Keys) { if (key.Parent == null) { roots.Add(key); } } return(roots); } return(_buildHierarchy[root]); }
/// <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> /// Constructor. /// </summary> public SchedulableRequest(SchedulingData collection, BuildRequest request, SchedulableRequest parent) { ErrorUtilities.VerifyThrowArgumentNull(collection, "collection"); ErrorUtilities.VerifyThrowArgumentNull(request, "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> /// Removes the association between this request and the one we are blocked by. /// </summary> internal void DisconnectRequestWeAreBlockedBy(BlockingRequestKey blockingRequestKey) { ErrorUtilities.VerifyThrow(_requestsWeAreBlockedBy.ContainsKey(blockingRequestKey), "We are not blocked by the specified request."); SchedulableRequest unblockingRequest = _requestsWeAreBlockedBy[blockingRequestKey]; 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> /// Returns true if the request can be scheduled to the specified node. /// </summary> public bool CanScheduleRequestToNode(SchedulableRequest request, int nodeId) { int requiredNodeId = GetAssignedNodeForRequestConfiguration(request.BuildRequest.ConfigurationId); return(requiredNodeId == Scheduler.InvalidNodeId || requiredNodeId == nodeId); }
/// <summary> /// Determines if the specified request is currently scheduled. /// </summary> public bool IsRequestScheduled(SchedulableRequest request) { return(InternalGetScheduledRequestByGlobalRequestId(request.BuildRequest.GlobalRequestId) != null); }
/// <summary> /// Updates the state of the specified request. /// </summary> public void UpdateFromState(SchedulableRequest request, SchedulableRequestState previousState) { // Remove from its old collection switch (previousState) { case SchedulableRequestState.Blocked: _blockedRequests.Remove(request.BuildRequest.GlobalRequestId); break; case SchedulableRequestState.Yielding: _yieldingRequests.Remove(request.BuildRequest.GlobalRequestId); break; case SchedulableRequestState.Completed: ErrorUtilities.ThrowInternalError("Should not be updating a request after it has reached the Completed state."); break; case SchedulableRequestState.Executing: _executingRequests.Remove(request.BuildRequest.GlobalRequestId); _executingRequestByNode[request.AssignedNode] = null; break; case SchedulableRequestState.Ready: _readyRequests.Remove(request.BuildRequest.GlobalRequestId); _readyRequestsByNode[request.AssignedNode].Remove(request); break; case SchedulableRequestState.Unscheduled: LinkedListNode <SchedulableRequest> requestNode = _unscheduledRequestNodesByRequest[request]; _unscheduledRequestNodesByRequest.Remove(request); _unscheduledRequests.Remove(requestNode); if (request.State != SchedulableRequestState.Completed) { // Map the request to the node. HashSet <SchedulableRequest> requestsAssignedToNode; if (!_scheduledRequestsByNode.TryGetValue(request.AssignedNode, out requestsAssignedToNode)) { requestsAssignedToNode = new HashSet <SchedulableRequest>(); _scheduledRequestsByNode[request.AssignedNode] = requestsAssignedToNode; } ErrorUtilities.VerifyThrow(!requestsAssignedToNode.Contains(request), "Request {0} is already scheduled to node {1}", request.BuildRequest.GlobalRequestId, request.AssignedNode); requestsAssignedToNode.Add(request); // Map the configuration to the node. HashSet <int> configurationsAssignedToNode; if (!_configurationsByNode.TryGetValue(request.AssignedNode, out configurationsAssignedToNode)) { configurationsAssignedToNode = new HashSet <int>(); _configurationsByNode[request.AssignedNode] = configurationsAssignedToNode; } if (!configurationsAssignedToNode.Contains(request.BuildRequest.ConfigurationId)) { configurationsAssignedToNode.Add(request.BuildRequest.ConfigurationId); } } break; } // Add it to its new location switch (request.State) { case SchedulableRequestState.Blocked: ErrorUtilities.VerifyThrow(!_blockedRequests.ContainsKey(request.BuildRequest.GlobalRequestId), "Request with global id {0} is already blocked!"); _blockedRequests[request.BuildRequest.GlobalRequestId] = request; break; case SchedulableRequestState.Yielding: ErrorUtilities.VerifyThrow(!_yieldingRequests.ContainsKey(request.BuildRequest.GlobalRequestId), "Request with global id {0} is already yielded!"); _yieldingRequests[request.BuildRequest.GlobalRequestId] = request; break; case SchedulableRequestState.Completed: ErrorUtilities.VerifyThrow(_configurationToRequests.ContainsKey(request.BuildRequest.ConfigurationId), "Configuration {0} never had requests assigned to it.", request.BuildRequest.ConfigurationId); ErrorUtilities.VerifyThrow(_configurationToRequests[request.BuildRequest.ConfigurationId].Count > 0, "Configuration {0} has no requests assigned to it.", request.BuildRequest.ConfigurationId); _configurationToRequests[request.BuildRequest.ConfigurationId].Remove(request); if (_scheduledRequestsByNode.ContainsKey(request.AssignedNode)) { _scheduledRequestsByNode[request.AssignedNode].Remove(request); } request.EndTime = EventTime; break; case SchedulableRequestState.Executing: ErrorUtilities.VerifyThrow(!_executingRequests.ContainsKey(request.BuildRequest.GlobalRequestId), "Request with global id {0} is already executing!"); ErrorUtilities.VerifyThrow(!_executingRequestByNode.ContainsKey(request.AssignedNode) || _executingRequestByNode[request.AssignedNode] == null, "Node {0} is currently executing a request.", request.AssignedNode); _executingRequests[request.BuildRequest.GlobalRequestId] = request; _executingRequestByNode[request.AssignedNode] = request; _configurationToNode[request.BuildRequest.ConfigurationId] = request.AssignedNode; if (previousState == SchedulableRequestState.Unscheduled) { request.StartTime = EventTime; } break; case SchedulableRequestState.Ready: ErrorUtilities.VerifyThrow(!_readyRequests.ContainsKey(request.BuildRequest.GlobalRequestId), "Request with global id {0} is already ready!"); _readyRequests[request.BuildRequest.GlobalRequestId] = request; HashSet <SchedulableRequest> readyRequestsOnNode; if (!_readyRequestsByNode.TryGetValue(request.AssignedNode, out readyRequestsOnNode)) { readyRequestsOnNode = new HashSet <SchedulableRequest>(); _readyRequestsByNode[request.AssignedNode] = readyRequestsOnNode; } ErrorUtilities.VerifyThrow(!readyRequestsOnNode.Contains(request), "Request with global id {0} is already marked as ready on node {1}", request.BuildRequest.GlobalRequestId, request.AssignedNode); readyRequestsOnNode.Add(request); break; case SchedulableRequestState.Unscheduled: ErrorUtilities.ThrowInternalError("Request with global id {0} cannot transition to the Unscheduled state", request.BuildRequest.GlobalRequestId); break; } _buildEvents.Add(new SchedulingEvent(EventTime, request, previousState, request.State)); }
/// <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); }