Skip to content

Fixes #155 - selecting outermost scope crashes debug host. #156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

rkeithhill
Copy link
Contributor

Because we use AddScript, that adds another scope not associated with any filename. That resulted in a StackFrame ScriptPath with a null value. Later, VSCode sent us a request for soruce with a sourceReference value of null. That caused the JSON deserialization of that message to throw and that crashed the debug host. I made two changes. First, when we construct the StackFrameDetails, if the PowerShell CallStackFrame.ScriptName is null, we use the string "" instead for ScriptPath. The second change was to ExecuteScriptAtPath. Essentially, if you do not pass args we now use AddCommand(scriptPath) which does not add the extra stack frame. If you do use args then we still use AddScript. You will see the extra outermost stack frame of ", ". What is somewhat useful about this is that you can open Script variables to look at $MyInvocation.Line to see how the script was invoked with the args.

Because we use AddScript, that adds another scope not associated with any filename.  That resulted in a StackFrame ScriptPath with a null value.  Later, VSCode sent us a request for soruce with a sourceReference value of null.  That caused the JSON deserialization of that message to throw and that crashed the debug host.  I made two changes.  First, when we construct the StackFrameDetails, if the PowerShell CallStackFrame.ScriptName is null, we use the string "<No File>" instead for ScriptPath.  The second change was to ExecuteScriptAtPath.  Essentially, if you do not pass args we now use AddCommand(scriptPath) which does not add the extra stack frame.  If you do use args then we still use AddScript.  You will see the extra outermost stack frame of "<ScriptBlock>, <NoFile>".  What is somewhat useful about this is that you can open Script variables to look at $MyInvocation.Line to see how the script was invoked with the args.
@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

Looks great! Had to restart the CI build because of a test hang :/ You can merge it anytime though.

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

The test hang is an old recurring one, not to imply that your change caused it ;)

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

Argh, another hang. Bad luck on Appveyor today!

@rkeithhill
Copy link
Contributor Author

So if I debug that test ServiceExecutesReplCommandAndReceivesInputPrompt it passes. If I just run it, it fails. A timing thing?

@rkeithhill
Copy link
Contributor Author

Hmm, if I debug the unit test printf style, it seems the output is off by one:
pses-unittestfail
The Key Value should be in index 1 not two.

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

Yeah, there's a funky timing issue with that one for some reason... There's some kind of bug in OutputDebouncer but I haven't been able to find a consistent repro to help diagnose a fix. Only appears on AppVeyor for me. Strange thing is that I put a hack in that test to avoid it and it still seems to get hit for you.

@rkeithhill
Copy link
Contributor Author

In my case, the extra newline is appearing between Name: John and Key Value which the test hack doesn't invoke in my case because the extra line is not before Name: John.

@rkeithhill
Copy link
Contributor Author

Does this printf (action breakpoint) output tell you anything:

Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) read output "\r\n                $NameField = New-Object System.Management.Automation.Host.FieldDescription \"Name\"\r\n                $NameField.SetParameterType([System.String])\r\n                $fields = [System.Management.Automation.Host.FieldDescription[]]($NameField)\r\n                $host.ui.Prompt($null, $null, $fields)\r\n\r\nName: John\r\n"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) nextOutputString is ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) foud new line? true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "                $NameField = New-Object System.Management.Automation.Host.FieldDescription \"Name\""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "                $NameField.SetParameterType([System.String])"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "                $fields = [System.Management.Automation.Host.FieldDescription[]]($NameField)"
Lock Dispose 14696841
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "                $host.ui.Prompt($null, $null, $fields)"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "Name: John"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "                $NameField = New-Object System.Management.Automation.Host.FieldDescription \"Name\""  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "                $NameField.SetParameterType([System.String])"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "                $fields = [System.Management.Automation.Host.FieldDescription[]]($NameField)"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "                $host.ui.Prompt($null, $null, $fields)"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  ""  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "Name: John"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  ""  /  lineHasNewLine true
Lock Dispose 14696841
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) read output "\r\nKey  Value\r\n---  -----\r\nName John \r\n\r\n\r\n\r\nPS C:\\Users\\Keith\\GitHub\\rkeithhill\\PowerShellEditorServices\\test\\PowerShellEditorServices.Test.Host\\bin\\Debug\r\n"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) nextOutputString is ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) foud new line? true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "Key  Value"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "---  -----"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "Name John "
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: "PS C:\\Users\\Keith\\GitHub\\rkeithhill\\PowerShellEditorServices\\test\\PowerShellEditorServices.Test.Host\\bin\\Debug"
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) enqueuing line: ""
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "Key  Value"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "---  -----"  /  lineHasNewLine true

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

Awesome! I was just about to do something like that, thanks for the log. I'll analyze this for a bit and let you know.

After some thought, I think this is a bug in the OutputReader that I'm using to read buffered output. I can see where there might be a cause that an empty string is being returned when no output is ready yet. Your log should help me come up with the reason why

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

The last line in this sequence seems wrong to me:

Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "                $fields = [System.Management.Automation.Host.FieldDescription[]]($NameField)"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "                $host.ui.Prompt($null, $null, $fields)"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  ""  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  "Name: John"  /  lineHasNewLine true
Microsoft.PowerShell.EditorServices.Test.Host.OutputReader.ReadLine(string) buffered nextOutputString:  ""  /  lineHasNewLine true

I think the logic for lineHasNewLine is incorrectly marking the last item as a newline when the final segment is an empty string. Let me see if I can come up with a fix

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

And I think this is only when the lines that are read are being enqueued into the buffer.

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

I think I have a fix for this, will submit as a separate PR

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

Lets see if this fixes it: #158

@rkeithhill
Copy link
Contributor Author

Hmm, is this hanging again? The unit tests did all pass before I pushed the merged changes from PR #158.

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

Damn... Hanging on the same test. This is really annoying.

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

The unfortunate thing is that there's no reliable way to debug these issues on the CI server. The best I can usually do is enable RDP and then remote desktop into the machine and try to read the logs. I'll take a look at it some more tomorrow. You can go ahead and merge the PR if you like or you can keep it open so we can try any new bug fixes that I figure out.

@rkeithhill
Copy link
Contributor Author

FWIW unit tests pass much more consistently now on my dev PC. Occasionally the tests hang and when I attach the debugger, I see that Shutdown is waiting for output to flush on one thread:

    mscorlib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 662 C#
    mscorlib.dll!System.Threading.Tasks.Task.SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 3320  C#
    mscorlib.dll!System.Threading.Tasks.Task.InternalWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 3252  C#
    mscorlib.dll!System.Threading.Tasks.Task.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 3167  C#
    mscorlib.dll!System.Threading.Tasks.Task.Wait() Line 3054   C#
>   Microsoft.PowerShell.EditorServices.Protocol.dll!Microsoft.PowerShell.EditorServices.Protocol.Server.LanguageServer.Shutdown() Line 86  C#
...

@daviwil
Copy link
Contributor

daviwil commented Feb 15, 2016

Jeez, even with a CPU load generator I can't make these issues repro on my machine. Seems like I need to specify a timeout there at the very least, but I need to check and see why it would be hanging anyway. Might be that the client's stream has disconnected before the final flush can occur so the SendEvent method in OutputDebouncer.OnFlush can't complete. That should throw an error, though...

If you're able to reproduce that issue locally again sometime today, could you email me a dump of the host process? It'd be helpful to see what state everything is in at that point.

@daviwil
Copy link
Contributor

daviwil commented Feb 16, 2016

Thanks to the dump you sent I was able to reproduce and fix the latest hang issue. Sync, merge, and try again if you don't mind :)

@rkeithhill
Copy link
Contributor Author

Yay! The tests passed with no hangs!

rkeithhill added a commit that referenced this pull request Feb 16, 2016
…ackframe

Fixes #155 - selecting outermost scope crashes debug host.
@rkeithhill rkeithhill merged commit 3c1e119 into PowerShell:master Feb 16, 2016
@daviwil
Copy link
Contributor

daviwil commented Feb 16, 2016

That's awesome. Thanks a lot for the debugging help!

@rkeithhill rkeithhill deleted the rkeithhill/addscript-extra-stackframe branch February 16, 2016 03:09
@rkeithhill
Copy link
Contributor Author

No problem. Hopefully that's the end of the unit test hangs (crossing my fingers). :-)

@daviwil
Copy link
Contributor

daviwil commented Feb 16, 2016

Haha, I'm sure I'll find a way to create some more soon ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants