-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
Callstack not correct when exiting a function to top-most frame. #28639
Comments
@StephenBloomquist if it is not trivial for your debugger to handle multiple call stack requests than simply always return 20 stack frames, you do not have to respect the As for the callstack not being correct please note that vscode shows a stale stack frame for a second or so, is this state permanent? |
@isidorn I'm not entirely sure I understand what you mean, are you saying just treat the callstack request as if there was only 1 (like pre 1.13)? Because when I was doing that, I was getting a callstack where the top frame was duplicated since I would still get the second request which would again return a full callstack. Would looks something like:
As for your question, yes, the state is permanent. At least until I step to the next line. Here's an exact play-by-play of what I'm seeing, if that helps: The first step out works as expected after waiting the .4 seconds for the stale frames to update, and is how it behaves in situations where I'm not stepping out to the top-most function. But when I do step out to the top-most function, the stale frame never updates. Stepping over to the next line will clear the stale frame. I'll check against the latest insiders version when I get a chance. Thanks! |
What I am trying to say is ignore the I have tried this exact situation with other debuggers (node) and it works fine. Yes, please check with the insiders |
Interesting, I was always getting a second request, even when responding to the first with multiple frames, which is why I tried changing the logic on my end. Edit: Oh wait, I can think of at least one situation where I would get 2 requests, that would again be when there's only 1 frame in the callstack because I'm at the top. My debugger would then respond to the second request from VSCode with the top frame again, and I assume VSCode interpreted that as a new frame. This is because I was ignoring both levels and start frame. |
So I just checked against insiders and I'm still getting the same issue with the old frame not clearing. I also switched my logic around to simply return the full stack on the first request and early-out if startFrame isn't 0 to catch the case where the first request only returns 1 frame (when there's only 1 frame available), but the result is the same. I'll keep poking around on my end, but I'm running out of ideas on what I could be doing wrong. |
Open VSCode insiders, F1 > developer tools > sources tab > open debugModel.ts put a breakpoint here You will see the An alternative is to log all requests coming from the vscode side to your adapter and analyze those. |
see #28808 for something similar. |
@isidorn The part where I was having problems with the second request was, I believe, my fault since my debugger was initially ignoring both start frame and levels. What would happen was this:
That problem was fixed when I changed the code to work like I have it in the snippet in my first post above, but I was still left with the issue of the stale frame when exiting to the top-most frame. @weinand Interesting. I was indeed not setting the totalFrames variable in the response body. At the time I couldn't work out what would be different between stackFrames.length and totalFrames, so I decided to leave it undefined since it was optional. I tried messing with the totalFrames variable with the recent problems, however, but I haven't seen any change in behavior unfortunately. For the record, the debug adapter and protocol versions I'm running are both 1.20.0 Here's a small dump of those logs, in case they may help (they've been pretty heavily trimmed). Please note that my code logic has changed in this case to return the full callstack on the first request as @isidorn suggested. You'll notice that when the callstack is more than 1 frame deep I do indeed only get 1 request, but when it's 1 frame deep I get 2, the second of which is early-outed, sending an empty response. The First, I step into FunctionA from OnKeyboardEvent: (Session) Requesting step in...
// Duktape pause notification handling omitted
(Session) Requesting threads...
(Session) Requesting stack trace...
(Client) Retrieving callstack...
(Protocol) [duktape] << (GetCallstack: REQ <Int6: 28> EOM)
(Protocol) Recieved 130 bytes across 1 reads.
(Protocol) ========== Dispatching Messages ==========
(Protocol) [duktape] >> REP <Str16: debugger_testing.js> <Str5: FunctionA> <Int6: 18> <Int6: 0> <Str16: debugger_testing.js> <Str5: OnKeyboardEvent> <Int6: 26> <Int6: 13> EOM
(Client) Converting callstack into stackframes...
(Client) Formatting stackframes...
// Omitted the obtaining of class names (would return with "DebuggerTesting")
(Client) Building the response body...
(Client) Pushing frame:
id: 1000
name: DebuggerTesting.FunctionA()
line: 24
(Client) Pushing frame:
id: 1001
name: DebuggerTesting.OnKeyboardEvent()
line: 33
(Session) Requesting scopes...
// Omitted
(Session) Requesting variables...
// Omitted Then, I step out (Session) Requesting step out...
// Duktape pause notification handling omitted
(Session) Requesting threads...
(Session) Requesting stack trace...
(Client) Retrieving callstack...
(Protocol) [duktape] << (GetCallstack: REQ <Int6: 28> EOM)
(Protocol) Recieved 69 bytes across 1 reads.
(Protocol) ========== Dispatching Messages ==========
(Protocol) [duktape] >> REP <Str16: debugger_testing.js> <Str5: OnKeyboardEvent> <Int6: 28> <Int6: 14> EOM
(Client) Converting callstack into stackframes...
(Client) Formatting stackframes...
// Omitted the obtaining of class name (would return with "DebuggerTesting")
(Client) Building the response body...
(Client) Pushing frame:
id: 1000
name: DebuggerTesting.OnKeyboardEvent()
line: 36
(Session) Requesting scopes...
// Omitted
(Session) Requesting variables...
// Omitted
(Session) Requesting stack trace...
(Client) Start frame not 0, aborting request. I'm now left with 2 frames in the callstack, the top frame is my actual current frame on line 36, and the second frame is the stale one on line 33. |
We have introduced https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/parts/debug/common/debugProtocol.d.ts#L992 |
@isidorn Hey, after updating to the release of 1.14.0 and adjusting my debugger to not support delayed stacktrace requests, I'm still encountering this issue with the stale stack frame upon stepping out to the top stack frame. I've spent a few hours using the insider developer tools to step through the code trying to work out when the callstack changes (I had narrowed it down as far as the OnCallStackChangeScheduler on line 274 in debugViews.ts). The outcome seems to differ depending on how I step through the code (with the stale frame actually going away on occasion) which makes me wonder if there might be a race condition occurring? It took a while for me to get VSCode in a state that mimics what I'm seeing, but eventually I was able to get this: Another thing that I noticed is that if I copy the callstack (right click -> copy callstack), the stale frame doesn't get copied. I began to wonder about whether it's actually simply "Stepping out" to the top-most function that does it, and decided to try something different. I made a function chain to get me a number of stack frames deep and then executed a "run to cursor" out of all of them, and this is what resulted: |
Thank you for the analysis, however if there was a race condition I believe that other debuggers would also be encountering this. |
I'm not entirely sure what you mean by reproducing this issue with javascript / typescript, since it's already in typescript? If you haven't been able to reproduce this bug with other debuggers I'm not entirely sure what more I can do aside from writing a standalone program that can interface with my debugger. The best question I can think of to ask right now is what could I be doing that would get VSCode to show me a callstack with stale frames, but give me a copy of the callstack with no stale frames? It's like it has 2 different callstacks. |
@isidorn I hope at some point I'll be able to find the time to write a program that would let you use my debugger. Until then, I found a program I can use to make a gif so you can actually see what I'm seeing as it happens, rather than through still images and arrows, just in case I haven't explained it well. Here's a rundown of what I'm doing:
The thing I forgot to show is how the stale frames get cleared when I step again. Just imagine I execute a step over and the stale frames go away. Edit: Wanted to add that the callstack I get from the copy is always correct. |
We also have a very similar issue in our debug adapter. Also if step through the code sometimes the stackframe simply does not refresh. We will try to isolate the problem more, just wanted to respond that the above issue is not a standalone one. |
Please try vscode insiders today, I pushed a potential fix for this. Thanks |
It does not work for me. Now in method foo() I also have a breakpoint. All is fine when the two breakpoints are hit first time |
If it's version 1.15.0 then I'm not seeing any change unfortunately. If there's anywhere you'd like me to put breakpoints, just let me know. It would probably be better than me trying to work out where certain actions on the callstack occur from scratch. |
Yeah it is version 1.15.0. If you are still seeing issues please create a new issue with concise steps that I cen reproduce on my machine. THanks Also here's a code pointer on the fetch call stack side on vscode so you can potentially investigate further |
@isidorn @StephenBloomquist @kalberes The problem is here. if (callStack.length === 1) {
// To reduce flashing of the call stack view simply append the stale call stack
// once we have the correct data the tree will refresh and we will no longer display it.
callStack = callStack.concat(thread.getStaleCallStack().slice(1));
} All call stacks are calculated correctly, however, when hitting the topmost frame (callStack.length === 1), stale call stack is added on top and showed up in the debug view. The scenario is the following:
It is hard to reproduce using the existing debuggers, because you can't hit the breakpoint in the topmost frame. For example, in node you will always get some internal node's frames before you hit yours. And this surfaces only when you hit the topmost frame. But in any case, I hope it should be clear from the description what the problem is. |
@dennisfrostlander thanks for the nice analysis, I have pushed a workaround which should fix this in 7dc9ae4 |
Thanks for the quick fix! This will most definitely resolve our problem because we don't use delayed stack loading. |
That did it, awesome! Nice find, @dennisfrostlander |
Steps to Reproduce:
After fixing my debugger to work with the 2 callstack requests (which isn't quite so straight forward in my case since I can't request partial callstacks from Duktape), I noticed that when exiting a function which would take me to the top level of the stack I'm left with the stack frame I had before entering the function in addition to the stack frame of where the debugger is currently paused.
This does not happen when I'm deeper in the callstack. Leaving a function when I'm, say, a couple functions deep will briefly show the old callstack before updating. I think this might relate to issue #28044 because it almost seems like a stale frame that wasn't cleared.
What my debugger would be doing in this situation is returning the top (and only) frame for the first callstack request, and an empty callstack for the second.
Here's a snippet of how my code is handling this:
@isidorn - I think you're an appropriate person to tag for this, if not I apologize.
The text was updated successfully, but these errors were encountered: