// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #region Using directives using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Management.Automation; using System.Threading; #endregion namespace Microsoft.Management.Infrastructure.CimCmdlets { /// /// /// Async operation base class, it will issue async operation through /// 1...* CimSession object(s), processing the async results, extended /// pssemantics operations, and manage the lifecycle of created /// CimSession object(s). /// /// internal abstract class CimAsyncOperation : IDisposable { #region Constructor /// /// Constructor. /// public CimAsyncOperation() { this.moreActionEvent = new ManualResetEventSlim(false); this.actionQueue = new ConcurrentQueue(); this._disposed = 0; this.operationCount = 0; } #endregion #region Event handler /// /// /// Handler used to handle new action event from /// object. /// /// /// /// object raised the event /// /// Event argument. protected void NewCmdletActionHandler(object cimSession, CmdletActionEventArgs actionArgs) { DebugHelper.WriteLogEx("Disposed {0}, action type = {1}", 0, this._disposed, actionArgs.Action); if (this.Disposed) { if (actionArgs.Action is CimSyncAction) { // unblock the thread waiting for response (actionArgs.Action as CimSyncAction).OnComplete(); } return; } bool isEmpty = this.actionQueue.IsEmpty; this.actionQueue.Enqueue(actionArgs.Action); if (isEmpty) { this.moreActionEvent.Set(); } } /// /// /// Handler used to handle new operation event from /// object. /// /// /// /// object raised the event. /// /// Event argument. protected void OperationCreatedHandler(object cimSession, OperationEventArgs actionArgs) { DebugHelper.WriteLogEx(); lock (this.a_lock) { this.operationCount++; } } /// /// /// Handler used to handle operation deletion event from /// object. /// /// /// /// object raised the event. /// /// Event argument. protected void OperationDeletedHandler(object cimSession, OperationEventArgs actionArgs) { DebugHelper.WriteLogEx(); lock (this.a_lock) { this.operationCount--; if (this.operationCount == 0) { this.moreActionEvent.Set(); } } } #endregion /// /// /// process all actions in the action queue /// /// /// /// wrapper of cmdlet, for details. /// public void ProcessActions(CmdletOperationBase cmdletOperation) { if (!this.actionQueue.IsEmpty) { CimBaseAction action; while (GetActionAndRemove(out action)) { action.Execute(cmdletOperation); if (this.Disposed) { break; } } } } /// /// /// process remaining actions until all operations are completed or /// current cmdlet is terminated by user. /// /// /// /// wrapper of cmdlet, for details. /// public void ProcessRemainActions(CmdletOperationBase cmdletOperation) { DebugHelper.WriteLogEx(); while (true) { ProcessActions(cmdletOperation); if (!this.IsActive()) { DebugHelper.WriteLogEx("Either disposed or all operations completed.", 2); break; } try { this.moreActionEvent.Wait(); this.moreActionEvent.Reset(); } catch (ObjectDisposedException ex) { // This might happen if this object is being disposed, // while another thread is processing the remaining actions DebugHelper.WriteLogEx("moreActionEvent was disposed: {0}.", 2, ex); break; } } ProcessActions(cmdletOperation); } #region helper methods /// /// /// Get action object from action queue. /// /// /// Next action to execute. /// True indicates there is an valid action, otherwise false. protected bool GetActionAndRemove(out CimBaseAction action) { return this.actionQueue.TryDequeue(out action); } /// /// /// Add temporary object to cache. /// /// /// Computer name of the cimsession. /// Cimsession wrapper object. protected void AddCimSessionProxy(CimSessionProxy sessionproxy) { lock (cimSessionProxyCacheLock) { if (this.cimSessionProxyCache == null) { this.cimSessionProxyCache = new List(); } if (!this.cimSessionProxyCache.Contains(sessionproxy)) { this.cimSessionProxyCache.Add(sessionproxy); } } } /// /// /// Are there active operations? /// /// /// True for having active operations, otherwise false. protected bool IsActive() { DebugHelper.WriteLogEx("Disposed {0}, Operation Count {1}", 2, this.Disposed, this.operationCount); bool isActive = (!this.Disposed) && (this.operationCount > 0); return isActive; } /// /// Create object. /// /// protected CimSessionProxy CreateCimSessionProxy(CimSessionProxy originalProxy) { CimSessionProxy proxy = new CimSessionProxy(originalProxy); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } /// /// Create object. /// /// protected CimSessionProxy CreateCimSessionProxy(CimSessionProxy originalProxy, bool passThru) { CimSessionProxy proxy = new CimSessionProxySetCimInstance(originalProxy, passThru); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } /// /// Create object. /// /// protected CimSessionProxy CreateCimSessionProxy(CimSession session) { CimSessionProxy proxy = new CimSessionProxy(session); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } /// /// Create object. /// /// protected CimSessionProxy CreateCimSessionProxy(CimSession session, bool passThru) { CimSessionProxy proxy = new CimSessionProxySetCimInstance(session, passThru); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } /// /// Create object, and /// add the proxy into cache. /// /// protected CimSessionProxy CreateCimSessionProxy(string computerName) { CimSessionProxy proxy = new CimSessionProxy(computerName); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } /// /// Create object, and /// add the proxy into cache. /// /// /// /// protected CimSessionProxy CreateCimSessionProxy(string computerName, CimInstance cimInstance) { CimSessionProxy proxy = new CimSessionProxy(computerName, cimInstance); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } /// /// Create object, and /// add the proxy into cache. /// /// /// /// protected CimSessionProxy CreateCimSessionProxy(string computerName, CimInstance cimInstance, bool passThru) { CimSessionProxy proxy = new CimSessionProxySetCimInstance(computerName, cimInstance, passThru); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } /// /// Subscribe event from proxy and add proxy to cache. /// /// protected void SubscribeEventAndAddProxytoCache(CimSessionProxy proxy) { this.AddCimSessionProxy(proxy); SubscribeToCimSessionProxyEvent(proxy); } /// /// /// Subscribe to the events issued by . /// /// /// protected virtual void SubscribeToCimSessionProxyEvent(CimSessionProxy proxy) { DebugHelper.WriteLogEx(); proxy.OnNewCmdletAction += this.NewCmdletActionHandler; proxy.OnOperationCreated += this.OperationCreatedHandler; proxy.OnOperationDeleted += this.OperationDeletedHandler; } /// /// Retrieve the base object out if wrapped in psobject. /// /// /// protected object GetBaseObject(object value) { PSObject psObject = value as PSObject; if (psObject == null) { return value; } else { object baseObject = psObject.BaseObject; var arrayObject = baseObject as object[]; if (arrayObject == null) { return baseObject; } else { object[] arraybaseObject = new object[arrayObject.Length]; for (int i = 0; i < arrayObject.Length; i++) { arraybaseObject[i] = GetBaseObject(arrayObject[i]); } return arraybaseObject; } } } /// /// Retrieve the reference object or reference array object. /// The returned object has to be either CimInstance or CImInstance[] type, /// if not thrown exception. /// /// /// Output the cimtype of the value, either Reference or ReferenceArray. /// protected object GetReferenceOrReferenceArrayObject(object value, ref CimType referenceType) { PSReference cimReference = value as PSReference; if (cimReference != null) { object baseObject = GetBaseObject(cimReference.Value); CimInstance cimInstance = baseObject as CimInstance; if (cimInstance == null) { return null; } referenceType = CimType.Reference; return cimInstance; } else { object[] cimReferenceArray = value as object[]; if (cimReferenceArray == null) { return null; } else if (!(cimReferenceArray[0] is PSReference)) { return null; } CimInstance[] cimInstanceArray = new CimInstance[cimReferenceArray.Length]; for (int i = 0; i < cimReferenceArray.Length; i++) { PSReference tempCimReference = cimReferenceArray[i] as PSReference; if (tempCimReference == null) { return null; } object baseObject = GetBaseObject(tempCimReference.Value); cimInstanceArray[i] = baseObject as CimInstance; if (cimInstanceArray[i] == null) { return null; } } referenceType = CimType.ReferenceArray; return cimInstanceArray; } } #endregion #region IDisposable /// /// /// Indicates whether this object was disposed or not /// /// protected bool Disposed { get { return Interlocked.Read(ref this._disposed) == 1; } } private long _disposed; /// /// /// Dispose() calls Dispose(true). /// Implement IDisposable. Do not make this method virtual. /// A derived class should not be able to override this method. /// /// public void Dispose() { Dispose(true); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } /// /// /// Dispose(bool disposing) executes in two distinct scenarios. /// If disposing equals true, the method has been called directly /// or indirectly by a user's code. Managed and unmanaged resources /// can be disposed. /// If disposing equals false, the method has been called by the /// runtime from inside the finalizer and you should not reference /// other objects. Only unmanaged resources can be disposed. /// /// /// Whether it is directly called. protected virtual void Dispose(bool disposing) { if (Interlocked.CompareExchange(ref this._disposed, 1, 0) == 0) { if (disposing) { // free managed resources Cleanup(); } // free native resources if there are any } } /// /// /// Clean up managed resources /// /// private void Cleanup() { DebugHelper.WriteLogEx(); // unblock thread that waiting for more actions this.moreActionEvent.Set(); CimBaseAction action; while (GetActionAndRemove(out action)) { DebugHelper.WriteLog("Action {0}", 2, action); if (action is CimSyncAction) { // unblock the thread waiting for response (action as CimSyncAction).OnComplete(); } } if (this.cimSessionProxyCache != null) { List temporaryProxy; lock (this.cimSessionProxyCache) { temporaryProxy = new List(this.cimSessionProxyCache); this.cimSessionProxyCache.Clear(); } // clean up all proxy objects foreach (CimSessionProxy proxy in temporaryProxy) { DebugHelper.WriteLog("Dispose proxy ", 2); proxy.Dispose(); } } this.moreActionEvent.Dispose(); if (this.ackedEvent != null) { this.ackedEvent.Dispose(); } DebugHelper.WriteLog("Cleanup complete.", 2); } #endregion #region private members /// /// Lock object. /// private readonly object a_lock = new object(); /// /// Number of active operations. /// private uint operationCount; /// /// Event to notify ps thread that more action is available. /// private ManualResetEventSlim moreActionEvent; /// /// The following is the definition of action queue. /// The queue holding all actions to be executed in the context of either /// ProcessRecord or EndProcessing. /// private ConcurrentQueue actionQueue; /// /// Lock object. /// private readonly object cimSessionProxyCacheLock = new object(); /// /// Cache all objects related to /// the current operation. /// private List cimSessionProxyCache; #endregion #region protected members /// /// Event to notify ps thread that either a ACK message sent back /// or a error happened. Currently only used by class /// . /// protected ManualResetEventSlim ackedEvent; #endregion #region const strings internal const string ComputerNameArgument = @"ComputerName"; internal const string CimSessionArgument = @"CimSession"; #endregion } }