@@ -66,7 +66,7 @@ static PowerShellContextService()
6666        #region Fields
6767
6868        private  readonly  SemaphoreSlim  resumeRequestHandle  =  AsyncUtils . CreateSimpleLockingSemaphore ( ) ; 
69-         private  readonly  SemaphoreSlim  sessionStateLock  =  AsyncUtils . CreateSimpleLockingSemaphore ( ) ; 
69+         private  readonly  SessionStateLock  sessionStateLock  =  new   SessionStateLock ( ) ; 
7070
7171        private  readonly  OmniSharp . Extensions . LanguageServer . Protocol . Server . ILanguageServer  _languageServer ; 
7272        private  readonly  bool  isPSReadLineEnabled ; 
@@ -744,7 +744,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
744744                // Don't change our SessionState for ReadLine. 
745745                if  ( ! executionOptions . IsReadLine ) 
746746                { 
747-                     await  this . sessionStateLock . WaitAsync ( ) . ConfigureAwait ( false ) ; 
747+                     await  this . sessionStateLock . AcquireForExecuteCommandAsync ( ) . ConfigureAwait ( false ) ; 
748748                    shell . InvocationStateChanged  +=  PowerShell_InvocationStateChanged ; 
749749                } 
750750
@@ -768,7 +768,7 @@ public async Task<IEnumerable<TResult>> ExecuteCommandAsync<TResult>(
768768                    if  ( ! executionOptions . IsReadLine ) 
769769                    { 
770770                        shell . InvocationStateChanged  -=  PowerShell_InvocationStateChanged ; 
771-                         this . sessionStateLock . Release ( ) ; 
771+                         await   this . sessionStateLock . ReleaseForExecuteCommand ( ) . ConfigureAwait ( false ) ; 
772772                    } 
773773
774774                    if  ( shell . HadErrors ) 
@@ -1242,7 +1242,7 @@ public void AbortExecution(bool shouldAbortDebugSession)
12421242            // Currently we try to acquire a lock on the execution status changed event. 
12431243            // If we can't, it's because a command is executing, so we shouldn't change the status. 
12441244            // If we can, we own the status and should fire the event. 
1245-             if  ( this . sessionStateLock . Wait ( 0 ) ) 
1245+             if  ( this . sessionStateLock . TryAcquireForDebuggerAbort ( ) ) 
12461246            { 
12471247                try 
12481248                { 
@@ -1255,7 +1255,7 @@ public void AbortExecution(bool shouldAbortDebugSession)
12551255                finally 
12561256                { 
12571257                    this . SessionState  =  PowerShellContextState . Ready ; 
1258-                     this . sessionStateLock . Release ( ) ; 
1258+                     this . sessionStateLock . ReleaseForDebuggerAbort ( ) ; 
12591259                } 
12601260            } 
12611261        } 
@@ -2700,5 +2700,90 @@ void IHostSupportsInteractiveSession.PopRunspace()
27002700        } 
27012701
27022702        #endregion
2703+ 
2704+         /// <summary> 
2705+         /// Encapsulates the locking semantics hacked together for debugging to work. 
2706+         /// This allows ExecuteCommandAsync locking to work "re-entrantly", 
2707+         /// while making sure that a debug abort won't corrupt state. 
2708+         /// </summary> 
2709+         private  class  SessionStateLock 
2710+         { 
2711+             /// <summary> 
2712+             /// The actual lock to acquire to modify the session state of the PowerShellContextService. 
2713+             /// </summary> 
2714+             private  readonly  SemaphoreSlim  _sessionStateLock ; 
2715+ 
2716+             /// <summary> 
2717+             /// A lock used by this class to ensure that count incrementing and session state locking happens atomically. 
2718+             /// </summary> 
2719+             private  readonly  SemaphoreSlim  _internalLock ; 
2720+ 
2721+             /// <summary> 
2722+             /// A count of how re-entrant the current execute command lock call is, 
2723+             /// so we can effectively use it as a two-way semaphore. 
2724+             /// </summary> 
2725+             private  int  _executeCommandLockCount ; 
2726+ 
2727+             public  SessionStateLock ( ) 
2728+             { 
2729+                 _sessionStateLock  =  AsyncUtils . CreateSimpleLockingSemaphore ( ) ; 
2730+                 _internalLock  =  AsyncUtils . CreateSimpleLockingSemaphore ( ) ; 
2731+                 _executeCommandLockCount  =  0 ; 
2732+             } 
2733+ 
2734+             public  async  Task  AcquireForExecuteCommandAsync ( ) 
2735+             { 
2736+                 // Algorithm here is: 
2737+                 // - Acquire the internal lock to keep operations atomic 
2738+                 // - Increment the number of lock holders 
2739+                 // - If we're the only one, acquire the lock 
2740+                 // - Release the internal lock 
2741+ 
2742+                 await  _internalLock . WaitAsync ( ) . ConfigureAwait ( false ) ; 
2743+                 try 
2744+                 { 
2745+                     if  ( _executeCommandLockCount ++  ==  0 ) 
2746+                     { 
2747+                         await  _sessionStateLock . WaitAsync ( ) . ConfigureAwait ( false ) ; 
2748+                     } 
2749+                 } 
2750+                 finally 
2751+                 { 
2752+                     _internalLock . Release ( ) ; 
2753+                 } 
2754+             } 
2755+ 
2756+             public  bool  TryAcquireForDebuggerAbort ( ) 
2757+             { 
2758+                 return  _sessionStateLock . Wait ( 0 ) ; 
2759+             } 
2760+ 
2761+             public  async  Task  ReleaseForExecuteCommand ( ) 
2762+             { 
2763+                 // Algorithm here is the opposite of the acquisition algorithm: 
2764+                 // - Acquire the internal lock to ensure the operation is atomic 
2765+                 // - Decrement the lock holder count 
2766+                 // - If we were the last ones, release the lock 
2767+                 // - Release the internal lock 
2768+ 
2769+                 await  _internalLock . WaitAsync ( ) . ConfigureAwait ( false ) ; 
2770+                 try 
2771+                 { 
2772+                     if  ( -- _executeCommandLockCount  ==  0 ) 
2773+                     { 
2774+                         _sessionStateLock . Release ( ) ; 
2775+                     } 
2776+                 } 
2777+                 finally 
2778+                 { 
2779+                     _internalLock . Release ( ) ; 
2780+                 } 
2781+             } 
2782+ 
2783+             public  void  ReleaseForDebuggerAbort ( ) 
2784+             { 
2785+                 _sessionStateLock . Release ( ) ; 
2786+             } 
2787+         } 
27032788    } 
27042789} 
0 commit comments