Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -378,15 +378,26 @@ private void CoreClose(bool syncCall)
// Wait till the runspace is opened - This is set in DoOpenHelper()
// Release the lock before we wait
Monitor.Exit(SyncRoot);
bool opened = false;
try
{
RunspaceOpening.Wait();
opened = RunspaceOpening.Wait(TimeSpan.FromSeconds(30));
}
finally
{
// Acquire the lock before we carry on with the rest operations
Monitor.Enter(SyncRoot);
}

if (!opened)
{
SetRunspaceState(RunspaceState.Broken,
new TimeoutException(
StringUtil.Format(RunspaceStrings.StopPipelinesTimedOut,
TimeSpan.FromSeconds(30))));
RaiseRunspaceStateEvents();
return;
}
}

if (_bSessionStateProxyCallInProgress)
Expand Down Expand Up @@ -960,7 +971,14 @@ internal bool WaitForFinishofPipelines()
tuple.Item2.Set();
}),
stateInfo);
return waitAllIsDone.WaitOne();
if (!waitAllIsDone.WaitOne(TimeSpan.FromSeconds(30)))
{
throw new TimeoutException(
StringUtil.Format(RunspaceStrings.StopPipelinesTimedOut,
TimeSpan.FromSeconds(30)));
}

return true;
}
}

Expand All @@ -976,6 +994,14 @@ internal bool WaitForFinishofPipelines()
/// Stops all the running pipelines.
/// </summary>
protected void StopPipelines()
{
StopPipelines(System.Threading.Timeout.InfiniteTimeSpan);
}

/// <summary>
/// Stops all the running pipelines with a bounded timeout using parallel stop.
/// </summary>
protected void StopPipelines(TimeSpan timeout)
{
PipelineBase[] runningPipelines;

Expand All @@ -986,10 +1012,19 @@ protected void StopPipelines()

if (runningPipelines.Length > 0)
{
// Start from the most recent pipeline.
for (int i = runningPipelines.Length - 1; i >= 0; i--)
System.Threading.Tasks.Task[] stopTasks =
new System.Threading.Tasks.Task[runningPipelines.Length];
for (int i = 0; i < runningPipelines.Length; i++)
{
int index = i;
stopTasks[i] = System.Threading.Tasks.Task.Run(
() => runningPipelines[index].Stop());
}

if (!System.Threading.Tasks.Task.WaitAll(stopTasks, timeout))
{
runningPipelines[i].Stop();
throw new TimeoutException(
StringUtil.Format(RunspaceStrings.StopPipelinesTimedOut, timeout));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,12 @@ private static void CloseOrDisconnectAllRemoteRunspaces(Func<List<RemoteRunspace

throttleManager.EndSubmitOperations();

remoteRunspaceCloseCompleted.WaitOne();
if (!remoteRunspaceCloseCompleted.WaitOne(TimeSpan.FromSeconds(30)))
{
throw new TimeoutException(
StringUtil.Format(RunspaceStrings.StopPipelinesTimedOut,
TimeSpan.FromSeconds(30)));
}
}
}

Expand Down Expand Up @@ -1002,7 +1007,12 @@ private void StopOrDisconnectAllJobs()

// Stop jobs.
throttleManager.EndSubmitOperations();
jobsStopCompleted.WaitOne();
if (!jobsStopCompleted.WaitOne(TimeSpan.FromSeconds(30)))
{
throw new TimeoutException(
StringUtil.Format(RunspaceStrings.StopPipelinesTimedOut,
TimeSpan.FromSeconds(30)));
}
}

// Disconnect all disconnectable job runspaces found.
Expand Down Expand Up @@ -1225,7 +1235,16 @@ protected override void Dispose(bool disposing)

if (disposing)
{
Close();
try
{
Close();
}
catch (TimeoutException te)
{
// Graceful close timed out — force Broken state so resources are released.
SetRunspaceState(RunspaceState.Broken, te);
}

_engine = null;
_history = null;
_transcriptionData = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,12 @@ private void StopHelper()

_stopper.Stop();
// Wait for pipeline to finish
PipelineFinishedEvent.WaitOne();
if (!PipelineFinishedEvent.WaitOne(TimeSpan.FromSeconds(30)))
{
throw new TimeoutException(
StringUtil.Format(RunspaceStrings.StopPipelinesTimedOut,
TimeSpan.FromSeconds(30)));
}
}

/// <summary>
Expand Down
108 changes: 97 additions & 11 deletions src/System.Management.Automation/engine/hostifaces/PowerShell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,12 @@ public bool ExposeFlowControlExceptions
/// layer supports this operation.
/// </summary>
internal bool InvokeAndDisconnect { get; set; }

/// <summary>
/// Maximum time to wait for synchronous operations (Invoke, Stop, Close).
/// Default is Timeout.InfiniteTimeSpan which preserves backwards compatibility.
/// </summary>
public TimeSpan Timeout { get; set; } = System.Threading.Timeout.InfiniteTimeSpan;
}

/// <summary>
Expand Down Expand Up @@ -451,6 +457,18 @@ internal void Wait()
_completionEvent.WaitOne();
}

/// <summary>
/// Waits for the completion event with a timeout.
/// </summary>
internal void Wait(TimeSpan timeout)
{
if (!_completionEvent.WaitOne(timeout))
{
throw new TimeoutException(
StringUtil.Format(PowerShellStrings.OperationTimedOut, timeout));
}
}

/// <summary>
/// Signals the completion event.
/// </summary>
Expand Down Expand Up @@ -3078,7 +3096,7 @@ public IAsyncResult BeginInvoke<TInput, TOutput>(PSDataCollection<TInput> input,
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The running PowerShell pipeline was stopped.
/// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// This occurs when <see cref="PowerShell.Stop()"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// </exception>
public Task<PSDataCollection<PSObject>> InvokeAsync()
=> Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke(), _endInvokeMethod);
Expand Down Expand Up @@ -3123,7 +3141,7 @@ public Task<PSDataCollection<PSObject>> InvokeAsync()
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The running PowerShell pipeline was stopped.
/// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// This occurs when <see cref="PowerShell.Stop()"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// </exception>
public Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input)
=> Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<T>(input), _endInvokeMethod);
Expand Down Expand Up @@ -3181,7 +3199,7 @@ public Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The running PowerShell pipeline was stopped.
/// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// This occurs when <see cref="PowerShell.Stop()"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// </exception>
public Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input, PSInvocationSettings settings, AsyncCallback callback, object state)
=> Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<T>(input, settings, callback, state), _endInvokeMethod);
Expand Down Expand Up @@ -3233,7 +3251,7 @@ public Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The running PowerShell pipeline was stopped.
/// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// This occurs when <see cref="PowerShell.Stop()"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// To collect partial output in this scenario,
/// supply a <see cref="System.Management.Automation.PSDataCollection{T}" /> for the <paramref name="output"/> parameter,
/// and either add a handler for the <see cref="System.Management.Automation.PSDataCollection{T}.DataAdding"/> event
Expand Down Expand Up @@ -3303,7 +3321,7 @@ public Task<PSDataCollection<PSObject>> InvokeAsync<TInput, TOutput>(PSDataColle
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The running PowerShell pipeline was stopped.
/// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// This occurs when <see cref="PowerShell.Stop()"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// To collect partial output in this scenario,
/// supply a <see cref="System.Management.Automation.PSDataCollection{T}" /> for the <paramref name="output"/> parameter,
/// and either add a handler for the <see cref="System.Management.Automation.PSDataCollection{T}.DataAdding"/> event
Expand Down Expand Up @@ -3573,7 +3591,11 @@ private void DoRemainingBatchCommands(PSDataCollection<PSObject> objs)
// Queue a batch work item here.
// Calling CoreInvokeAsync / CoreInvoke here directly doesn't work and causes the thread to not respond.
ThreadPool.QueueUserWorkItem(new WaitCallback(BatchInvocationWorkItem), context);
context.Wait();
TimeSpan batchTimeout = _batchInvocationSettings?.Timeout ?? System.Threading.Timeout.InfiniteTimeSpan;
if (batchTimeout == System.Threading.Timeout.InfiniteTimeSpan)
context.Wait();
else
context.Wait(batchTimeout);
}
}
}
Expand Down Expand Up @@ -3677,7 +3699,7 @@ private void AppendExceptionToErrorStream(Exception e)
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The running PowerShell pipeline was stopped.
/// This occurs when <see cref="PowerShell.Stop"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// This occurs when <see cref="PowerShell.Stop()"/> or <see cref="PowerShell.StopAsync(AsyncCallback, object)"/> is called.
/// To collect partial output in this scenario,
/// supply a <see cref="System.Management.Automation.PSDataCollection{T}" /> to <see cref="PowerShell.BeginInvoke"/> for the <paramref name="output"/> parameter
/// and either add a handler for the <see cref="System.Management.Automation.PSDataCollection{T}.DataAdding"/> event
Expand Down Expand Up @@ -3755,6 +3777,29 @@ public void Stop()
}
}

/// <summary>
/// Stop the currently running command with a bounded timeout.
/// </summary>
/// <param name="timeout">Maximum time to wait for stop to complete.</param>
/// <exception cref="TimeoutException">Thrown if stop does not complete within timeout.</exception>
public void Stop(TimeSpan timeout)
{
try
{
IAsyncResult asyncResult = CoreStop(true, null, null);
if (!asyncResult.AsyncWaitHandle.WaitOne(timeout))
{
throw new TimeoutException(
StringUtil.Format(PowerShellStrings.StopTimedOut, timeout));
}

ResetOutputBufferAsNeeded();
}
catch (ObjectDisposedException)
{
}
}

/// <summary>
/// Stop the currently running command asynchronously. If the command is not started,
/// the state of PowerShell instance is changed to Stopped and corresponding events
Expand Down Expand Up @@ -4521,7 +4566,13 @@ private void CoreInvokeHelper<TInput, TOutput>(PSDataCollection<TInput> input, P
// getting the runspace asynchronously so that Stop can be supported from a different
// thread.
_worker.GetRunspaceAsyncResult = pool.BeginGetRunspace(null, null);
_worker.GetRunspaceAsyncResult.AsyncWaitHandle.WaitOne();
TimeSpan poolTimeout = settings?.Timeout ?? System.Threading.Timeout.InfiniteTimeSpan;
if (!_worker.GetRunspaceAsyncResult.AsyncWaitHandle.WaitOne(poolTimeout))
{
throw new TimeoutException(
StringUtil.Format(PowerShellStrings.OperationTimedOut, poolTimeout));
}

rsToUse = pool.EndGetRunspace(_worker.GetRunspaceAsyncResult);
}
else
Expand All @@ -4547,8 +4598,39 @@ private void CoreInvokeHelper<TInput, TOutput>(PSDataCollection<TInput> input, P
}
}

// perform the work in the current thread
_worker.CreateRunspaceIfNeededAndDoWork(rsToUse, true);
// perform the work in the current thread (Phase 2: bounded when Timeout is set)
TimeSpan invokeTimeout = settings?.Timeout ?? System.Threading.Timeout.InfiniteTimeSpan;
if (invokeTimeout == System.Threading.Timeout.InfiniteTimeSpan)
{
// Backwards-compatible fast path: zero overhead, no extra allocation.
_worker.CreateRunspaceIfNeededAndDoWork(rsToUse, true);
}
else
{
// Bounded path: spin invocation on a ThreadPool thread; join with timeout.
// Note: Task.Run dispatches to an MTA thread. Scripts relying on STA COM
// apartment state should use the InfiniteTimeSpan (default) path instead.
var invokeTask = System.Threading.Tasks.Task.Run(
() => _worker.CreateRunspaceIfNeededAndDoWork(rsToUse, true));
bool invokeCompleted;
try
{
invokeCompleted = invokeTask.Wait(invokeTimeout);
}
catch (AggregateException ae) when (ae.InnerExceptions.Count == 1)
{
// Unwrap so callers see the real exception type (e.g. RuntimeException).
System.Runtime.ExceptionServices.ExceptionDispatchInfo
.Capture(ae.InnerExceptions[0]).Throw();
throw; // unreachable; satisfies compiler
}
if (!invokeCompleted)
{
CoreStop(true, null, null); // best-effort: signal pipeline to stop
throw new TimeoutException(
StringUtil.Format(PowerShellStrings.OperationTimedOut, invokeTimeout));
}
}
}
else
{
Expand Down Expand Up @@ -5146,7 +5228,11 @@ private IAsyncResult CoreStop(bool isSyncCall, AsyncCallback callback, object st

if (isSyncCall)
{
_stopAsyncResult.AsyncWaitHandle.WaitOne();
if (!_stopAsyncResult.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(30)))
{
throw new TimeoutException(
StringUtil.Format(PowerShellStrings.StopTimedOut, TimeSpan.FromSeconds(30)));
}
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,10 @@
<data name="NoDefaultRunspaceForPSCreate" xml:space="preserve">
<value>A PowerShell object cannot be created that uses the current runspace because there is no current runspace available. The current runspace might be starting, such as when it is created with an Initial Session State.</value>
</data>
<data name="OperationTimedOut" xml:space="preserve">
<value>The operation did not complete within the allotted time of {0}.</value>
</data>
<data name="StopTimedOut" xml:space="preserve">
<value>Stop did not complete within the allotted time of {0}.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,7 @@
<data name="PrimaryRunspaceAlreadySet" xml:space="preserve">
<value>The static PrimaryRunspace property can only be set once, and has already been set.</value>
</data>
<data name="StopPipelinesTimedOut" xml:space="preserve">
<value>One or more pipelines did not stop within the allotted time of {0}.</value>
</data>
</root>
Loading