@@ -57,10 +57,16 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
5757
5858 private readonly IdempotentLatch _isRunningLatch = new ( ) ;
5959
60+ private readonly TaskCompletionSource < bool > _started = new ( ) ;
61+
62+ private readonly TaskCompletionSource < bool > _stopped = new ( ) ;
63+
6064 private EngineIntrinsics _mainRunspaceEngineIntrinsics ;
6165
6266 private bool _shouldExit = false ;
6367
68+ private int _shuttingDown = 0 ;
69+
6470 private string _localComputerName ;
6571
6672 private ConsoleKeyInfo ? _lastKey ;
@@ -128,12 +134,16 @@ public PsesInternalHost(
128134
129135 public string InitialWorkingDirectory { get ; private set ; }
130136
137+ public Task Shutdown => _stopped . Task ;
138+
131139 IRunspaceInfo IRunspaceContext . CurrentRunspace => CurrentRunspace ;
132140
133141 private PowerShellContextFrame CurrentFrame => _psFrameStack . Peek ( ) ;
134142
135143 public event Action < object , RunspaceChangedEventArgs > RunspaceChanged ;
136144
145+ private bool ShouldExitExecutionLoop => _shouldExit || _shuttingDown != 0 ;
146+
137147 public override void EnterNestedPrompt ( )
138148 {
139149 PushPowerShellAndRunLoop ( CreateNestedPowerShell ( CurrentRunspace ) , PowerShellFrameType . Nested ) ;
@@ -172,13 +182,22 @@ public override void SetShouldExit(int exitCode)
172182 SetExit ( ) ;
173183 }
174184
175- public async Task StartAsync ( HostStartOptions startOptions , CancellationToken cancellationToken )
185+ /// <summary>
186+ /// Try to start the PowerShell loop in the host.
187+ /// If the host is already started, this is idempotent.
188+ /// Returns when the host is in a valid initialized state.
189+ /// </summary>
190+ /// <param name="startOptions">Options to configure host startup.</param>
191+ /// <param name="cancellationToken">A token to cancel startup.</param>
192+ /// <returns>A task that resolves when the host has finished startup, with the value true if the caller started the host, and false otherwise.</returns>
193+ public async Task < bool > TryStartAsync ( HostStartOptions startOptions , CancellationToken cancellationToken )
176194 {
177195 _logger . LogInformation ( "Host starting" ) ;
178196 if ( ! _isRunningLatch . TryEnter ( ) )
179197 {
180- _logger . LogDebug ( "Host start requested after already started" ) ;
181- return ;
198+ _logger . LogDebug ( "Host start requested after already started." ) ;
199+ await _started . Task . ConfigureAwait ( false ) ;
200+ return false ;
182201 }
183202
184203 _pipelineThread . Start ( ) ;
@@ -198,6 +217,15 @@ await ExecuteDelegateAsync(
198217 {
199218 await SetInitialWorkingDirectoryAsync ( startOptions . InitialWorkingDirectory , CancellationToken . None ) . ConfigureAwait ( false ) ;
200219 }
220+
221+ await _started . Task . ConfigureAwait ( false ) ;
222+ return true ;
223+ }
224+
225+ public void TriggerShutdown ( )
226+ {
227+ Interlocked . Exchange ( ref _shuttingDown , 1 ) ;
228+ _cancellationContext . CancelCurrentTaskStack ( ) ;
201229 }
202230
203231 public void SetExit ( )
@@ -349,11 +377,19 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance
349377
350378 private void Run ( )
351379 {
352- ( PowerShell pwsh , RunspaceInfo localRunspaceInfo , EngineIntrinsics engineIntrinsics ) = CreateInitialPowerShellSession ( ) ;
353- _mainRunspaceEngineIntrinsics = engineIntrinsics ;
354- _localComputerName = localRunspaceInfo . SessionDetails . ComputerName ;
355- _runspaceStack . Push ( new RunspaceFrame ( pwsh . Runspace , localRunspaceInfo ) ) ;
356- PushPowerShellAndRunLoop ( pwsh , PowerShellFrameType . Normal , localRunspaceInfo ) ;
380+ try
381+ {
382+ ( PowerShell pwsh , RunspaceInfo localRunspaceInfo , EngineIntrinsics engineIntrinsics ) = CreateInitialPowerShellSession ( ) ;
383+ _mainRunspaceEngineIntrinsics = engineIntrinsics ;
384+ _localComputerName = localRunspaceInfo . SessionDetails . ComputerName ;
385+ _runspaceStack . Push ( new RunspaceFrame ( pwsh . Runspace , localRunspaceInfo ) ) ;
386+ PushPowerShellAndRunLoop ( pwsh , PowerShellFrameType . Normal , localRunspaceInfo ) ;
387+ }
388+ catch ( Exception e )
389+ {
390+ _started . TrySetException ( e ) ;
391+ _stopped . TrySetException ( e ) ;
392+ }
357393 }
358394
359395 private ( PowerShell , RunspaceInfo , EngineIntrinsics ) CreateInitialPowerShellSession ( )
@@ -465,25 +501,40 @@ private void PopPowerShell(RunspaceChangeAction runspaceChangeAction = RunspaceC
465501
466502 private void RunTopLevelExecutionLoop ( )
467503 {
468- // Make sure we execute any startup tasks first
469- while ( _taskQueue . TryTake ( out ISynchronousTask task ) )
504+ try
470505 {
471- task . ExecuteSynchronously ( CancellationToken . None ) ;
472- }
506+ // Make sure we execute any startup tasks first
507+ while ( _taskQueue . TryTake ( out ISynchronousTask task ) )
508+ {
509+ task . ExecuteSynchronously ( CancellationToken . None ) ;
510+ }
473511
474- if ( _hostInfo . ConsoleReplEnabled )
475- {
476- RunExecutionLoop ( ) ;
512+ // Signal that we are ready for outside services to use
513+ _started . TrySetResult ( true ) ;
514+
515+ if ( _hostInfo . ConsoleReplEnabled )
516+ {
517+ RunExecutionLoop ( ) ;
518+ }
519+ else
520+ {
521+ RunNoPromptExecutionLoop ( ) ;
522+ }
477523 }
478- else
524+ catch ( Exception e )
479525 {
480- RunNoPromptExecutionLoop ( ) ;
526+ _logger . LogError ( e , "PSES pipeline thread loop experienced an unexpected top-level exception" ) ;
527+ _stopped . TrySetException ( e ) ;
528+ return ;
481529 }
530+
531+ _logger . LogInformation ( "PSES pipeline thread loop shutting down" ) ;
532+ _stopped . SetResult ( true ) ;
482533 }
483534
484535 private void RunNoPromptExecutionLoop ( )
485536 {
486- while ( ! _shouldExit )
537+ while ( ! ShouldExitExecutionLoop )
487538 {
488539 using ( CancellationScope cancellationScope = _cancellationContext . EnterScope ( isIdleScope : false ) )
489540 {
@@ -521,13 +572,13 @@ private void RunDebugExecutionLoop()
521572
522573 private void RunExecutionLoop ( )
523574 {
524- while ( ! _shouldExit )
575+ while ( ! ShouldExitExecutionLoop )
525576 {
526577 using ( CancellationScope cancellationScope = _cancellationContext . EnterScope ( isIdleScope : false ) )
527578 {
528579 DoOneRepl ( cancellationScope . CancellationToken ) ;
529580
530- while ( ! _shouldExit
581+ while ( ! ShouldExitExecutionLoop
531582 && ! cancellationScope . CancellationToken . IsCancellationRequested
532583 && _taskQueue . TryTake ( out ISynchronousTask task ) )
533584 {
@@ -806,7 +857,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs break
806857
807858 private void OnRunspaceStateChanged ( object sender , RunspaceStateEventArgs runspaceStateEventArgs )
808859 {
809- if ( ! _shouldExit && ! _resettingRunspace && ! runspaceStateEventArgs . RunspaceStateInfo . IsUsable ( ) )
860+ if ( ! ShouldExitExecutionLoop && ! _resettingRunspace && ! runspaceStateEventArgs . RunspaceStateInfo . IsUsable ( ) )
810861 {
811862 _resettingRunspace = true ;
812863 PopOrReinitializeRunspaceAsync ( ) . HandleErrorsAsync ( _logger ) ;
0 commit comments