Skip to content

Commit c5b73eb

Browse files
Fix debugger step through on Unix platforms
This change resolves an issue where the debugger would appear hung until a key was pressed on Unix platforms. This issue was caused by a quirk with the Unix implementation for System.Console. The way cursor position is retreived is by writing the cursor position escape sequence query and reading it from stdin. To ensure those escape sequences don't trigger `Console.ReadKey`, the internal stdin buffer is locked while `ReadKey` is running. This means that while `ReadKey` is running, any attempts to query cursor position will block the thread until the pending `ReadKey` finishes. The pipeline appears to hang on a debugger stop because there is a pending `ReadKey` call in another thread and various methods in both the internal PowerShell engine and in our host implementation obtain the cursor position between command executions to determine prompt location. The best solution to this problem (credit goes to @rkeithhill) is to create an alternative `ReadKey` implementation that waits for `KeyAvailable` before blocking. However, if `ReadKey` is not currently running then any input is echoed to the console. Aside from being generally annoying, this presents a huge problem while trying to do something like `Read-Host -AsSecureString`. To get around this, I stripped out the native code from corefx that disables console input echo, created a separate package that this project can consume, and implemented a `WaitForKeyAvailable` method that utilizes it. This fix should be viewed as a temporary solution until either `ReadKey` acts like this by default or a `ReadKeyAsync` method is implemented into corefx.
1 parent 3ca4139 commit c5b73eb

File tree

3 files changed

+36
-39
lines changed

3 files changed

+36
-39
lines changed

PowerShellEditorServices.build.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ task TestPowerShellApi -If { !$script:IsUnix } {
129129
task Build {
130130
exec { & $script:dotnetExe build -c $Configuration .\src\PowerShellEditorServices.Host\PowerShellEditorServices.Host.csproj $script:TargetFrameworksParam }
131131
exec { & $script:dotnetExe build -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj $script:TargetFrameworksParam }
132+
exec { & $script:dotnetExe publish -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f netstandard1.6 }
133+
Copy-Item $PSScriptRoot\src\PowerShellEditorServices\bin\$Configuration\netstandard1.6\publish\UnixConsoleEcho.dll -Destination $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6
134+
Copy-Item $PSScriptRoot\src\PowerShellEditorServices\bin\$Configuration\netstandard1.6\publish\runtimes\osx-64\native\libdisablekeyecho.dylib -Destination $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6
135+
Copy-Item $PSScriptRoot\src\PowerShellEditorServices\bin\$Configuration\netstandard1.6\publish\runtimes\linux-64\native\libdisablekeyecho.so -Destination $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6
132136
}
133137

134138
function UploadTestLogs {
@@ -185,9 +189,12 @@ task LayoutModule -After Build {
185189
New-Item -Force $PSScriptRoot\module\PowerShellEditorServices\bin\Core -Type Directory | Out-Null
186190

187191
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\* -Filter Microsoft.PowerShell.EditorServices*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
192+
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\UnixConsoleEcho.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
193+
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\libdisablekeyecho.* -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
188194
if (!$script:IsUnix) {
189195
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\* -Filter Microsoft.PowerShell.EditorServices*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
190196
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\Newtonsoft.Json.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
197+
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\UnixConsoleEcho.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
191198
}
192199

193200
# Lay out the PowerShellEditorServices.VSCode module's binaries

src/PowerShellEditorServices/Console/ConsoleReadLine.cs

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Text;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using UnixConsoleEcho;
1112

1213
namespace Microsoft.PowerShell.EditorServices.Console
1314
{
@@ -20,8 +21,6 @@ internal class ConsoleReadLine
2021
{
2122
#region Private Field
2223

23-
private object readKeyLock = new object();
24-
private ConsoleKeyInfo? bufferedKey;
2524
private PowerShellContext powerShellContext;
2625

2726
#endregion
@@ -61,7 +60,7 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
6160
{
6261
while (!cancellationToken.IsCancellationRequested)
6362
{
64-
ConsoleKeyInfo keyInfo = await this.ReadKeyAsync(cancellationToken);
63+
ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken);
6564

6665
if ((int)keyInfo.Key == 3 ||
6766
keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control))
@@ -129,6 +128,28 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
129128

130129
#region Private Methods
131130

131+
private static async Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken)
132+
{
133+
await WaitForKeyAvailableAsync(cancellationToken);
134+
return Console.ReadKey(true);
135+
}
136+
137+
private static async Task WaitForKeyAvailableAsync(CancellationToken cancellationToken)
138+
{
139+
InputEcho.Disable();
140+
try
141+
{
142+
while (!Console.KeyAvailable)
143+
{
144+
await Task.Delay(50, cancellationToken);
145+
}
146+
}
147+
finally
148+
{
149+
InputEcho.Enable();
150+
}
151+
}
152+
132153
private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancellationToken)
133154
{
134155
string inputBeforeCompletion = null;
@@ -154,7 +175,7 @@ private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancel
154175
{
155176
while (!cancellationToken.IsCancellationRequested)
156177
{
157-
ConsoleKeyInfo keyInfo = await this.ReadKeyAsync(cancellationToken);
178+
ConsoleKeyInfo keyInfo = await ReadKeyAsync(cancellationToken);
158179

159180
// Do final position calculation after the key has been pressed
160181
// because the window could have been resized before then
@@ -466,41 +487,6 @@ await this.powerShellContext.ExecuteCommand<PSObject>(
466487
return null;
467488
}
468489

469-
private async Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken)
470-
{
471-
return await
472-
Task.Factory.StartNew(
473-
() =>
474-
{
475-
ConsoleKeyInfo keyInfo;
476-
477-
lock (this.readKeyLock)
478-
{
479-
if (cancellationToken.IsCancellationRequested)
480-
{
481-
throw new TaskCanceledException();
482-
}
483-
else if (this.bufferedKey.HasValue)
484-
{
485-
keyInfo = this.bufferedKey.Value;
486-
this.bufferedKey = null;
487-
}
488-
else
489-
{
490-
keyInfo = Console.ReadKey(true);
491-
492-
if (cancellationToken.IsCancellationRequested)
493-
{
494-
this.bufferedKey = keyInfo;
495-
throw new TaskCanceledException();
496-
}
497-
}
498-
}
499-
500-
return keyInfo;
501-
});
502-
}
503-
504490
private int CalculateIndexFromCursor(
505491
int promptStartCol,
506492
int promptStartRow,

src/PowerShellEditorServices/PowerShellEditorServices.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959
<Reference Include="System.Xml" />
6060
</ItemGroup>
6161

62+
<ItemGroup>
63+
<PackageReference Include="UnixConsoleEcho" Version="0.1.0" />
64+
</ItemGroup>
65+
6266
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
6367
<DefineConstants>$(DefineConstants);CoreCLR</DefineConstants>
6468
</PropertyGroup>

0 commit comments

Comments
 (0)