MegaBites-AI/Windows-powershell
0372
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 