Repository: vscode
Issue: "Load More Stack Frames" is incorrectly displayed after debug adapter has sent the full stack
Labels: bug,debug


Type: <b>Bug</b>

Sometimes when landing on a breakpoint, VS Code displays the thread's call stack followed by "Load More Stack Frames" even though the debug adapter has sent all of the stack frames. A simple repro that is working fairly reliably for me is to put two breakpoints in a function a few levels deep. In my case my debug adapter reports a stack with 4 entries.

1. Start debugging
2. Hit the first breakpoint (everything is correct)
3. Wait 2 or 3 seconds (not sure it matters how long but I figured I should wait until all communication with the debug adapter is complete)
4. Press F5
5. Hit the second breakpoint

Upon hitting the second breakpoint, the thread list (which was reported in an identical manner from the debug adapter) is incorrectly followed by "Load More Stack Frames". Here is the full dump of all of the json sent between VS Code and my debug adapter when following the above steps:

(Note: all stack frame IDs after the first are just `1` because they don't actually represent anything meaningful in my case. I usually send them with "presentationHint":"label", but I removed that while simplifying my code for this test case.)

```
To debugger (t 372): {                    // Hit first breakpoint
    "body":{
        "allThreadsStopped":true,
        "reason":"breakpoint",
        "threadId":1
    },
    "event":"stopped",
    "seq":13,
    "type":"event"
}
From debugger (t 388): {                    // One stack frame requested
    "arguments":{
        "levels":1,
        "startFrame":0,
        "threadId":1
    },
    "command":"stackTrace",
    "seq":12,
    "type":"request"
}
To debugger (t 389): {                    // One returned, totalFrames 4
    "body":{
        "stackFrames":[
            {
                "id":268435456,
                "name":"A"
            }
        ],
        "totalFrames":4
    },
    "command":"stackTrace",
    "request_seq":12,
    "seq":14,
    "success":true,
    "type":"response"
}
From debugger (t 390): {
    "command":"threads",
    "seq":13,
    "type":"request"
}
To debugger (t 391): {
    "body":{
        "threads":[
            {
                "id":1,
                "name":"T0"
            },
            {
                "id":11,
                "name":"T11"
            }
        ]
    },
    "command":"threads",
    "request_seq":13,
    "seq":15,
    "success":true,
    "type":"response"
}
From debugger (t 410): {                    // One stack frame requested again
    "arguments":{
        "levels":1,
        "startFrame":0,
        "threadId":1
    },
    "command":"stackTrace",
    "seq":14,
    "type":"request"
}
To debugger (t 411): {                    // One returned, totalFrames 4
    "body":{
        "stackFrames":[
            {
                "id":268435457,
                "name":"A"
            }
        ],
        "totalFrames":4
    },
    "command":"stackTrace",
    "request_seq":14,
    "seq":16,
    "success":true,
    "type":"response"
}
From debugger (t 829): {
    "arguments":{
        "frameId":268435457
    },
    "command":"scopes",
    "seq":33,
    "type":"request"
}
To debugger (t 830): {
    "body":{
        "scopes":[
            ... (omitted) ...
        ]
    },
    "command":"scopes",
    "request_seq":33,
    "seq":35,
    "success":true,
    "type":"response"
}
From debugger (t 864): {
    "arguments":{
        "variablesReference":268435460
    },
    "command":"variables",
    "seq":34,
    "type":"request"
}
To debugger (t 865): {
    "body":{
        ... (omitted) ...
    },
    "command":"variables",
    "request_seq":34,
    "seq":36,
    "success":true,
    "type":"response"
}
From debugger (t 890): {                    // 19 more frames requested
    "arguments":{
        "levels":19,
        "startFrame":1,
        "threadId":1
    },
    "command":"stackTrace",
    "seq":35,
    "type":"request"
}
To debugger (t 890): {                    // 3 frames returned, totalFrames omitted
    "body":{
        "stackFrames":[
            {
                "id":1,
                "name":"B"
            },
            {
                "id":1,
                "name":"C"
            },
            {
                "id":1,
                "name":"D"
            }
        ]
    },
    "command":"stackTrace",
    "request_seq":35,
    "seq":37,
    "success":true,
    "type":"response"
}

                 // A couple seconds pass. The four call frames were displayed properly.

From debugger (t 2571): {                     // User pressed F5
    "arguments":{
        "threadId":1
    },
    "command":"continue",
    "seq":36,
    "type":"request"
}
To debugger (t 2571): {
    "command":"continue",
    "request_seq":36,
    "seq":38,
    "success":true,
    "type":"response"
}
To debugger (t 2571): {                    // Hit second breakpoint
    "body":{
        "allThreadsStopped":true,
        "reason":"breakpoint",
        "threadId":1
    },
    "event":"stopped",
    "seq":39,
    "type":"event"
}
From debugger (t 2580): {                    // One stack frame requested
    "arguments":{
        "levels":1,
        "startFrame":0,
        "threadId":1
    },
    "command":"stackTrace",
    "seq":37,
    "type":"request"
}
To debugger (t 2580): {                    // One returned, totalFrames 4
    "body":{
        "stackFrames":[
            {
                "id":268435464,
                "name":"A"
            }
        ],
        "totalFrames":4
    },
    "command":"stackTrace",
    "request_seq":37,
    "seq":40,
    "success":true,
    "type":"response"
}
From debugger (t 2582): {
    "command":"threads",
    "seq":38,
    "type":"request"
}
To debugger (t 2582): {
    "body":{
        "threads":[
            {
                "id":1,
                "name":"T0"
            },
            {
                "id":11,
                "name":"T11"
            }
        ]
    },
    "command":"threads",
    "request_seq":38,
    "seq":41,
    "success":true,
    "type":"response"
}
From debugger (t 2587): {                    // One stack frame requested again
    "arguments":{
        "levels":1,
        "startFrame":0,
        "threadId":1
    },
    "command":"stackTrace",
    "seq":39,
    "type":"request"
}
To debugger (t 2587): {                    // One returned, totalFrames 4
    "body":{
        "stackFrames":[
            {
                "id":268435465,
                "name":"A"
            }
        ],
        "totalFrames":4
    },
    "command":"stackTrace",
    "request_seq":39,
    "seq":42,
    "success":true,
    "type":"response"
}
From debugger (t 3018): {
    "arguments":{
        "frameId":268435465
    },
    "command":"scopes",
    "seq":58,
    "type":"request"
}
To debugger (t 3018): {
    "body":{
        "scopes":[
            ... (omitted) ...
        ]
    },
    "command":"scopes",
    "request_seq":58,
    "seq":61,
    "success":true,
    "type":"response"
}
From debugger (t 3031): {                    // 19 more frames requested
    "arguments":{
        "levels":19,
        "startFrame":1,
        "threadId":1
    },
    "command":"stackTrace",
    "seq":59,
    "type":"request"
}
To debugger (t 3031): {                    // 3 frames returned, totalFrames omitted
    "body":{
        "stackFrames":[
            {
                "id":1,
                "name":"B"
            },
            {
                "id":1,
                "name":"C"
            },
            {
                "id":1,
                "name":"D"
            }
        ]
    },
    "command":"stackTrace",
    "request_seq":59,
    "seq":62,
    "success":true,
    "type":"response"
}
From debugger (t 3033): {
    "arguments":{
        "variablesReference":268435468
    },
    "command":"variables",
    "seq":60,
    "type":"request"
}
To debugger (t 3033): {
    "body":{
        ... (omitted) ...
    },
    "command":"variables",
    "request_seq":60,
    "seq":63,
    "success":true,
    "type":"response"
}


        // End of messages. "Load More Stack Frames" is incorrectly displayed.


From debugger (t 572677): { // "Load More Stack Frames" is later clicked. 20 more frames are requested
	"arguments":{
		"levels":20,
		"startFrame":4,
		"threadId":1
	},
	"command":"stackTrace",
	"seq":61,
	"type":"request"
}
To debugger (t 572677): {  // But there are none to send. "Load More Stack Frames" disappears.
	"body":{
		"stackFrames":[
		]
	},
	"command":"stackTrace",
	"request_seq":61,
	"seq":64,
	"success":true,
	"type":"response"
}
```


Note the documentation for "totalFrames" in the DAP:
```
    /**
     * The total number of frames available in the stack. If omitted or if
     * `totalFrames` is larger than the available frames, a client is expected
     * to request frames until a request returns less frames than requested
     * (which indicates the end of the stack). Returning monotonically
     * increasing `totalFrames` values for subsequent requests can be used to
     * enforce paging in the client.
     */
    totalFrames?: number;
```

I believe I am using it correctly by omitting it and returning fewer frames than requested "(which indicates the end of the stack)" (and of course it seems to work properly the first time, just not the second).

I am running a custom extension that I don't see in the list. All it does is hook up a language server and my debug adapter. I am debugging it locally from a separate VS Code instance.

I will add a screenshot once this is on github.

VS Code version: Code 1.72.2 (d045a5eda657f4d7b676dedbfa7aab8207f8a075, 2022-10-12T22:15:18.074Z)
OS version: Windows_NT x64 10.0.19043
Modes:
Sandboxed: No

<details>
<summary>System Info</summary>

|Item|Value|
|---|---|
|CPUs|Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz (16 x 3600)|
|GPU Status|2d_canvas: enabled<br>canvas_oop_rasterization: disabled_off<br>direct_rendering_display_compositor: disabled_off_ok<br>gpu_compositing: enabled<br>multiple_raster_threads: enabled_on<br>opengl: enabled_on<br>rasterization: enabled<br>raw_draw: disabled_off_ok<br>skia_renderer: enabled_on<br>video_decode: enabled<br>video_encode: enabled<br>vulkan: disabled_off<br>webgl: enabled<br>webgl2: enabled<br>webgpu: disabled_off|
|Load (avg)|undefined|
|Memory (System)|31.93GB (14.08GB free)|
|Process Argv|--crash-reporter-id bf2f04f0-6a4b-42cf-8980-65c0496065a0|
|Screen Reader|no|
|VM|0%|
</details><details><summary>Extensions (16)</summary>

Extension|Author (truncated)|Version
---|---|---
doxdocgen|csc|1.4.0
debug-visualizer|hed|2.3.1
better-cpp-syntax|jef|1.16.3
cmake-language-support-vscode|jos|0.0.7
vscode-dotnet-runtime|ms-|1.5.0
remote-containers|ms-|0.255.4
remote-ssh|ms-|0.90.1
remote-ssh-edit|ms-|0.84.0
remote-wsl|ms-|0.72.0
cmake-tools|ms-|1.12.27
cpptools|ms-|1.12.4
cpptools-extension-pack|ms-|1.3.0
rust|rus|0.7.8
cmake|twx|0.0.17
vscode-lldb|vad|1.8.1
markdown-all-in-one|yzh|3.4.3

(2 theme extensions excluded)

</details>
<!-- generated by issue reporter -->