Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
throttlemanager.cs767 linesDownload Raw Back to common
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Threading;6 7using Dbg = System.Management.Automation.Diagnostics;8 9namespace System.Management.Automation.Remoting10{11    #region OperationState12 13    /// <summary>14    /// Defines the different states of the operation.15    /// </summary>16    internal enum OperationState17    {18        /// <summary>19        /// Start operation completed successfully.20        /// </summary>21        StartComplete = 0,22 23        /// <summary>24        /// Stop operation completed successfully.25        /// </summary>26        StopComplete = 1,27    }28 29    /// <summary>30    /// Class describing event args which a helper class31    /// implementing IThrottleOperation need to throw.32    /// </summary>33    internal sealed class OperationStateEventArgs : EventArgs34    {35        /// <summary>36        /// Operation state.37        /// </summary>38        internal OperationState OperationState { get; set; }39 40        /// <summary>41        /// The original event which actually resulted in this42        /// event being raised.43        /// </summary>44        internal EventArgs BaseEvent { get; set; }45    }46 47    #endregion OperationState48 49    #region IThrottleOperation50 51    /// <summary>52    /// Interface which needs to be implemented by a class which wants to53    /// submit operations to the throttle manager.54    /// </summary>55    /// <remarks>Any synchronization that needs to be performed between56    /// StartOperation and StopOperation in the class that implements this57    /// interface should take care of handling the same. For instance,58    /// say New-Runspace class internally uses a class A which implements59    /// the IThrottleOperation interface. StartOperation of this60    /// class opens a runspace asynchronously on a remote machine. Stop61    /// operation is supposed to cancel the opening of this runspace. Any62    /// synchronization/cleanup issues should be handled by class A.63    /// </remarks>64    internal abstract class IThrottleOperation65    {66        /// <summary>67        /// This method should handle the actual operation which need to be68        /// controlled and performed. Examples of this can be Opening remote69        /// runspace, invoking expression in a remote runspace, etc. Once70        /// an event is successfully received as a result of this function,71        /// the handler has to ensure that it raises an OperationComplete72        /// event with StartComplete or StopComplete for the throttle manager73        /// to handle.74        /// </summary>75        internal abstract void StartOperation();76 77        /// <summary>78        /// This method should handle the situation when a stop signal is sent79        /// for this operation. For instance, when trying to open a set of80        /// remote runspaces, the user might hit ctrl-C. In which case, the81        /// pending runspaces to be opened will actually be signalled through82        /// this method to stop operation and return back. This method also83        /// needs to be asynchronous. Once an event is successfully received84        /// as a result of this function, the handler has to ensure that it85        /// raises an OperationComplete event with StopComplete for the86        /// throttle manager to handle. It is important that this function87        /// does not raise a StartComplete which will then result in the88        /// ThrottleComplete event not being raised by the throttle manager.89        /// </summary>90        internal abstract void StopOperation();91 92        /// <summary>93        /// Event which will be triggered when the operation is complete. It is94        /// assumed that all the operations performed by StartOperation and95        /// StopOperation are asynchronous. The submitter of operations may96        /// subscribe to this event to know when it's complete (or it can handle97        /// the synchronization with its scheduler) and the throttle98        /// manager will subscribe to this event to know that it's complete99        /// and to start the operation on the next item.100        /// </summary>101        internal abstract event EventHandler<OperationStateEventArgs> OperationComplete;102 103        /// <summary>104        /// This Property indicates whether an operation has been stopped.105        /// </summary>106        /// <remarks>107        /// In the initial implementation of ThrottleManager stopping108        /// individual operations was not supported. When the support109        /// for stopping individual operations was added, there was110        /// the following problem - if an operation is not there in111        /// the pending queue and in the startOperationQueue as well,112        /// then the following two scenarios are possible113        ///      (a) Operation was started and start completed114        ///      (b) Operation was started and stopped and both completed115        /// This property has been added in order to disambiguate between116        /// these two cases. When this property is set, StopOperation117        /// need not be called on the operation (this can be when the118        /// operation has stop completed or stop has been called and is119        /// pending)120        /// </remarks>121        internal bool IgnoreStop122        {123            get124            {125                return _ignoreStop;126            }127 128            set129            {130                _ignoreStop = true;131            }132        }133 134        private bool _ignoreStop = false;135 136        #region Runspace Debug137 138        /// <summary>139        /// When true enables runspace debugging for operations involving runspaces.140        /// </summary>141        internal bool RunspaceDebuggingEnabled142        {143            get;144            set;145        }146 147        /// <summary>148        /// When true configures runspace debugging to stop at first opportunity.149        /// </summary>150        internal bool RunspaceDebugStepInEnabled151        {152            get;153            set;154        }155 156        /// <summary>157        /// Event raised when operation runspace enters a debugger stopped state.158        /// </summary>159        internal event EventHandler<StartRunspaceDebugProcessingEventArgs> RunspaceDebugStop;160 161        /// <summary>162        /// RaiseRunspaceDebugStopEvent.163        /// </summary>164        /// <param name="runspace">Runspace.</param>165        internal void RaiseRunspaceDebugStopEvent(System.Management.Automation.Runspaces.Runspace runspace)166        {167            RunspaceDebugStop.SafeInvoke(this, new StartRunspaceDebugProcessingEventArgs(runspace));168        }169 170        #endregion171    }172 173    #endregion IThrottleOperation174 175    #region ThrottleManager176 177    /// <summary>178    /// Class which handles the throttling operations. This class is singleton and therefore179    /// when used either across cmdlets or at the infrastructure level it will ensure that180    /// there aren't more operations by way of accumulation than what is intended by design.181    ///182    /// This class contains a queue of items, each of which has the183    /// <see cref="System.Management.Automation.Remoting.IThrottleOperation">184    /// IThrottleOperation</see> interface implemented. To begin with185    /// THROTTLE_LIMIT number of items will be taken from the queue and the operations on186    /// them will be executed. Subsequently, as and when operations complete, new items from187    /// the queue will be taken and their operations executed.188    ///189    /// Whenever a consumer submits or adds operations, the methods will start as much190    /// operations from the queue as permitted based on the throttle limit. Also the event191    /// handler will start an operation once a previous event is completed.192    ///193    /// The queue used is a generic queue of type IThrottleOperations, as it will offer better194    /// performance.195    /// </summary>196    /// <remarks>Throttle limit is currently set to 50. This value may be modified later based197    /// on a figure that we may arrive at out of experience.</remarks>198    internal class ThrottleManager : IDisposable199    {200        #region Public (internal) Properties201 202        /// <summary>203        /// Allows the consumer to override the default throttle limit.204        /// </summary>205        internal int ThrottleLimit206        {207            get208            {209                return _throttleLimit;210            }211 212            set213            {214                if (value > 0 && value <= s_THROTTLE_LIMIT_MAX)215                {216                    _throttleLimit = value;217                }218            }219        }220 221        private int _throttleLimit = s_DEFAULT_THROTTLE_LIMIT;222 223        #endregion Public (internal) Properties224 225        #region Public (internal) Methods226 227        /// <summary>228        /// Submit a list of operations that need to be throttled.229        /// </summary>230        /// <param name="operations">List of operations to be throttled.</param>231        /// <remarks>Once the operations are added to the queue, the method will232        /// start operations from the queue233        /// </remarks>234        internal void SubmitOperations(List<IThrottleOperation> operations)235        {236            lock (_syncObject)237            {238                // operations can be submitted only until submitComplete239                // is not set to true (happens when EndSubmitOperations is called)240                if (!_submitComplete)241                {242                    // add items to the queue243                    foreach (IThrottleOperation operation in operations)244                    {245                        Dbg.Assert(operation != null,246                            "Operation submitComplete to throttle manager cannot be null");247                        _operationsQueue.Add(operation);248                    }249                }250                else251                {252                    throw new InvalidOperationException();253                }254            }255 256            // schedule operations here if possible257            StartOperationsFromQueue();258        }259 260        /// <summary>261        /// Add a single operation to the queue.262        /// </summary>263        /// <param name="operation">Operation to be added.</param>264        internal void AddOperation(IThrottleOperation operation)265        {266            // add item to the queue267            lock (_syncObject)268            {269                // operations can be submitted only until submitComplete270                // is not set to true (happens when EndSubmitOperations is called)271                if (!_submitComplete)272                {273                    Dbg.Assert(operation != null,274                        "Operation submitComplete to throttle manager cannot be null");275 276                    _operationsQueue.Add(operation);277                }278                else279                {280                    throw new InvalidOperationException();281                }282            }283 284            // start operations from queue if possible285            StartOperationsFromQueue();286        }287 288        /// <summary>289        /// Stop throttling operations.290        /// </summary>291        /// <remarks>Calling this method will also affect other cmdlets which292        /// could have potentially submitComplete operations for processing293        /// </remarks>294        /// <returns>Number of objects cleared from queue without being295        /// stopped.</returns>296        internal void StopAllOperations()297        {298            // if stopping is already in progress, make it a no op299            bool needToReturn = false;300 301            lock (_syncObject)302            {303                if (!_stopping)304                {305                    _stopping = true;306                }307                else308                {309                    needToReturn = true;310                }311            }312 313            if (needToReturn)314            {315                RaiseThrottleManagerEvents();316                return;317            }318 319            IThrottleOperation[] startOperationsInProcessArray;320 321            lock (_syncObject)322            {323                // no more submissions possible once stopped324                _submitComplete = true;325 326                // Clear all pending operations in queue so that they are not327                // scheduled when a stop operation completes328                _operationsQueue.Clear();329 330                // Make a copy of the in process queue so as to stop all331                // operations in progress332                startOperationsInProcessArray =333                        new IThrottleOperation[_startOperationQueue.Count];334                _startOperationQueue.CopyTo(startOperationsInProcessArray);335 336                // stop all operations in process (using the copy)337                foreach (IThrottleOperation operation in startOperationsInProcessArray)338                {339                    // When iterating through the array of operations in process340                    // it is quite possible that a runspace gets to the open state341                    // before stop is actually called on it. In that case, the342                    // OperationCompleteHandler will remove it from the343                    // operationsInProcess queue. Now when the runspace is closed344                    // the same handler will try removing it again and so there will345                    // be an exception. Hence adding it a second time before stop346                    // will ensure that the operation is available in the queue for347                    // removal. In case the stop succeeds before start succeeds then348                    // both will get removed (it goes without saying that there cannot349                    // be a situation where start succeeds after stop succeeded)350                    _stopOperationQueue.Add(operation);351 352                    operation.IgnoreStop = true;353                }354            }355 356            foreach (IThrottleOperation operation in startOperationsInProcessArray)357            {358                operation.StopOperation();359            }360 361            // Raise event as it can be that at this point, all operations are362            // complete363            RaiseThrottleManagerEvents();364        }365 366        /// <summary>367        /// Stop the specified operation.368        /// </summary>369        /// <param name="operation">Operation which needs to be stopped.</param>370        internal void StopOperation(IThrottleOperation operation)371        {372            // StopOperation is being called a second time373            // or the stop operation has already completed374            // - in either case just return375            if (operation.IgnoreStop)376            {377                return;378            }379 380            // If the operation has not yet been started, then381            // remove it from the pending queue382            if (_operationsQueue.IndexOf(operation) != -1)383            {384                lock (_syncObject)385                {386                    if (_operationsQueue.IndexOf(operation) != -1)387                    {388                        _operationsQueue.Remove(operation);389                        RaiseThrottleManagerEvents();390                        return;391                    }392                }393            }394 395            // The operation has already started, then add it396            // to the inprocess queue and call stop. Refer to397            // comment in StopAllOperations() as to why this is398            // being added a second time399            lock (_syncObject)400            {401                _stopOperationQueue.Add(operation);402 403                operation.IgnoreStop = true;404            }405 406            // stop the operation outside of the lock407            operation.StopOperation();408        }409 410        /// <summary>411        /// Signals that no more operations can be submitComplete412        /// for throttling.413        /// </summary>414        internal void EndSubmitOperations()415        {416            lock (_syncObject)417            {418                _submitComplete = true;419            }420 421            RaiseThrottleManagerEvents();422        }423 424        #endregion Public (internal) Methods425 426        #region Public (internal) Events427 428        /// <summary>429        /// Event raised when throttling all operations is complete.430        /// </summary>431        internal event EventHandler<EventArgs> ThrottleComplete;432 433        #endregion Public (internal) Events434 435        #region Constructors436 437        /// <summary>438        /// Public constructor.439        /// </summary>440        public ThrottleManager()441        {442            _operationsQueue = new List<IThrottleOperation>();443            _startOperationQueue = new List<IThrottleOperation>();444            _stopOperationQueue = new List<IThrottleOperation>();445            _syncObject = new object();446        }447 448        #endregion Constructors449 450        #region Private Methods451 452        /// <summary>453        /// Handler which handles state change for the object which implements454        /// the <see cref="System.Management.Automation.Remoting.IThrottleOperation"/>455        /// interface.456        /// </summary>457        /// <param name="source">Sender of the event.</param>458        /// <param name="stateEventArgs">Event information object which describes the event459        /// which triggered this method</param>460        private void OperationCompleteHandler(object source, OperationStateEventArgs stateEventArgs)461        {462            // An item has completed operation. If it's a start operation which completed463            // remove the instance from the startOperationqueue. If it's a stop operation464            // which completed, then remove the instance from both queues465            lock (_syncObject)466            {467                IThrottleOperation operation = source as IThrottleOperation;468 469                Dbg.Assert(operation != null, "Source of event should not be null");470 471                int index = -1;472 473                if (stateEventArgs.OperationState == OperationState.StartComplete)474                {475                    // A stop operation can be initiated before a start operation completes.476                    // A stop operation handler cleans up an outstanding start operation.477                    // So it is possible that a start operation complete callback will find the478                    // operation removed from the queue by an earlier stop operation complete.479                    index = _startOperationQueue.IndexOf(operation);480                    if (index != -1)481                    {482                        _startOperationQueue.RemoveAt(index);483                    }484                }485                else486                {487                    // for a stop operation, the same operation object would have been488                    // added to the stopOperationQueue as well. So we need to489                    // remove both the instances.490                    index = _startOperationQueue.IndexOf(operation);491                    if (index != -1)492                    {493                        _startOperationQueue.RemoveAt(index);494                    }495 496                    index = _stopOperationQueue.IndexOf(operation);497                    if (index != -1)498                    {499                        _stopOperationQueue.RemoveAt(index);500                    }501 502                    // if an operation signals a stopcomplete, it can mean503                    // that the operation has completed. In this case, we504                    // need to set the isStopped to true505                    operation.IgnoreStop = true;506                }507            }508 509            // It's possible that all operations are completed at this point510            // and submit is complete. So raise event511            RaiseThrottleManagerEvents();512 513            // Do necessary things for starting operation for the next item in the queue514            StartOneOperationFromQueue();515        }516 517        /// <summary>518        /// Method used to start the operation on one item in the queue.519        /// </summary>520        private void StartOneOperationFromQueue()521        {522            IThrottleOperation operation = null;523 524            lock (_syncObject)525            {526                if (_operationsQueue.Count > 0)527                {528                    operation = _operationsQueue[0];529                    _operationsQueue.RemoveAt(0);530                    operation.OperationComplete += OperationCompleteHandler;531                    _startOperationQueue.Add(operation);532                }533            }534 535            operation?.StartOperation();536        }537 538        /// <summary>539        /// Start operations to the limit possible from the queue.540        /// </summary>541        private void StartOperationsFromQueue()542        {543            int operationsInProcessCount = 0;544            int operationsQueueCount = 0;545 546            lock (_syncObject)547            {548                operationsInProcessCount = _startOperationQueue.Count;549                operationsQueueCount = _operationsQueue.Count;550            }551 552            int remainingCap = _throttleLimit - operationsInProcessCount;553 554            if (remainingCap > 0)555            {556                int numOperations = (remainingCap > operationsQueueCount) ? operationsQueueCount : remainingCap;557 558                for (int i = 0; i < numOperations; i++)559                {560                    StartOneOperationFromQueue();561                }562            }563        }564 565        /// <summary>566        /// Raise the throttle manager events once the conditions are met.567        /// </summary>568        private void RaiseThrottleManagerEvents()569        {570            bool readyToRaise = false;571 572            lock (_syncObject)573            {574                // if submit is complete, there are no operations in progress and575                // the pending queue is empty, then raise events576                if (_submitComplete &&577                    _startOperationQueue.Count == 0 &&578                    _stopOperationQueue.Count == 0 &&579                    _operationsQueue.Count == 0)580                {581                    readyToRaise = true;582                }583            }584 585            if (readyToRaise)586            {587                ThrottleComplete.SafeInvoke(this, EventArgs.Empty);588            }589        }590 591        #endregion Private Methods592 593        #region Private Members594 595        /// <summary>596        /// Default throttle limit - the maximum number of operations597        /// to be processed at a time.598        /// </summary>599        private static readonly int s_DEFAULT_THROTTLE_LIMIT = 32;600 601        /// <summary>602        /// Maximum value that the throttle limit can be set to.603        /// </summary>604        private static readonly int s_THROTTLE_LIMIT_MAX = int.MaxValue;605 606        /// <summary>607        /// All pending operations.608        /// </summary>609        private readonly List<IThrottleOperation> _operationsQueue;610 611        /// <summary>612        /// List of items on which a StartOperation has613        /// been called.614        /// </summary>615        private readonly List<IThrottleOperation> _startOperationQueue;616 617        /// <summary>618        /// List of items on which a StopOperation has619        /// been called.620        /// </summary>621        private readonly List<IThrottleOperation> _stopOperationQueue;622 623        /// <summary>624        /// Object used to synchronize access to the queues.625        /// </summary>626        private readonly object _syncObject;627 628        private bool _submitComplete = false;                    // to check if operations have been submitComplete629        private bool _stopping = false;                      // if stop is in process630 631        #endregion Private Members632 633        #region IDisposable Overrides634 635        /// <summary>636        /// Dispose method of IDisposable. Any cmdlet that uses637        /// the throttle manager needs to call this method from its638        /// Dispose method.639        /// </summary>640        public void Dispose()641        {642            Dispose(true);643 644            GC.SuppressFinalize(this);645        }646 647        /// <summary>648        /// Internal dispose method which does the actual dispose649        /// operations and finalize suppressions.650        /// </summary>651        /// <param name="disposing">If method is called from652        /// disposing of destructor</param>653        private void Dispose(bool disposing)654        {655            if (disposing)656            {657                StopAllOperations();658            }659        }660 661        #endregion IDisposable Overrides662    }663 664    #endregion ThrottleManager665 666    #region Helper Class for Testing667 668#if !CORECLR // Skip The Helper Class for Testing (Thread.Abort() Not In CoreCLR)669    internal class Operation : IThrottleOperation670    {671        private ThreadStart workerThreadDelegate;672        private Thread workerThreadStart;673        private Thread workerThreadStop;674 675        public bool Done { get; set; }676 677        public int SleepTime { get; set; } = 100;678 679        private void WorkerThreadMethodStart()680        {681            Thread.Sleep(SleepTime);682            Done = true;683            OperationStateEventArgs operationStateEventArgs =684                    new OperationStateEventArgs();685            operationStateEventArgs.OperationState = OperationState.StartComplete;686            OperationComplete.SafeInvoke(this, operationStateEventArgs);687        }688 689        private void WorkerThreadMethodStop()690        {691            workerThreadStart.Abort();692 693            OperationStateEventArgs operationStateEventArgs =694                    new OperationStateEventArgs();695            operationStateEventArgs.OperationState = OperationState.StopComplete;696            OperationComplete.SafeInvoke(this, operationStateEventArgs);697        }698 699        internal Operation()700        {701            Done = false;702            workerThreadDelegate = new ThreadStart(WorkerThreadMethodStart);703            workerThreadStart = new Thread(workerThreadDelegate);704            workerThreadDelegate = new ThreadStart(WorkerThreadMethodStop);705            workerThreadStop = new Thread(workerThreadDelegate);706        }707 708        internal override void StartOperation()709        {710            workerThreadStart.Start();711        }712 713        internal override void StopOperation()714        {715            workerThreadStop.Start();716        }717 718        internal override event EventHandler<OperationStateEventArgs> OperationComplete;719 720        internal event EventHandler<EventArgs> InternalEvent = null;721 722        internal event EventHandler<EventArgs> EventHandler723        {724            add725            {726                bool firstEntry = (InternalEvent == null);727 728                InternalEvent += value;729 730                if (firstEntry)731                {732                    OperationComplete += new EventHandler<OperationStateEventArgs>(Operation_OperationComplete);733                }734            }735 736            remove737            {738                InternalEvent -= value;739            }740        }741 742        private void Operation_OperationComplete(object sender, OperationStateEventArgs e)743        {744            InternalEvent.SafeInvoke(sender, e);745        }746 747        internal static void SubmitOperations(List<object> operations, ThrottleManager throttleManager)748        {749            List<IThrottleOperation> newOperations = new List<IThrottleOperation>();750            foreach (object operation in operations)751            {752                newOperations.Add((IThrottleOperation)operation);753            }754 755            throttleManager.SubmitOperations(newOperations);756        }757 758        internal static void AddOperation(object operation, ThrottleManager throttleManager)759        {760            throttleManager.AddOperation((IThrottleOperation)operation);761        }762    }763#endif764 765    #endregion Helper Class for Testing766}767