From d02445f7cebb8b44065fe390c18af147ab87657d Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 6 Jan 2020 17:35:50 -0800 Subject: [PATCH 1/5] Fix PSRL static constructor threading --- .../Server/PsesDebugServer.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index dd2b2f644..2cf5eadf1 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -5,6 +5,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -21,7 +22,10 @@ namespace Microsoft.PowerShell.EditorServices.Server /// internal class PsesDebugServer : IDisposable { - private static bool s_hasRunPsrlStaticCtor = false; + /// + /// This is a bool but must be an int, since Interlocked.Exchange can't handle a bool + /// + private static int s_hasRunPsrlStaticCtor = 0; private readonly Stream _inputStream; private readonly Stream _outputStream; @@ -59,12 +63,12 @@ public PsesDebugServer( /// A task that completes when the server is ready. public async Task StartAsync() { - _jsonRpcServer = await JsonRpcServer.From(options => + _jsonRpcServer = await JsonRpcServer.From(async options => { options.Serializer = new DapProtocolSerializer(); options.Reciever = new DapReciever(); options.LoggerFactory = _loggerFactory; - Extensions.Logging.ILogger logger = options.LoggerFactory.CreateLogger("DebugOptionsStartup"); + ILogger logger = options.LoggerFactory.CreateLogger("DebugOptionsStartup"); // We need to let the PowerShell Context Service know that we are in a debug session // so that it doesn't send the powerShell/startDebugger message. @@ -72,12 +76,10 @@ public async Task StartAsync() _powerShellContextService.IsDebugServerActive = true; // Needed to make sure PSReadLine's static properties are initialized in the pipeline thread. - if (!s_hasRunPsrlStaticCtor && _usePSReadLine) + if (_usePSReadLine && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) { - s_hasRunPsrlStaticCtor = true; - _powerShellContextService - .ExecuteScriptStringAsync("[System.Runtime.CompilerServices.RuntimeHelpers]::RunClassConstructor([Microsoft.PowerShell.PSConsoleReadLine].TypeHandle)") - .Wait(); + await _powerShellContextService + .ExecuteScriptStringAsync("[System.Runtime.CompilerServices.RuntimeHelpers]::RunClassConstructor([Microsoft.PowerShell.PSConsoleReadLine].TypeHandle)"); } options.Services = new ServiceCollection() From af590797ce1881d3fb9af1e8d47b71a9d23aeebd Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 7 Jan 2020 11:02:00 -0800 Subject: [PATCH 2/5] Force synchronous PSRL static ctor run --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 2cf5eadf1..c1c14dae8 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -63,7 +63,7 @@ public PsesDebugServer( /// A task that completes when the server is ready. public async Task StartAsync() { - _jsonRpcServer = await JsonRpcServer.From(async options => + _jsonRpcServer = await JsonRpcServer.From(options => { options.Serializer = new DapProtocolSerializer(); options.Reciever = new DapReciever(); @@ -78,8 +78,10 @@ public async Task StartAsync() // Needed to make sure PSReadLine's static properties are initialized in the pipeline thread. if (_usePSReadLine && Interlocked.Exchange(ref s_hasRunPsrlStaticCtor, 1) == 0) { - await _powerShellContextService - .ExecuteScriptStringAsync("[System.Runtime.CompilerServices.RuntimeHelpers]::RunClassConstructor([Microsoft.PowerShell.PSConsoleReadLine].TypeHandle)"); + // This must be run synchronously to ensure debugging works + _powerShellContextService + .ExecuteScriptStringAsync("[System.Runtime.CompilerServices.RuntimeHelpers]::RunClassConstructor([Microsoft.PowerShell.PSConsoleReadLine].TypeHandle)") + .Wait(); } options.Services = new ServiceCollection() From 13d6f0bc5d1e7a52d580fb44d485667339f96875 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 8 Jan 2020 11:43:53 -0800 Subject: [PATCH 3/5] Improve thread context so WinForms does not block --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index c1c14dae8..f9760de9d 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -84,6 +84,11 @@ public async Task StartAsync() .Wait(); } + // We must also set a thread SynchronizationContext for the pipeline thread so that WinForms doesn't derail it + // TODO: For some reason this doesn't stop the blocking on first invocation, and moving it to the startup of PowerShellContext causes initialization to not respond + // TODO: Create a custom synchronization context that explicitly handles WinForms + _powerShellContextService.ExecuteScriptStringAsync("[System.Threading.SynchronizationContext]::SetSynchronizationContext([System.Threading.SynchronizationContext]::new())"); + options.Services = new ServiceCollection() .AddPsesDebugServices(ServiceProvider, this, _useTempSession); From 83b591d86b4b287072b24e67582fc03bfb37d0f3 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Wed, 8 Jan 2020 14:51:45 -0800 Subject: [PATCH 4/5] Remove dud thread sync context, fix await thread marshalling error --- .../Server/PsesDebugServer.cs | 5 ---- .../PowerShellContextService.cs | 24 +++++++++---------- .../Session/ThreadController.cs | 4 ++-- .../Utility/AsyncQueue.cs | 4 ++-- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index f9760de9d..c1c14dae8 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -84,11 +84,6 @@ public async Task StartAsync() .Wait(); } - // We must also set a thread SynchronizationContext for the pipeline thread so that WinForms doesn't derail it - // TODO: For some reason this doesn't stop the blocking on first invocation, and moving it to the startup of PowerShellContext causes initialization to not respond - // TODO: Create a custom synchronization context that explicitly handles WinForms - _powerShellContextService.ExecuteScriptStringAsync("[System.Threading.SynchronizationContext]::SetSynchronizationContext([System.Threading.SynchronizationContext]::new())"); - options.Services = new ServiceCollection() .AddPsesDebugServices(ServiceProvider, this, _useTempSession); diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs index 037b8427f..fa9659fa3 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs @@ -2356,18 +2356,18 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) PowerShellExecutionResult.Stopped, null)); - // Get the session details and push the current - // runspace if the session has changed - SessionDetails sessionDetails = null; - try - { - sessionDetails = this.GetSessionDetailsInDebugger(); - } - catch (InvalidOperationException) - { - this.logger.LogTrace( - "Attempting to get session details failed, most likely due to a running pipeline that is attempting to stop."); - } + // Get the session details and push the current + // runspace if the session has changed + SessionDetails sessionDetails = null; + try + { + sessionDetails = this.GetSessionDetailsInDebugger(); + } + catch (InvalidOperationException) + { + this.logger.LogTrace( + "Attempting to get session details failed, most likely due to a running pipeline that is attempting to stop."); + } if (!localThreadController.FrameExitTask.Task.IsCompleted) { diff --git a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs index 4066d5117..c84dcae3d 100644 --- a/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs +++ b/src/PowerShellEditorServices/Services/PowerShellContext/Session/ThreadController.cs @@ -78,9 +78,9 @@ internal async Task> RequestPipelineExecutionAsync /// A task object representing the asynchronous operation. The Result property will return /// the retrieved pipeline execution request. /// - internal async Task TakeExecutionRequestAsync() + internal Task TakeExecutionRequestAsync() { - return await PipelineRequestQueue.DequeueAsync(); + return PipelineRequestQueue.DequeueAsync(); } /// diff --git a/src/PowerShellEditorServices/Utility/AsyncQueue.cs b/src/PowerShellEditorServices/Utility/AsyncQueue.cs index 8125076b7..44585762f 100644 --- a/src/PowerShellEditorServices/Utility/AsyncQueue.cs +++ b/src/PowerShellEditorServices/Utility/AsyncQueue.cs @@ -146,7 +146,7 @@ public async Task DequeueAsync(CancellationToken cancellationToken) { Task requestTask; - using (await queueLock.LockAsync(cancellationToken)) + using (await queueLock.LockAsync(cancellationToken).ConfigureAwait(false)) { if (this.itemQueue.Count > 0) { @@ -171,7 +171,7 @@ public async Task DequeueAsync(CancellationToken cancellationToken) } // Wait for the request task to complete outside of the lock - return await requestTask; + return await requestTask.ConfigureAwait(false); } /// From 48faa96abfcb882cf5b78d7f9762d5126c2a50b2 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 9 Jan 2020 09:56:25 -0800 Subject: [PATCH 5/5] Update src/PowerShellEditorServices/Server/PsesDebugServer.cs Co-Authored-By: Patrick Meinecke --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index c1c14dae8..3eeefb041 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -81,7 +81,8 @@ public async Task StartAsync() // This must be run synchronously to ensure debugging works _powerShellContextService .ExecuteScriptStringAsync("[System.Runtime.CompilerServices.RuntimeHelpers]::RunClassConstructor([Microsoft.PowerShell.PSConsoleReadLine].TypeHandle)") - .Wait(); + .GetAwaiter() + .GetResult(); } options.Services = new ServiceCollection()