private void InitializeScopeList(ManageMemoryMode mode) { m_manageResourcesScopeList.Clear(); lock (m_syncLock) { Func <ResourceScope, bool> isEligible; IComparer <ResourceScope> comparer; switch (mode) { case ManageMemoryMode.CancelSuspended: isEligible = (scope) => scope.IsSuspended; // When cancelling suspended processes, we start from the processes having the largest commit charge. comparer = s_largestCommitSizeFirstComparer; break; case ManageMemoryMode.CancellationRam: case ManageMemoryMode.CancellationCommit: isEligible = (scope) => scope.CanCancel && scope.MemoryCounters.PeakWorkingSetMb != 0; // When cancelling processes, we start from the processes having the shortest running time. comparer = s_shortestRunningTimeFirstComparer; break; case ManageMemoryMode.EmptyWorkingSet: isEligible = (scope) => !scope.IsSuspended && scope.MemoryCounters.LastWorkingSetMb != 0; comparer = s_largestWorkingSetFirstComparer; break; case ManageMemoryMode.Suspend: isEligible = (scope) => !scope.IsSuspended && scope.MemoryCounters.PeakWorkingSetMb != 0; comparer = s_largestWorkingSetFirstComparer; break; case ManageMemoryMode.Resume: isEligible = (scope) => scope.IsSuspended; // When resuming processes, we start from the processes having the longest running time. comparer = s_longestRunningTimeFirstComparer; break; default: isEligible = null; comparer = null; Contract.Assert(false, $"Unknown mode: {mode}"); break; } foreach (var scope in m_pipResourceScopes.Values) { if (isEligible(scope)) { m_manageResourcesScopeList.Add(scope); } } m_manageResourcesScopeList.Sort(comparer); } }
private void ManageResourcesByPreference(ManageMemoryMode mode, int requiredSize) { // Resume and EmptyWorkingSet can be called for all items in the list. // However, cancellation and suspend should keep one item untouchable to keep the scheduler progressing. int allowedCount = (mode == ManageMemoryMode.Resume || mode == ManageMemoryMode.EmptyWorkingSet) ? m_manageResourcesScopeList.Count : m_manageResourcesScopeList.Count - 1; for (int i = 0; i < m_manageResourcesScopeList.Count && requiredSize > 0 && allowedCount > 0; i++) { var scope = m_manageResourcesScopeList[i]; int sizeMb = 0; switch (mode) { case ManageMemoryMode.CancellationRam: case ManageMemoryMode.CancellationCommit: case ManageMemoryMode.CancelSuspended: { sizeMb = mode == ManageMemoryMode.CancellationRam ? scope.MemoryCounters.LastWorkingSetMb : scope.MemoryCounters.LastCommitSizeMb; scope.CancelAsync(ResourceScopeCancellationReason.ResourceLimits).Forget(); break; } case ManageMemoryMode.EmptyWorkingSet: case ManageMemoryMode.Suspend: { sizeMb = scope.EmptyWorkingSet(m_loggingContext, mode == ManageMemoryMode.Suspend); break; } case ManageMemoryMode.Resume: { if (scope.RamMbNeededForResume < requiredSize) { sizeMb = scope.ResumeProcess(m_loggingContext); } break; } } requiredSize -= sizeMb; allowedCount--; } }
private void ManageResourcesByPreference(ManageMemoryMode mode, int requiredSize) { int allowedCount = GetAllowedManagedResourceScopeCount(mode); for (int i = 0; i < m_manageResourcesScopeList.Count && requiredSize > 0 && allowedCount > 0; i++) { var scope = m_manageResourcesScopeList[i]; int sizeMb = 0; switch (mode) { case ManageMemoryMode.CancellationRam: case ManageMemoryMode.CancellationCommit: case ManageMemoryMode.CancelSuspended: { sizeMb = mode == ManageMemoryMode.CancellationRam ? scope.MemoryCounters.LastWorkingSetMb : scope.MemoryCounters.LastCommitSizeMb; scope.CancelAsync(ResourceScopeCancellationReason.ResourceLimits).Forget(); break; } case ManageMemoryMode.EmptyWorkingSet: case ManageMemoryMode.Suspend: { sizeMb = scope.EmptyWorkingSet(m_loggingContext, mode == ManageMemoryMode.Suspend); break; } case ManageMemoryMode.Resume: { if (scope.RamMbNeededForResume < requiredSize) { sizeMb = scope.ResumeProcess(m_loggingContext); } break; } } requiredSize -= sizeMb; allowedCount--; } }
/// <summary> /// Get the number of resource scopes that are allowed to be managed, /// according to the ManageMemoryMode requested and the state of the resource list. /// </summary> private int GetAllowedManagedResourceScopeCount(ManageMemoryMode mode) { if (mode == ManageMemoryMode.Resume || mode == ManageMemoryMode.EmptyWorkingSet) { // Resume and EmptyWorkingSet can be called for all resource scopes in the list. return(m_manageResourcesScopeList.Count); } // Other modes are cancellation/suspend // Normally, cancellation and suspend should keep one item untouchable to ensure scheduler progress. // However, if the remaining resource scope is a single suspended process and the request is for cancellation, // then that suspendend process needs to be cancelled in order to ensure scheduler progress. // This can happen if after having a single suspended process, the memory usage is still high. if (m_manageResourcesScopeList.Count == 1 && m_manageResourcesScopeList[0].IsSuspended) { return(m_manageResourcesScopeList.Count); } // In other cases, cancellation and suspend should keep one item untouchable to keep the scheduler progressing. return(m_manageResourcesScopeList.Count - 1); }
/// <summary> /// Attempt to manage resources by using the mode /// </summary> public void TryManageResources(int requiredSizeMb, ManageMemoryMode mode) { if (requiredSizeMb <= 0) { return; } if (Interlocked.CompareExchange(ref m_manageResourcesReentrancyCheck, 1, 0) == 0) { try { InitializeScopeList(mode); ManageResourcesByPreference(mode, requiredSizeMb); } finally { m_manageResourcesReentrancyCheck = 0; } } LastRequiredSizeMb = requiredSizeMb; LastManageMemoryMode = mode; }
private void RunResourceManager(ProcessResourceManager resourceManager, int requiredSizeMb, ManageMemoryMode mode) { resourceManager.RefreshMemoryCounters(); resourceManager.TryManageResources(requiredSizeMb, mode); }