mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
2176 lines
94 KiB
C#
2176 lines
94 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.Serialization;
|
|
using System.Text;
|
|
using System.ComponentModel;
|
|
using System.Management.Automation.Tracing;
|
|
using System.Management.Automation.Language;
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
// Stops compiler from warning about unknown warnings
|
|
#pragma warning disable 1634, 1691
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
#region PowerShell v3 Job Extensions
|
|
|
|
/// <summary>
|
|
/// New base class for a job that provides extended state
|
|
/// management functionality on the job. Since the existing
|
|
/// Job class is an abstract class and there are existing
|
|
/// implementations of the same, it is required to have a
|
|
/// new class that will have the extended functionality. This
|
|
/// is to ensure that backwards compatibility is maintained
|
|
///
|
|
/// However, this class will derive from the existing Job
|
|
/// class. The option of deprecating the existing class was
|
|
/// considered as well. In order to maintain backwards
|
|
/// compatibility of PowerShell job cmdlets they will have
|
|
/// to work with the old interface and hence deprecating
|
|
/// the Job class did not add any benefit rather than
|
|
/// deriving from the same
|
|
/// </summary>
|
|
/// <remarks>The following are some of the notes about
|
|
/// why the asynchronous operations are provided this way
|
|
/// in this class. There are two possible options in which
|
|
/// asynchronous support can be provided:
|
|
/// 1. Classical pattern (Begin and End)
|
|
/// 2. Event based pattern
|
|
///
|
|
/// Although the PowerShell API uses the classical pattern
|
|
/// and we would like the Job API and PowerShell API to be
|
|
/// as close as possible, the classical pattern is inherently
|
|
/// complex to use.</remarks>
|
|
public abstract class Job2 : Job
|
|
{
|
|
#region Private Members
|
|
|
|
/// <summary>
|
|
/// These are the parameters that can be used by a job
|
|
/// implementation when they want to specify parameters
|
|
/// to start a job
|
|
/// </summary>
|
|
private List<CommandParameterCollection> _parameters;
|
|
|
|
/// <summary>
|
|
/// Object that will be used for thread synchronization
|
|
/// </summary>
|
|
private readonly object _syncobject = new object();
|
|
|
|
private const int StartJobOperation = 1;
|
|
private const int StopJobOperation = 2;
|
|
private const int SuspendJobOperation = 3;
|
|
private const int ResumeJobOperation = 4;
|
|
private const int UnblockJobOperation = 5;
|
|
|
|
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
|
|
|
|
#endregion Private Members
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Parameters to be used to start a job.
|
|
/// This is a property because CommandParameterCollection
|
|
/// does not have a public constructor. Hence the
|
|
/// infrastructure creates an instance and provides
|
|
/// it for the implementations to use
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
|
[SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
|
|
public List<CommandParameterCollection> StartParameters
|
|
{
|
|
get
|
|
{
|
|
if (_parameters == null)
|
|
{
|
|
lock (_syncobject)
|
|
{
|
|
if (_parameters == null)
|
|
_parameters = new List<CommandParameterCollection>();
|
|
}
|
|
}
|
|
|
|
return _parameters;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("value");
|
|
}
|
|
|
|
lock (_syncobject)
|
|
{
|
|
_parameters = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
protected object SyncRoot
|
|
{
|
|
get { return syncObject; }
|
|
}
|
|
|
|
#endregion Properties
|
|
|
|
#region Protected Methods
|
|
|
|
/// <summary>
|
|
/// Default no argument constructor
|
|
/// </summary>
|
|
protected Job2() : base() { }
|
|
|
|
/// <summary>
|
|
/// Constructor which will initialize the job
|
|
/// with the associated command string
|
|
/// </summary>
|
|
/// <param name="command">string representation
|
|
/// of the command the job is running</param>
|
|
protected Job2(string command) : base(command) { }
|
|
|
|
/// <summary>
|
|
/// Creates an instance of this class
|
|
/// </summary>
|
|
/// <param name="command">Command invoked by this job object</param>
|
|
/// <param name="name">Friendly name for the job object</param>
|
|
protected Job2(string command, string name)
|
|
: base(command, name)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of this class
|
|
/// </summary>
|
|
/// <param name="command">Command invoked by this job object</param>
|
|
/// <param name="name">Friendly name for the job object</param>
|
|
/// <param name="childJobs">Child jobs of this job object</param>
|
|
protected Job2(string command, string name, IList<Job> childJobs)
|
|
: base(command, name, childJobs)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of this class
|
|
/// </summary>
|
|
/// <param name="command">Command invoked by this job object</param>
|
|
/// <param name="name">Friendly name for the job object</param>
|
|
/// <param name="token">JobIdentifier token used to assign Id and InstanceId</param>
|
|
protected Job2(string command, string name, JobIdentifier token)
|
|
: base(command, name, token)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of this class
|
|
/// </summary>
|
|
/// <param name="command">Command string</param>
|
|
/// <param name="name">Friendly name for the job</param>
|
|
/// <param name="instanceId">Instance ID to allow job identification across sessions.</param>
|
|
protected Job2(string command, string name, Guid instanceId)
|
|
: base(command, name, instanceId)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// There is an internal method in Job which is not made
|
|
/// public. In order to make this available to someone
|
|
/// implementing a job it has to be added here. If the
|
|
/// original method is made public it has changes of
|
|
/// colliding with some implementation which may have
|
|
/// added that method
|
|
/// </summary>
|
|
/// <param name="state">state of the job</param>
|
|
/// <param name="reason">exception associated with the
|
|
/// job entering this state</param>
|
|
protected new void SetJobState(JobState state, Exception reason)
|
|
{
|
|
base.SetJobState(state, reason);
|
|
}
|
|
|
|
#endregion Protected Methods
|
|
|
|
#region State Management
|
|
|
|
/// <summary>
|
|
/// start a job. The job will be started with the parameters
|
|
/// specified in StartParameters
|
|
/// </summary>
|
|
/// <remarks>It is redundant to have a method named StartJob
|
|
/// on a job class. However, this is done so as to avoid
|
|
/// an FxCop violation "CA1716:IdentifiersShouldNotMatchKeywords"
|
|
/// Stop and Resume are reserved keyworks in C# and hence cannot
|
|
/// be used as method names. Therefore to be consistent it has
|
|
/// been decided to use *Job in the name of the methods</remarks>
|
|
public abstract void StartJob();
|
|
|
|
/// <summary>
|
|
/// Start a job asynchronously
|
|
/// </summary>
|
|
public abstract void StartJobAsync();
|
|
|
|
/// <summary>
|
|
/// Event to be raise when the start job activity is completed.
|
|
/// This event should not be raised for
|
|
/// synchronous operation
|
|
/// </summary>
|
|
public event EventHandler<AsyncCompletedEventArgs> StartJobCompleted;
|
|
|
|
/// <summary>
|
|
/// Method which can be extended or called by derived
|
|
/// classes to raise the event when start of
|
|
/// the job is completed
|
|
/// </summary>
|
|
/// <param name="eventArgs">arguments describing
|
|
/// an exception that is associated with the event</param>
|
|
protected virtual void OnStartJobCompleted(AsyncCompletedEventArgs eventArgs)
|
|
{
|
|
RaiseCompletedHandler(StartJobOperation, eventArgs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method which can be extended or called by derived
|
|
/// classes to raise the event when stopping a
|
|
/// job is completed
|
|
/// </summary>
|
|
/// <param name="eventArgs">argument describing
|
|
/// an exception that is associated with the event</param>
|
|
protected virtual void OnStopJobCompleted(AsyncCompletedEventArgs eventArgs)
|
|
{
|
|
RaiseCompletedHandler(StopJobOperation, eventArgs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method which can be extended or called by derived
|
|
/// classes to raise the event when suspending a
|
|
/// job is completed
|
|
/// </summary>
|
|
/// <param name="eventArgs">argument describing
|
|
/// an exception that is associated with the event</param>
|
|
protected virtual void OnSuspendJobCompleted(AsyncCompletedEventArgs eventArgs)
|
|
{
|
|
RaiseCompletedHandler(SuspendJobOperation, eventArgs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method which can be extended or called by derived
|
|
/// classes to raise the event when resuming a
|
|
/// suspended job is completed
|
|
/// </summary>
|
|
/// <param name="eventArgs">argument describing
|
|
/// an exception that is associated with the event</param>
|
|
protected virtual void OnResumeJobCompleted(AsyncCompletedEventArgs eventArgs)
|
|
{
|
|
RaiseCompletedHandler(ResumeJobOperation, eventArgs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method which can be extended or called by derived
|
|
/// classes to raise the event when unblocking a
|
|
/// blocked job is completed
|
|
/// </summary>
|
|
/// <param name="eventArgs">argument describing
|
|
/// an exception that is associated with the event</param>
|
|
protected virtual void OnUnblockJobCompleted(AsyncCompletedEventArgs eventArgs)
|
|
{
|
|
RaiseCompletedHandler(UnblockJobOperation, eventArgs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the appropriate event based on the operation
|
|
/// and the associated event arguments
|
|
/// </summary>
|
|
/// <param name="operation">operation for which the event
|
|
/// needs to be raised</param>
|
|
/// <param name="eventArgs"></param>
|
|
private void RaiseCompletedHandler(int operation, AsyncCompletedEventArgs eventArgs)
|
|
{
|
|
// Make a temporary copy of the event to avoid possibility of
|
|
// a race condition if the last subscriber unsubscribes
|
|
// immediately after the null check and before the event is raised.
|
|
EventHandler<AsyncCompletedEventArgs> handler = null;
|
|
|
|
switch (operation)
|
|
{
|
|
case StartJobOperation:
|
|
{
|
|
handler = StartJobCompleted;
|
|
}
|
|
break;
|
|
case StopJobOperation:
|
|
{
|
|
handler = StopJobCompleted;
|
|
}
|
|
break;
|
|
case SuspendJobOperation:
|
|
{
|
|
handler = SuspendJobCompleted;
|
|
}
|
|
break;
|
|
case ResumeJobOperation:
|
|
{
|
|
handler = ResumeJobCompleted;
|
|
}
|
|
break;
|
|
case UnblockJobOperation:
|
|
{
|
|
handler = UnblockJobCompleted;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
Dbg.Assert(false, "this condition should not be hit, check the value of operation that you passed");
|
|
}
|
|
break;
|
|
}
|
|
#pragma warning disable 56500
|
|
try
|
|
{
|
|
if (handler != null)
|
|
{
|
|
handler(this, eventArgs);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
// errors in the handlers are not errors in the operation
|
|
// silently ignore them
|
|
_tracer.TraceException(exception);
|
|
}
|
|
#pragma warning restore 56500
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop a job asynchronously
|
|
/// </summary>
|
|
public abstract void StopJobAsync();
|
|
|
|
/// <summary>
|
|
/// Event to be raised when the asynchronous stopping of a job
|
|
/// is completed.This event should not be raised for
|
|
/// synchronous operation
|
|
/// </summary>
|
|
public event EventHandler<AsyncCompletedEventArgs> StopJobCompleted;
|
|
|
|
/// <summary>
|
|
/// Suspend a job
|
|
/// </summary>
|
|
public abstract void SuspendJob();
|
|
|
|
/// <summary>
|
|
/// Asynchronously suspend a job
|
|
/// </summary>
|
|
public abstract void SuspendJobAsync();
|
|
|
|
/// <summary>
|
|
/// This event should be raised whenever the asynchronous suspend of
|
|
/// a job is completed. This event should not be raised for
|
|
/// synchronous operation
|
|
/// </summary>
|
|
public event EventHandler<AsyncCompletedEventArgs> SuspendJobCompleted;
|
|
|
|
/// <summary>
|
|
/// Resume a suspended job
|
|
/// </summary>
|
|
public abstract void ResumeJob();
|
|
|
|
/// <summary>
|
|
/// Resume a suspended job asynchronously.
|
|
/// </summary>
|
|
public abstract void ResumeJobAsync();
|
|
|
|
/// <summary>
|
|
/// This event should be raised whenever the asynchronous resume of
|
|
/// a suspended job is completed. This event should not be raised for
|
|
/// synchronous operation
|
|
/// </summary>
|
|
public event EventHandler<AsyncCompletedEventArgs> ResumeJobCompleted;
|
|
|
|
/// <summary>
|
|
/// Unblock a blocked job
|
|
/// </summary>
|
|
public abstract void UnblockJob();
|
|
|
|
/// <summary>
|
|
/// Unblock a blocked job asynchronously
|
|
/// </summary>
|
|
public abstract void UnblockJobAsync();
|
|
|
|
/// <summary>
|
|
/// StopJob
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
public abstract void StopJob(bool force, string reason);
|
|
|
|
/// <summary>
|
|
/// StopJobAsync
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
public abstract void StopJobAsync(bool force, string reason);
|
|
|
|
/// <summary>
|
|
/// SuspendJob
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
public abstract void SuspendJob(bool force, string reason);
|
|
|
|
/// <summary>
|
|
/// SuspendJobAsync
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
public abstract void SuspendJobAsync(bool force, string reason);
|
|
|
|
/// <summary>
|
|
/// This event should be raised whenever the asynchronous unblock
|
|
/// of a blocked job is completed. This event should not be raised for
|
|
/// synchronous operation
|
|
/// </summary>
|
|
public event EventHandler<AsyncCompletedEventArgs> UnblockJobCompleted;
|
|
|
|
#endregion State Management
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the various thread options that can be used
|
|
/// for the ThreadBasedJob
|
|
/// </summary>
|
|
public enum JobThreadOptions
|
|
{
|
|
/// <summary>
|
|
/// Use the default behavior, which is to use a
|
|
/// ThreadPoolThread
|
|
/// </summary>
|
|
Default = 0,
|
|
|
|
/// <summary>
|
|
/// Use a thread pool thread
|
|
/// </summary>
|
|
UseThreadPoolThread = 1,
|
|
|
|
/// <summary>
|
|
/// Create a new thread everything and reuse
|
|
/// </summary>
|
|
UseNewThread = 2,
|
|
}
|
|
|
|
/*/// <summary>
|
|
/// This job will provide asynchronous behavior by running
|
|
/// the user specified script block in a separate process.
|
|
/// There will be options for running the scriptblock
|
|
/// in a new process or an existing process
|
|
/// </summary>
|
|
/// <remarks>Jobs for the out-of-process activity manager
|
|
/// can be implemented using this interface</remarks>
|
|
public abstract class ProcessBasedJob : Job2
|
|
{
|
|
public override void Start()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void StartAsync()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}*/
|
|
|
|
/// <summary>
|
|
/// Top level container job
|
|
/// </summary>
|
|
public sealed class ContainerParentJob : Job2
|
|
{
|
|
#region Private Members
|
|
|
|
private const string TraceClassName = "ContainerParentJob";
|
|
private bool _moreData = true;
|
|
private readonly object _syncObject = new object();
|
|
private int _isDisposed = 0;
|
|
private const int DisposedTrue = 1;
|
|
private const int DisposedFalse = 0;
|
|
//This variable is set to true if atleast one child job failed.
|
|
|
|
// count of number of child jobs which have finished
|
|
private int _finishedChildJobsCount = 0;
|
|
|
|
// count of number of child jobs which are blocked
|
|
private int _blockedChildJobsCount = 0;
|
|
|
|
// count of number of child jobs which are suspended
|
|
private int _suspendedChildJobsCount = 0;
|
|
|
|
// count of number of child jobs which are suspending
|
|
private int _suspendingChildJobsCount = 0;
|
|
|
|
// count of number of child jobs which failed
|
|
private int _failedChildJobsCount = 0;
|
|
|
|
// count of number of child jobs which stopped
|
|
private int _stoppedChildJobsCount = 0;
|
|
|
|
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
|
|
private readonly PSDataCollection<ErrorRecord> _executionError = new PSDataCollection<ErrorRecord>();
|
|
|
|
private PSEventManager _eventManager;
|
|
internal PSEventManager EventManager
|
|
{
|
|
get { return _eventManager; }
|
|
set
|
|
{
|
|
_tracer.WriteMessage("Setting event manager for Job ", InstanceId);
|
|
_eventManager = value;
|
|
}
|
|
}
|
|
|
|
private ManualResetEvent _jobRunning;
|
|
private ManualResetEvent JobRunning
|
|
{
|
|
get
|
|
{
|
|
if (_jobRunning == null)
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
if (_jobRunning == null)
|
|
{
|
|
// this assert is required so that a wait handle
|
|
// is not created after the object is disposed
|
|
// which will result in a leak
|
|
AssertNotDisposed();
|
|
_jobRunning = new ManualResetEvent(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _jobRunning;
|
|
}
|
|
}
|
|
|
|
private ManualResetEvent _jobSuspendedOrAborted;
|
|
private ManualResetEvent JobSuspendedOrAborted
|
|
{
|
|
get
|
|
{
|
|
if (_jobSuspendedOrAborted == null)
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
if (_jobSuspendedOrAborted == null)
|
|
{
|
|
// this assert is required so that a wait handle
|
|
// is not created after the object is disposed
|
|
// which will result in a leak
|
|
AssertNotDisposed();
|
|
_jobSuspendedOrAborted = new ManualResetEvent(false);
|
|
}
|
|
}
|
|
}
|
|
return _jobSuspendedOrAborted;
|
|
}
|
|
}
|
|
|
|
#endregion Private Members
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Create a container parent job with the
|
|
/// specified command string and name
|
|
/// </summary>
|
|
/// <param name="command">command string</param>
|
|
/// <param name="name">friendly name for display</param>
|
|
public ContainerParentJob(string command, string name)
|
|
: base(command, name)
|
|
{
|
|
StateChanged += HandleMyStateChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a container parent job with the
|
|
/// specified command string
|
|
/// </summary>
|
|
/// <param name="command">Command string</param>
|
|
public ContainerParentJob(string command)
|
|
: base(command)
|
|
{
|
|
StateChanged += HandleMyStateChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a container parent job with the
|
|
/// specified command string
|
|
/// </summary>
|
|
/// <param name="command">Command string</param>
|
|
/// <param name="name">Friendly name for the job</param>
|
|
/// <param name="jobId">JobIdentifier token that allows reuse of an Id and Instance Id</param>
|
|
public ContainerParentJob(string command, string name, JobIdentifier jobId)
|
|
: base(command, name, jobId)
|
|
{
|
|
StateChanged += HandleMyStateChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a container parent job with the
|
|
/// specified command string
|
|
/// </summary>
|
|
/// <param name="command">Command string</param>
|
|
/// <param name="name">Friendly name for the job</param>
|
|
/// <param name="instanceId">Instance ID to allow job identification across sessions.</param>
|
|
public ContainerParentJob(string command, string name, Guid instanceId)
|
|
: base(command, name, instanceId)
|
|
{
|
|
StateChanged += HandleMyStateChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a container parent job with the
|
|
/// specified command string
|
|
/// </summary>
|
|
/// <param name="command">Command string</param>
|
|
/// <param name="name">Friendly name for the job</param>
|
|
/// <param name="jobId">JobIdentifier token that allows reuse of an Id and Instance Id</param>
|
|
/// <param name="jobType">Job type name</param>
|
|
public ContainerParentJob(string command, string name, JobIdentifier jobId, string jobType)
|
|
: base(command, name, jobId)
|
|
{
|
|
PSJobTypeName = jobType;
|
|
StateChanged += HandleMyStateChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a container parent job with the
|
|
/// specified command string
|
|
/// </summary>
|
|
/// <param name="command">Command string</param>
|
|
/// <param name="name">Friendly name for the job</param>
|
|
/// <param name="instanceId">Instance ID to allow job identification across sessions.</param>
|
|
/// <param name="jobType">Job type name</param>
|
|
public ContainerParentJob(string command, string name, Guid instanceId, string jobType)
|
|
: base(command, name, instanceId)
|
|
{
|
|
PSJobTypeName = jobType;
|
|
StateChanged += HandleMyStateChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a container parent job with the specified command, name,
|
|
/// job type strings.
|
|
/// </summary>
|
|
/// <param name="command">Command string</param>
|
|
/// <param name="name">Friendly name for the job</param>
|
|
/// <param name="jobType">Job type name</param>
|
|
public ContainerParentJob(string command, string name, string jobType)
|
|
: base(command, name)
|
|
{
|
|
PSJobTypeName = jobType;
|
|
StateChanged += HandleMyStateChanged;
|
|
}
|
|
|
|
#endregion Constructors
|
|
|
|
internal PSDataCollection<ErrorRecord> ExecutionError { get { return _executionError; } }
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Add a child job to the parent job
|
|
/// </summary>
|
|
/// <param name="childJob">child job to add</param>
|
|
/// <exception cref="ObjectDisposedException">Thrown if the job is disposed.</exception>
|
|
/// <exception cref="ArgumentNullException">Thrown if child being added is null.</exception>
|
|
public void AddChildJob(Job2 childJob)
|
|
{
|
|
AssertNotDisposed();
|
|
if (childJob == null)
|
|
{
|
|
throw new ArgumentNullException("childJob");
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "AddChildJob", Guid.Empty, childJob, "Adding Child to Parent with InstanceId : ", InstanceId.ToString());
|
|
|
|
JobStateInfo childJobStateInfo;
|
|
lock (childJob.syncObject)
|
|
{
|
|
// Store job's state and subscribe to State Changed event. Locking here will
|
|
// ensure that the jobstateinfo we get is the state before any state changed events are handled by ContainerParentJob.
|
|
childJobStateInfo = childJob.JobStateInfo;
|
|
childJob.StateChanged += new EventHandler<JobStateEventArgs>(HandleChildJobStateChanged);
|
|
}
|
|
ChildJobs.Add(childJob);
|
|
ParentJobStateCalculation(new JobStateEventArgs(childJobStateInfo, new JobStateInfo(JobState.NotStarted)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// indicates if more data is available
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This has more data if any of the child jobs have more data.
|
|
/// </remarks>
|
|
public override bool HasMoreData
|
|
{
|
|
get
|
|
{
|
|
// moreData is initially set to true, and it
|
|
// will remain so until the async result
|
|
// object has completed execution.
|
|
if (_moreData && IsFinishedState(JobStateInfo.State))
|
|
{
|
|
bool atleastOneChildHasMoreData = false;
|
|
|
|
for (int i = 0; i < ChildJobs.Count; i++)
|
|
{
|
|
if (ChildJobs[i].HasMoreData)
|
|
{
|
|
atleastOneChildHasMoreData = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_moreData = atleastOneChildHasMoreData;
|
|
}
|
|
|
|
return _moreData;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Message indicating status of the job
|
|
/// </summary>
|
|
public override string StatusMessage
|
|
{
|
|
get
|
|
{
|
|
return ConstructStatusMessage();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts all jobs.
|
|
/// </summary>
|
|
/// <exception cref="ObjectDisposedException">Thrown if job is disposed.</exception>
|
|
public override void StartJob()
|
|
{
|
|
AssertNotDisposed();
|
|
_tracer.WriteMessage(TraceClassName, "StartJob", Guid.Empty, this, "Entering method", null);
|
|
s_structuredTracer.BeginContainerParentJobExecution(InstanceId);
|
|
|
|
// If parent contains no child jobs then this method will not respond. Throw error in this case.
|
|
if (ChildJobs.Count == 0)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNoChildJobs);
|
|
}
|
|
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// If there is only one child job, call the synchronous method on the child to avoid use of another thread.
|
|
// If there are multiple, we can run them in parallel using the asynchronous versions.
|
|
if (ChildJobs.Count == 1)
|
|
{
|
|
Job2 child = ChildJobs[0] as Job2;
|
|
Dbg.Assert(child != null, "Job is null after initial null check");
|
|
#pragma warning disable 56500
|
|
try
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "StartJob", Guid.Empty, this,
|
|
"Single child job synchronously, child InstanceId: {0}", child.InstanceId.ToString());
|
|
child.StartJob();
|
|
JobRunning.WaitOne();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// These exceptions are thrown by third party code. Adding them here to the collection
|
|
// of execution errors to present consistent behavior of the object.
|
|
|
|
ExecutionError.Add(new ErrorRecord(e, "ContainerParentJobStartError",
|
|
ErrorCategory.InvalidResult, child));
|
|
_tracer.WriteMessage(TraceClassName, "StartJob", Guid.Empty, this,
|
|
"Single child job threw exception, child InstanceId: {0}", child.InstanceId.ToString());
|
|
_tracer.TraceException(e);
|
|
}
|
|
#pragma warning restore 56500
|
|
return;
|
|
}
|
|
|
|
var completed = new AutoResetEvent(false);
|
|
// Count of StartJobCompleted events from children.
|
|
var startedChildJobsCount = 0;
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = (object sender, AsyncCompletedEventArgs e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null,
|
|
"StartJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "StartJob-Handler", Guid.Empty, this,
|
|
"Finished starting child job asynchronously, child InstanceId: {0}", childJob.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(
|
|
new ErrorRecord(e.Error,
|
|
"ContainerParentJobStartError",
|
|
ErrorCategory.
|
|
InvalidResult,
|
|
childJob));
|
|
_tracer.WriteMessage(TraceClassName, "StartJob-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", childJob.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref startedChildJobsCount);
|
|
if (startedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "StartJob-Handler", Guid.Empty, this,
|
|
"Finished starting all child jobs asynchronously", null);
|
|
JobRunning.WaitOne();
|
|
completed.Set();
|
|
}
|
|
};
|
|
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
job.StartJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "StartJob", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
|
|
// This child job is created to run synchronously and so can be debugged. Set
|
|
// the IJobDebugger.IsAsync accordingly.
|
|
ScriptDebugger.SetDebugJobAsync(job as IJobDebugger, false);
|
|
job.StartJobAsync();
|
|
}
|
|
completed.WaitOne();
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
job.StartJobCompleted -= eventHandler;
|
|
}
|
|
|
|
/*
|
|
if (ExecutionError.Count > 0)
|
|
{
|
|
// Check to see expected behavior if one child job fails to start.
|
|
}
|
|
|
|
if (ExecutionError.Count == 1)
|
|
{
|
|
throw ExecutionError[0];
|
|
} */
|
|
_tracer.WriteMessage(TraceClassName, "StartJob", Guid.Empty, this, "Exiting method", null);
|
|
}
|
|
|
|
private static Tracer s_structuredTracer = new Tracer();
|
|
|
|
/// <summary>
|
|
/// Starts all child jobs asynchronously.
|
|
/// When all child jobs are started, StartJobCompleted event is raised.
|
|
/// </summary>
|
|
public override void StartJobAsync()
|
|
{
|
|
if (_isDisposed == DisposedTrue)
|
|
{
|
|
OnStartJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null));
|
|
return;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "StartJobAsync", Guid.Empty, this, "Entering method", null);
|
|
s_structuredTracer.BeginContainerParentJobExecution(InstanceId);
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// Count of StartJobCompleted events from children.
|
|
var startedChildJobsCount = 0;
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
eventHandler = (sender, e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null, "StartJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "StartJobAsync-Handler", Guid.Empty, this,
|
|
"Finished starting child job asynchronously, child InstanceId: {0}", childJob.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobStartAsyncError",
|
|
ErrorCategory.InvalidResult, childJob));
|
|
_tracer.WriteMessage(TraceClassName, "StartJobAsync-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", childJob.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref startedChildJobsCount);
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
childJob.StartJobCompleted -= eventHandler;
|
|
|
|
if (startedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "StartJobAsync-Handler", Guid.Empty, this,
|
|
"Finished starting all child jobs asynchronously", null);
|
|
|
|
JobRunning.WaitOne();
|
|
// There may be multiple exceptions raised. They
|
|
// are stored in the Error stream of this job object, which is otherwise
|
|
// unused.
|
|
OnStartJobCompleted(new AsyncCompletedEventArgs(null, false, null));
|
|
}
|
|
};
|
|
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
job.StartJobCompleted += eventHandler;
|
|
|
|
_tracer.WriteMessage(TraceClassName, "StartJobAsync", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
job.StartJobAsync();
|
|
}
|
|
|
|
_tracer.WriteMessage(TraceClassName, "StartJobAsync", Guid.Empty, this, "Exiting method", null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resume all jobs.
|
|
/// </summary>
|
|
/// <exception cref="ObjectDisposedException">Thrown if job is disposed.</exception>
|
|
public override void ResumeJob()
|
|
{
|
|
AssertNotDisposed();
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob", Guid.Empty, this, "Entering method", null);
|
|
|
|
// If parent contains no child jobs then this method will not respond. Throw error in this case.
|
|
if (ChildJobs.Count == 0)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNoChildJobs);
|
|
}
|
|
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// If there is only one child job, call the synchronous method on the child to avoid use of another thread.
|
|
// If there are multiple, we can run them in parallel using the asynchronous versions.
|
|
if (ChildJobs.Count == 1)
|
|
{
|
|
Job2 child = ChildJobs[0] as Job2;
|
|
Dbg.Assert(child != null, "Job is null after initial null check");
|
|
#pragma warning disable 56500
|
|
try
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob", Guid.Empty, this,
|
|
"Single child job synchronously, child InstanceId: {0}", child.InstanceId.ToString());
|
|
child.ResumeJob();
|
|
JobRunning.WaitOne();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// These exceptions are thrown by third party code. Adding them here to the collection
|
|
// of execution errors to present consistent behavior of the object.
|
|
|
|
ExecutionError.Add(new ErrorRecord(e, "ContainerParentJobResumeError",
|
|
ErrorCategory.InvalidResult, child));
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob", Guid.Empty, this,
|
|
"Single child job threw exception, child InstanceId: {0}", child.InstanceId.ToString());
|
|
_tracer.TraceException(e);
|
|
}
|
|
#pragma warning restore 56500
|
|
return;
|
|
}
|
|
|
|
var completed = new AutoResetEvent(false);
|
|
// Count of ResumeJobCompleted events from children.
|
|
var resumedChildJobsCount = 0;
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
eventHandler = (object sender, AsyncCompletedEventArgs e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null, "ResumeJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob-Handler", Guid.Empty, this,
|
|
"Finished resuming child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobResumeError",
|
|
ErrorCategory.InvalidResult, job));
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", job.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref resumedChildJobsCount);
|
|
if (resumedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob-Handler", Guid.Empty, this,
|
|
"Finished resuming all child jobs asynchronously", null);
|
|
JobRunning.WaitOne();
|
|
completed.Set();
|
|
}
|
|
};
|
|
job.ResumeJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
job.ResumeJobAsync();
|
|
}
|
|
completed.WaitOne();
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
job.ResumeJobCompleted -= eventHandler;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJob", Guid.Empty, this, "Exiting method", null);
|
|
|
|
// Errors are taken from the Error collection by the cmdlet for ContainerParentJob.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resume all jobs asynchronously.
|
|
/// </summary>
|
|
public override void ResumeJobAsync()
|
|
{
|
|
if (_isDisposed == DisposedTrue)
|
|
{
|
|
OnResumeJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null));
|
|
return;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJobAsync", Guid.Empty, this, "Entering method", null);
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// Count of ResumeJobCompleted events from children.
|
|
var resumedChildJobsCount = 0;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
eventHandler = (sender, e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null, "ResumeJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJobAsync-Handler", Guid.Empty, this,
|
|
"Finished resuming child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobResumeAsyncError",
|
|
ErrorCategory.InvalidResult, job));
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJobAsync-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", job.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref resumedChildJobsCount);
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
childJob.ResumeJobCompleted -= eventHandler;
|
|
if (resumedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJobAsync-Handler", Guid.Empty, this,
|
|
"Finished resuming all child jobs asynchronously", null);
|
|
|
|
JobRunning.WaitOne();
|
|
// There may be multiple exceptions raised. They
|
|
// are stored in the Error stream of this job object, which is otherwise
|
|
// unused.
|
|
OnResumeJobCompleted(new AsyncCompletedEventArgs(null, false, null));
|
|
}
|
|
};
|
|
job.ResumeJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJobAsync", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
job.ResumeJobAsync();
|
|
}
|
|
|
|
_tracer.WriteMessage(TraceClassName, "ResumeJobAsync", Guid.Empty, this, "Exiting method", null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Suspends all jobs.
|
|
/// </summary>
|
|
/// <exception cref="ObjectDisposedException">Thrown if job is disposed.</exception>
|
|
public override void SuspendJob()
|
|
{
|
|
SuspendJobInternal(null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Suspends all jobs forcefully.
|
|
/// </summary>
|
|
/// <param name="force">Force flag for suspending forcefully.</param>
|
|
/// <param name="reason">Reason for doing forceful suspend.</param>
|
|
public override void SuspendJob(bool force, string reason)
|
|
{
|
|
SuspendJobInternal(force, reason);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Suspends all jobs asynchronously.
|
|
/// When all jobs have been suspended, SuspendJobCompleted is raised.
|
|
/// </summary>
|
|
public override void SuspendJobAsync()
|
|
{
|
|
SuspendJobAsyncInternal(null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Suspends all jobs asynchronously with force flag.
|
|
/// When all jobs have been suspended, SuspendJobCompleted is raised.
|
|
/// </summary>
|
|
/// <param name="force">Force flag for suspending forcefully.</param>
|
|
/// <param name="reason">Reason for doing forceful suspend.</param>
|
|
public override void SuspendJobAsync(bool force, string reason)
|
|
{
|
|
SuspendJobAsyncInternal(force, reason);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop all child jobs.
|
|
/// </summary>
|
|
public override void StopJob()
|
|
{
|
|
StopJobInternal(null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops all child jobs asynchronously.
|
|
/// Once all child jobs are stopped, StopJobCompleted event is raised.
|
|
/// </summary>
|
|
public override void StopJobAsync()
|
|
{
|
|
StopJobAsyncInternal(null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// StopJob
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
public override void StopJob(bool force, string reason)
|
|
{
|
|
StopJobInternal(force, reason);
|
|
}
|
|
|
|
/// <summary>
|
|
/// StopJobAsync
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
public override void StopJobAsync(bool force, string reason)
|
|
{
|
|
StopJobAsyncInternal(force, reason);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unblock all child jobs.
|
|
/// </summary>
|
|
/// <exception cref="ObjectDisposedException">Thrown if job is disposed.</exception>
|
|
public override void UnblockJob()
|
|
{
|
|
AssertNotDisposed();
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob", Guid.Empty, this, "Entering method", null);
|
|
|
|
// If parent contains no child jobs then this method will not respond. Throw error in this case.
|
|
if (ChildJobs.Count == 0)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNoChildJobs);
|
|
}
|
|
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// If there is only one child job, call the synchronous method on the child to avoid use of another thread.
|
|
// If there are multiple, we can run them in parallel using the asynchronous versions.
|
|
if (ChildJobs.Count == 1)
|
|
{
|
|
Job2 child = ChildJobs[0] as Job2;
|
|
Dbg.Assert(child != null, "Job is null after initial null check");
|
|
#pragma warning disable 56500
|
|
try
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob", Guid.Empty, this,
|
|
"Single child job synchronously, child InstanceId: {0}", child.InstanceId.ToString());
|
|
child.UnblockJob();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// These exceptions are thrown by third party code. Adding them here to the collection
|
|
// of execution errors to present consistent behavior of the object.
|
|
|
|
ExecutionError.Add(new ErrorRecord(e, "ContainerParentJobUnblockError",
|
|
ErrorCategory.InvalidResult, child));
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob", Guid.Empty, this,
|
|
"Single child job threw exception, child InstanceId: {0}", child.InstanceId.ToString());
|
|
_tracer.TraceException(e);
|
|
}
|
|
#pragma warning restore 56500
|
|
return;
|
|
}
|
|
|
|
var completed = new AutoResetEvent(false);
|
|
// count of UnblockJobCompleted events from children.
|
|
int unblockedChildJobsCount = 0;
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
eventHandler = (object sender, AsyncCompletedEventArgs e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null, "UnblockJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob-Handler", Guid.Empty, this,
|
|
"Finished unblock child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobUnblockError",
|
|
ErrorCategory.InvalidResult, childJob));
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", job.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref unblockedChildJobsCount);
|
|
if (unblockedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob-Handler", Guid.Empty, this,
|
|
"Finished unblock all child jobs asynchronously", null);
|
|
completed.Set();
|
|
}
|
|
};
|
|
job.UnblockJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
job.UnblockJobAsync();
|
|
}
|
|
completed.WaitOne();
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
job.UnblockJobCompleted -= eventHandler;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJob", Guid.Empty, this, "Exiting method", null);
|
|
|
|
// Errors are taken from the Error collection by the cmdlet for ContainerParentJob.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unblock all child jobs asynchronously.
|
|
/// Once all child jobs are unblocked, UnblockJobCompleted event is raised.
|
|
/// </summary>
|
|
public override void UnblockJobAsync()
|
|
{
|
|
if (_isDisposed == DisposedTrue)
|
|
{
|
|
OnUnblockJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null));
|
|
return;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJobAsync", Guid.Empty, this, "Entering method", null);
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// count of UnblockJobCompleted events from children.
|
|
int unblockedChildJobsCount = 0;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
eventHandler = (sender, e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null, "UnblockJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJobAsync-Handler", Guid.Empty, this,
|
|
"Finished unblock child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobUnblockError",
|
|
ErrorCategory.InvalidResult, childJob));
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJobAsync-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", job.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref unblockedChildJobsCount);
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
childJob.UnblockJobCompleted -= eventHandler;
|
|
if (unblockedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJobAsync-Handler", Guid.Empty, this,
|
|
"Finished unblock all child jobs asynchronously", null);
|
|
|
|
// State change is handled elsewhere.
|
|
// There may be multiple exceptions raised. They
|
|
// are stored in the Error stream of this job object, which is otherwise
|
|
// unused.
|
|
OnUnblockJobCompleted(new AsyncCompletedEventArgs(null, false, null));
|
|
}
|
|
};
|
|
job.UnblockJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJobAsync", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
job.UnblockJobAsync();
|
|
}
|
|
|
|
_tracer.WriteMessage(TraceClassName, "UnblockJobAsync", Guid.Empty, this, "Exiting method", null);
|
|
}
|
|
|
|
#endregion Public Methods
|
|
|
|
#region finish logic
|
|
|
|
/// <summary>
|
|
/// Internal synchronous SuspendJob, calls appropriate version if Force is specified.
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
private void SuspendJobInternal(bool? force, string reason)
|
|
{
|
|
AssertNotDisposed();
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob", Guid.Empty, this, "Entering method", null);
|
|
|
|
// If parent contains no child jobs then this method will not respond. Throw error in this case.
|
|
if (ChildJobs.Count == 0)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNoChildJobs);
|
|
}
|
|
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// If there is only one child job, call the synchronous method on the child to avoid use of another thread.
|
|
// If there are multiple, we can run them in parallel using the asynchronous versions.
|
|
if (ChildJobs.Count == 1)
|
|
{
|
|
Job2 child = ChildJobs[0] as Job2;
|
|
Dbg.Assert(child != null, "Job is null after initial null check");
|
|
#pragma warning disable 56500
|
|
try
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob", Guid.Empty, this,
|
|
"Single child job synchronously, child InstanceId: {0} force: {1}", child.InstanceId.ToString(), force.ToString());
|
|
if (force.HasValue)
|
|
child.SuspendJob(force.Value, reason);
|
|
else
|
|
child.SuspendJob();
|
|
JobSuspendedOrAborted.WaitOne();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// These exceptions are thrown by third party code. Adding them here to the collection
|
|
// of execution errors to present consistent behavior of the object.
|
|
|
|
ExecutionError.Add(new ErrorRecord(e, "ContainerParentJobSuspendError",
|
|
ErrorCategory.InvalidResult, child));
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob", Guid.Empty, this,
|
|
"Single child job threw exception, child InstanceId: {0} force: {1}", child.InstanceId.ToString(), force.ToString());
|
|
_tracer.TraceException(e);
|
|
}
|
|
#pragma warning restore 56500
|
|
return;
|
|
}
|
|
|
|
AutoResetEvent completed = new AutoResetEvent(false);
|
|
var suspendedChildJobsCount = 0;
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
eventHandler = (object sender, AsyncCompletedEventArgs e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null,
|
|
"SuspendJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob-Handler", Guid.Empty, this,
|
|
"Finished suspending child job asynchronously, child InstanceId: {0} force: {1}", job.InstanceId.ToString(), force.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobSuspendError",
|
|
ErrorCategory.InvalidResult, job));
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0} force: {1}", job.InstanceId.ToString(), force.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref suspendedChildJobsCount);
|
|
if (suspendedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob-Handler", Guid.Empty, this,
|
|
"Finished suspending all child jobs asynchronously", null);
|
|
JobSuspendedOrAborted.WaitOne();
|
|
completed.Set();
|
|
}
|
|
};
|
|
job.SuspendJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0} force: {1}", job.InstanceId.ToString(), force.ToString());
|
|
if (force.HasValue)
|
|
job.SuspendJobAsync(force.Value, reason);
|
|
else
|
|
job.SuspendJobAsync();
|
|
}
|
|
completed.WaitOne();
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
job.SuspendJobCompleted -= eventHandler;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJob", Guid.Empty, this, "Exiting method", null);
|
|
|
|
// Errors are taken from the Error collection by the cmdlet for ContainerParentJob.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal SuspendJobAsync. Calls appropriate method if Force is specified
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
private void SuspendJobAsyncInternal(bool? force, string reason)
|
|
{
|
|
if (_isDisposed == DisposedTrue)
|
|
{
|
|
OnSuspendJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null));
|
|
return;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJobAsync", Guid.Empty, this, "Entering method", null);
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// Count of SuspendJobCompleted events from children.
|
|
var suspendedChildJobsCount = 0;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
eventHandler = (sender, e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null, "SuspendJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJobAsync-Handler", Guid.Empty, this,
|
|
"Finished suspending child job asynchronously, child InstanceId: {0} force: {1}", job.InstanceId.ToString(), force.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobSuspendAsyncError",
|
|
ErrorCategory.InvalidResult, job));
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJobAsync-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0} force: {1}", job.InstanceId.ToString(), force.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref suspendedChildJobsCount);
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
childJob.SuspendJobCompleted -= eventHandler;
|
|
if (suspendedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJobAsync-Handler", Guid.Empty, this,
|
|
"Finished suspending all child jobs asynchronously", null);
|
|
|
|
JobSuspendedOrAborted.WaitOne();
|
|
// There may be multiple exceptions raised. They
|
|
// are stored in the Error stream of this job object, which is otherwise
|
|
// unused.
|
|
OnSuspendJobCompleted(new AsyncCompletedEventArgs(null, false, null));
|
|
}
|
|
};
|
|
job.SuspendJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJobAsync", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0} force: {1}", job.InstanceId.ToString(), force.ToString());
|
|
if (force.HasValue)
|
|
job.SuspendJobAsync(force.Value, reason);
|
|
else
|
|
job.SuspendJobAsync();
|
|
}
|
|
|
|
_tracer.WriteMessage(TraceClassName, "SuspendJobAsync", Guid.Empty, this, "Exiting method", null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// StopJob
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
private void StopJobInternal(bool? force, string reason)
|
|
{
|
|
AssertNotDisposed();
|
|
_tracer.WriteMessage(TraceClassName, "StopJob", Guid.Empty, this, "Entering method", null);
|
|
|
|
// If parent contains no child jobs then this method will not respond. Throw error in this case.
|
|
if (ChildJobs.Count == 0)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNoChildJobs);
|
|
}
|
|
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// If there is only one child job, call the synchronous method on the child to avoid use of another thread.
|
|
// If there are multiple, we can run them in parallel using the asynchronous versions.
|
|
if (ChildJobs.Count == 1)
|
|
{
|
|
Job2 child = ChildJobs[0] as Job2;
|
|
Dbg.Assert(child != null, "Job is null after initial null check");
|
|
#pragma warning disable 56500
|
|
try
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "StopJob", Guid.Empty, this,
|
|
"Single child job synchronously, child InstanceId: {0}", child.InstanceId.ToString());
|
|
if (force.HasValue)
|
|
child.StopJob(force.Value, reason);
|
|
else
|
|
child.StopJob();
|
|
Finished.WaitOne();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// These exceptions are thrown by third party code. Adding them here to the collection
|
|
// of execution errors to present consistent behavior of the object.
|
|
|
|
ExecutionError.Add(new ErrorRecord(e, "ContainerParentJobStopError",
|
|
ErrorCategory.InvalidResult, child));
|
|
_tracer.WriteMessage(TraceClassName, "StopJob", Guid.Empty, this,
|
|
"Single child job threw exception, child InstanceId: {0}", child.InstanceId.ToString());
|
|
_tracer.TraceException(e);
|
|
}
|
|
#pragma warning restore 56500
|
|
return;
|
|
}
|
|
|
|
AutoResetEvent completed = new AutoResetEvent(false);
|
|
// Count of StopJobCompleted events from children.
|
|
var stoppedChildJobsCount = 0;
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
eventHandler = (object sender, AsyncCompletedEventArgs e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null,
|
|
"StopJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "StopJob-Handler", Guid.Empty, this,
|
|
"Finished stopping child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobStopError",
|
|
ErrorCategory.InvalidResult, job));
|
|
_tracer.WriteMessage(TraceClassName, "StopJob-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", job.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref stoppedChildJobsCount);
|
|
if (stoppedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "StopJob-Handler", Guid.Empty, this,
|
|
"Finished stopping all child jobs asynchronously", null);
|
|
Finished.WaitOne();
|
|
completed.Set();
|
|
}
|
|
};
|
|
job.StopJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "StopJob", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (force.HasValue)
|
|
job.StopJobAsync(force.Value, reason);
|
|
else
|
|
job.StopJobAsync();
|
|
}
|
|
completed.WaitOne();
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
job.StopJobCompleted -= eventHandler;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "StopJob", Guid.Empty, this, "Exiting method", null);
|
|
|
|
// Errors are taken from the Error collection by the cmdlet for ContainerParentJob.
|
|
}
|
|
|
|
/// <summary>
|
|
/// StopJobAsync
|
|
/// </summary>
|
|
/// <param name="force"></param>
|
|
/// <param name="reason"></param>
|
|
private void StopJobAsyncInternal(bool? force, string reason)
|
|
{
|
|
if (_isDisposed == DisposedTrue)
|
|
{
|
|
OnStopJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null));
|
|
return;
|
|
}
|
|
_tracer.WriteMessage(TraceClassName, "StopJobAsync", Guid.Empty, this, "Entering method", null);
|
|
|
|
foreach (Job2 job in this.ChildJobs)
|
|
{
|
|
if (job == null) throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.JobActionInvalidWithNullChild);
|
|
}
|
|
|
|
// count of StopJobCompleted events from children.
|
|
int stoppedChildJobsCount = 0;
|
|
foreach (Job2 job in ChildJobs)
|
|
{
|
|
Dbg.Assert(job != null, "Job is null after initial null check");
|
|
|
|
EventHandler<AsyncCompletedEventArgs> eventHandler = null;
|
|
eventHandler = (sender, e) =>
|
|
{
|
|
var childJob = sender as Job2;
|
|
Dbg.Assert(childJob != null, "StopJobCompleted only available on Job2");
|
|
_tracer.WriteMessage(TraceClassName, "StopJobAsync-Handler", Guid.Empty, this,
|
|
"Finished stopping child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (e.Error != null)
|
|
{
|
|
ExecutionError.Add(new ErrorRecord(e.Error, "ContainerParentJobStopAsyncError",
|
|
ErrorCategory.InvalidResult, childJob));
|
|
_tracer.WriteMessage(TraceClassName, "StopJobAsync-Handler", Guid.Empty, this,
|
|
"Child job asynchronously had error, child InstanceId: {0}", job.InstanceId.ToString());
|
|
_tracer.TraceException(e.Error);
|
|
}
|
|
|
|
Interlocked.Increment(ref stoppedChildJobsCount);
|
|
Dbg.Assert(eventHandler != null, "Event handler magically disappeared");
|
|
childJob.StopJobCompleted -= eventHandler;
|
|
if (stoppedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "StopJobAsync-Handler", Guid.Empty, this,
|
|
"Finished stopping all child jobs asynchronously", null);
|
|
|
|
Finished.WaitOne();
|
|
// There may be multiple exceptions raised. They
|
|
// are stored in the Error stream of this job object, which is otherwise
|
|
// unused.
|
|
OnStopJobCompleted(new AsyncCompletedEventArgs(null, false, null));
|
|
}
|
|
};
|
|
job.StopJobCompleted += eventHandler;
|
|
_tracer.WriteMessage(TraceClassName, "StopJobAsync", Guid.Empty, this,
|
|
"Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString());
|
|
if (force.HasValue)
|
|
job.StopJobAsync(force.Value, reason);
|
|
else
|
|
job.StopJobAsync();
|
|
}
|
|
|
|
_tracer.WriteMessage(TraceClassName, "StopJobAsync", Guid.Empty, this, "Exiting method", null);
|
|
}
|
|
|
|
private void HandleMyStateChanged(object sender, JobStateEventArgs e)
|
|
{
|
|
_tracer.WriteMessage(TraceClassName, "HandleMyStateChanged", Guid.Empty, this,
|
|
"NewState: {0}; OldState: {1}", e.JobStateInfo.State.ToString(),
|
|
e.PreviousJobStateInfo.State.ToString());
|
|
|
|
switch (e.JobStateInfo.State)
|
|
{
|
|
case JobState.Running:
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
JobRunning.Set();
|
|
|
|
// Do not create the event if it doesn't already exist. Suspend may never be called.
|
|
if (_jobSuspendedOrAborted != null)
|
|
JobSuspendedOrAborted.Reset();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JobState.Suspended:
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
JobSuspendedOrAborted.Set();
|
|
JobRunning.Reset();
|
|
}
|
|
}
|
|
break;
|
|
case JobState.Failed:
|
|
case JobState.Completed:
|
|
case JobState.Stopped:
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
JobSuspendedOrAborted.Set();
|
|
|
|
// Do not reset JobRunning when the state is terminal.
|
|
// No thread should wait on a job transitioning again to
|
|
// JobState.Running.
|
|
JobRunning.Set();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the StateChanged event from each of the child job objects
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void HandleChildJobStateChanged(object sender, JobStateEventArgs e)
|
|
{
|
|
ParentJobStateCalculation(e);
|
|
}
|
|
|
|
private void ParentJobStateCalculation(JobStateEventArgs e)
|
|
{
|
|
JobState computedState;
|
|
if (ComputeJobStateFromChildJobStates("ContainerParentJob", e, ref _blockedChildJobsCount, ref _suspendedChildJobsCount, ref _suspendingChildJobsCount,
|
|
ref _finishedChildJobsCount, ref _failedChildJobsCount, ref _stoppedChildJobsCount, ChildJobs.Count, out computedState))
|
|
{
|
|
if (computedState != JobStateInfo.State)
|
|
{
|
|
if (JobStateInfo.State == JobState.NotStarted && computedState == JobState.Running)
|
|
{
|
|
PSBeginTime = DateTime.Now;
|
|
}
|
|
if (!IsFinishedState(JobStateInfo.State) && IsPersistentState(computedState))
|
|
{
|
|
PSEndTime = DateTime.Now;
|
|
}
|
|
|
|
SetJobState(computedState);
|
|
}
|
|
|
|
if (_finishedChildJobsCount == ChildJobs.Count)
|
|
{
|
|
s_structuredTracer.EndContainerParentJobExecution(InstanceId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the StateChanged event from each of the child job objects
|
|
/// </summary>
|
|
/// <param name="traceClassName"></param>
|
|
/// <param name="e"></param>
|
|
/// <param name="blockedChildJobsCount"></param>
|
|
/// <param name="suspendedChildJobsCount"></param>
|
|
/// <param name="suspendingChildJobsCount"></param>
|
|
/// <param name="finishedChildJobsCount"></param>
|
|
/// <param name="stoppedChildJobsCount"></param>
|
|
/// <param name="childJobsCount"></param>
|
|
/// <param name="computedJobState"></param>
|
|
/// <param name="failedChildJobsCount"></param>
|
|
/// <returns>true if the job state needs to be modified, false otherwise</returns>
|
|
internal static bool ComputeJobStateFromChildJobStates(string traceClassName, JobStateEventArgs e,
|
|
ref int blockedChildJobsCount, ref int suspendedChildJobsCount, ref int suspendingChildJobsCount, ref int finishedChildJobsCount,
|
|
ref int failedChildJobsCount, ref int stoppedChildJobsCount, int childJobsCount,
|
|
out JobState computedJobState)
|
|
{
|
|
computedJobState = JobState.NotStarted;
|
|
|
|
using (PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource())
|
|
{
|
|
if (e.JobStateInfo.State == JobState.Blocked)
|
|
{
|
|
// increment count of blocked child jobs
|
|
Interlocked.Increment(ref blockedChildJobsCount);
|
|
|
|
// if any of the child job is blocked, we set state to blocked
|
|
tracer.WriteMessage(traceClassName, ": JobState is Blocked, at least one child job is blocked.");
|
|
computedJobState = JobState.Blocked;
|
|
return true;
|
|
}
|
|
|
|
if (e.PreviousJobStateInfo.State == JobState.Blocked)
|
|
{
|
|
// check if any of the child jobs were unblocked
|
|
// in which case we need to check if the parent
|
|
// job needs to be unblocked as well
|
|
Interlocked.Decrement(ref blockedChildJobsCount);
|
|
|
|
if (blockedChildJobsCount == 0)
|
|
{
|
|
tracer.WriteMessage(traceClassName, ": JobState is unblocked, all child jobs are unblocked.");
|
|
computedJobState = JobState.Running;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (e.PreviousJobStateInfo.State == JobState.Suspended)
|
|
{
|
|
// decrement count of suspended child jobs
|
|
// needed to determine when all incomplete child jobs are suspended for parent job state.
|
|
Interlocked.Decrement(ref suspendedChildJobsCount);
|
|
}
|
|
|
|
if (e.PreviousJobStateInfo.State == JobState.Suspending)
|
|
{
|
|
// decrement count of suspending child jobs
|
|
// needed to determine when all incomplete child jobs are suspended for parent job state.
|
|
Interlocked.Decrement(ref suspendingChildJobsCount);
|
|
}
|
|
|
|
if (e.JobStateInfo.State == JobState.Suspended)
|
|
{
|
|
// increment count of suspended child jobs.
|
|
Interlocked.Increment(ref suspendedChildJobsCount);
|
|
|
|
// We know that at least one child is suspended. If all jobs are either complete or suspended, set the state.
|
|
if (suspendedChildJobsCount + finishedChildJobsCount == childJobsCount)
|
|
{
|
|
tracer.WriteMessage(traceClassName, ": JobState is suspended, all child jobs are suspended.");
|
|
computedJobState = JobState.Suspended;
|
|
return true;
|
|
}
|
|
|
|
// Job state should continue to be running unless:
|
|
// at least one child is suspended
|
|
// AND
|
|
// all child jobs are either suspended or finished.
|
|
return false;
|
|
}
|
|
|
|
if (e.JobStateInfo.State == JobState.Suspending)
|
|
{
|
|
// increment count of suspending child jobs.
|
|
Interlocked.Increment(ref suspendingChildJobsCount);
|
|
|
|
// We know that at least one child is suspended. If all jobs are either complete or suspended, set the state.
|
|
if (suspendedChildJobsCount + finishedChildJobsCount + suspendingChildJobsCount == childJobsCount)
|
|
{
|
|
tracer.WriteMessage(traceClassName, ": JobState is suspending, all child jobs are in suspending state.");
|
|
computedJobState = JobState.Suspending;
|
|
return true;
|
|
}
|
|
|
|
// Job state should continue to be running unless:
|
|
// at least one child is suspended, suspending
|
|
// AND
|
|
// all child jobs are either suspended or finished.
|
|
return false;
|
|
}
|
|
|
|
|
|
// Ignore state changes which are not resulting in state change to finished.
|
|
// State will be Running once at least one child is running.
|
|
if ((e.JobStateInfo.State != JobState.Completed && e.JobStateInfo.State != JobState.Failed) && e.JobStateInfo.State != JobState.Stopped)
|
|
{
|
|
if (e.JobStateInfo.State == JobState.Running)
|
|
{
|
|
computedJobState = JobState.Running;
|
|
return true;
|
|
}
|
|
|
|
// if the job state is Suspended, we have already returned.
|
|
// if the job state is NotStarted, do not set the state.
|
|
// if the job state is blocked, we have already returned.
|
|
return false;
|
|
}
|
|
if (e.JobStateInfo.State == JobState.Failed)
|
|
{
|
|
// If any of the child job failed, we set status to failed
|
|
// we can set it right now and
|
|
Interlocked.Increment(ref failedChildJobsCount);
|
|
}
|
|
|
|
// If stop has not been called, but a child has been stopped, the parent should
|
|
// reflect the stopped state.
|
|
if (e.JobStateInfo.State == JobState.Stopped)
|
|
{
|
|
Interlocked.Increment(ref stoppedChildJobsCount);
|
|
}
|
|
|
|
bool allChildJobsFinished = false;
|
|
|
|
int finishedChildJobsCountNew = Interlocked.Increment(ref finishedChildJobsCount);
|
|
|
|
//We are done
|
|
if (finishedChildJobsCountNew == childJobsCount)
|
|
{
|
|
allChildJobsFinished = true;
|
|
}
|
|
|
|
if (allChildJobsFinished)
|
|
{
|
|
//if any child job failed, set status to failed
|
|
//If stop was called set, status to stopped
|
|
//else completed);
|
|
if (failedChildJobsCount > 0)
|
|
{
|
|
tracer.WriteMessage(traceClassName, ": JobState is failed, at least one child job failed.");
|
|
computedJobState = JobState.Failed;
|
|
return true;
|
|
}
|
|
if (stoppedChildJobsCount > 0)
|
|
{
|
|
tracer.WriteMessage(traceClassName, ": JobState is stopped, stop is called.");
|
|
computedJobState = JobState.Stopped;
|
|
return true;
|
|
}
|
|
tracer.WriteMessage(traceClassName, ": JobState is completed.");
|
|
computedJobState = JobState.Completed;
|
|
return true;
|
|
}
|
|
|
|
// If not all jobs are finished, one child job may be suspended, even though this job did not finish.
|
|
// At this point, we know finishedChildJobsCountNew != childJobsCount
|
|
if (suspendedChildJobsCount + finishedChildJobsCountNew == childJobsCount)
|
|
{
|
|
tracer.WriteMessage(traceClassName, ": JobState is suspended, all child jobs are suspended.");
|
|
computedJobState = JobState.Suspended;
|
|
return true;
|
|
}
|
|
|
|
// If not all jobs are finished, one child job may be suspending, even though this job did not finish.
|
|
// At this point, we know finishedChildJobsCountNew != childJobsCount and finishChildJobsCount + suspendedChilJobsCout != childJobsCount
|
|
if (suspendingChildJobsCount + suspendedChildJobsCount + finishedChildJobsCountNew == childJobsCount)
|
|
{
|
|
tracer.WriteMessage(traceClassName, ": JobState is suspending, all child jobs are in suspending state.");
|
|
computedJobState = JobState.Suspending;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endregion finish logic
|
|
|
|
/// <summary>
|
|
/// Release all the resources.
|
|
/// </summary>
|
|
/// <param name="disposing">
|
|
/// if true, release all the managed objects.
|
|
/// </param>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (!disposing) return;
|
|
if (Interlocked.CompareExchange(ref _isDisposed, DisposedTrue, DisposedFalse) == DisposedTrue) return;
|
|
|
|
try
|
|
{
|
|
UnregisterAllJobEvents();
|
|
_executionError.Dispose();
|
|
StateChanged -= HandleMyStateChanged;
|
|
|
|
foreach (Job job in ChildJobs)
|
|
{
|
|
_tracer.WriteMessage("Disposing child job with id : " + job.Id);
|
|
job.Dispose();
|
|
}
|
|
|
|
if (_jobRunning != null)
|
|
_jobRunning.Dispose();
|
|
|
|
if (_jobSuspendedOrAborted != null)
|
|
_jobSuspendedOrAborted.Dispose();
|
|
}
|
|
finally
|
|
{
|
|
base.Dispose(true);
|
|
}
|
|
}
|
|
|
|
private String ConstructLocation()
|
|
{
|
|
if (ChildJobs == null || ChildJobs.Count == 0)
|
|
return string.Empty;
|
|
string location = ChildJobs.Select((job) => job.Location).Aggregate((s1, s2) => s1 + ',' + s2);
|
|
return location;
|
|
}
|
|
|
|
private String ConstructStatusMessage()
|
|
{
|
|
if (ChildJobs == null || ChildJobs.Count == 0)
|
|
return string.Empty;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int i = 0; i < ChildJobs.Count; i++)
|
|
{
|
|
if (!String.IsNullOrEmpty(ChildJobs[i].StatusMessage))
|
|
{
|
|
sb.Append(ChildJobs[i].StatusMessage);
|
|
}
|
|
|
|
if (i < (ChildJobs.Count - 1))
|
|
{
|
|
sb.Append(",");
|
|
}
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computers on which this job is running
|
|
/// </summary>
|
|
public override string Location
|
|
{
|
|
get
|
|
{
|
|
return ConstructLocation();
|
|
}
|
|
}
|
|
|
|
private void UnregisterJobEvent(Job job)
|
|
{
|
|
string sourceIdentifier = job.InstanceId + ":StateChanged";
|
|
|
|
_tracer.WriteMessage("Unregistering StateChanged event for job ", job.InstanceId);
|
|
foreach (PSEventSubscriber subscriber in
|
|
EventManager.Subscribers.Where(subscriber => String.Equals(subscriber.SourceIdentifier, sourceIdentifier, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
EventManager.UnsubscribeEvent(subscriber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void UnregisterAllJobEvents()
|
|
{
|
|
if (EventManager == null)
|
|
{
|
|
_tracer.WriteMessage("No events subscribed, skipping event unregistrations");
|
|
return;
|
|
}
|
|
|
|
foreach (var job in ChildJobs)
|
|
{
|
|
UnregisterJobEvent(job);
|
|
}
|
|
|
|
UnregisterJobEvent(this);
|
|
_tracer.WriteMessage("Setting event manager to null");
|
|
EventManager = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Container exception for jobs that can map errors and exceptions
|
|
/// to specific lines in their input.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class JobFailedException : SystemException
|
|
{
|
|
/// <summary>
|
|
/// Creates a new JobFailedException
|
|
/// </summary>
|
|
public JobFailedException()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new JobFailedException
|
|
/// </summary>
|
|
/// <param name="message">The message of the exception</param>
|
|
public JobFailedException(string message)
|
|
: base(message)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new JobFailedException
|
|
/// </summary>
|
|
/// <param name="message">The message of the exception</param>
|
|
/// <param name="innerException">The actual exception that caused this error.</param>
|
|
public JobFailedException(string message, Exception innerException)
|
|
: base(message, innerException)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new JobFailedException
|
|
/// </summary>
|
|
/// <param name="innerException">The actual exception that caused this error.</param>
|
|
/// <param name="displayScriptPosition">A ScriptExtent that describes where this error originated from.</param>
|
|
public JobFailedException(Exception innerException, ScriptExtent displayScriptPosition)
|
|
{
|
|
_reason = innerException;
|
|
_displayScriptPosition = displayScriptPosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Class constructor
|
|
/// </summary>
|
|
/// <param name="serializationInfo">serialization info</param>
|
|
/// <param name="streamingContext">streaming context</param>
|
|
protected JobFailedException(SerializationInfo serializationInfo, StreamingContext streamingContext)
|
|
: base(serializationInfo, streamingContext)
|
|
{
|
|
_reason = (Exception)serializationInfo.GetValue("Reason", typeof(Exception));
|
|
_displayScriptPosition = (ScriptExtent)serializationInfo.GetValue("DisplayScriptPosition", typeof(ScriptExtent));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The actual exception that caused this error.
|
|
/// </summary>
|
|
public Exception Reason { get { return _reason; } }
|
|
private Exception _reason;
|
|
|
|
/// <summary>
|
|
/// The user-focused location from where this error originated.
|
|
/// </summary>
|
|
public ScriptExtent DisplayScriptPosition { get { return _displayScriptPosition; } }
|
|
private ScriptExtent _displayScriptPosition;
|
|
|
|
/// <summary>
|
|
/// Gets the information for serialization
|
|
/// </summary>
|
|
/// <param name="info">The standard SerializationInfo</param>
|
|
/// <param name="context">The standard StreaminContext</param>
|
|
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
|
{
|
|
if (info == null)
|
|
throw new ArgumentNullException("info");
|
|
|
|
base.GetObjectData(info, context);
|
|
|
|
info.AddValue("Reason", _reason);
|
|
info.AddValue("DisplayScriptPosition", _displayScriptPosition);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the reason for this exception
|
|
/// </summary>
|
|
public override string Message
|
|
{
|
|
get
|
|
{
|
|
return Reason.Message;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion PowerShell v3 Job Extensions
|
|
}
|