microsoft / debug-adapter-protocol

Defines a common protocol for debug adapters.
https://microsoft.github.io/debug-adapter-protocol/
Other
1.4k stars 128 forks source link

Track cross-thread control-flow events #342

Open firelizzard18 opened 1 year ago

firelizzard18 commented 1 year ago

One of the core Go proverbs is, "Don't communicate by sharing memory; share memory by communicating." One of the core language features of Go is channels. Channels are effectively a concurrency-safe FIFO used to pass messages and for synchronization. This effectively means Go has a cross-thread, control-flow mechanism. Certain Go-based systems are built as a collection of components or modules executing concurrently, making heavy use of channels for message passing, synchronization, and control-flow. This makes it very difficult to debug events that involve channels. Two examples are Tendermint and libp2p.

I want a way to track these cross-thread, control-flow events in the debugger. So, some kind of cross-linking between threads indicating the flow of events. I don't know how this can be done but it would be amazingly useful so I want to start a discussion. For this to be useful, VSCode would need to implement some front-end, and Delve (the Go debugger) would need to implement some back-end, but I figured a good first step is building a conceptual framework for how it would work. CC @connor4312 @hyangah

connor4312 commented 1 year ago

A good first step is figuring out what kind of UI/UX we'd want for this scenario. Do you have any ideas in mind for the experience you'd want?

firelizzard18 commented 1 year ago

@connor4312 Perhaps some kind of 'thread events' view? I'm also thinking about #339. For example:

// thread 1
func main() {
    println("do stuff")
    go foo()
    println("do more stuff")
}

// thread 2
func foo() {
    println("do some asynchronous work")
}

Once #339 is implemented and Go, delve, and vscode-go support it, thread 2 may be shown as nested under thread 1. That's useful, but what I really want to know is what line of code caused thread 2 to be launched. By the time thread 2 hits a breakpoint, thread 1 will have moved on and might be deep in some call. So simply knowing that thread 1 spawned thread 2 isn't enough.

So maybe we add some way of listing important events for a thread. In the scenario above:

In a more complex scenario, the events history could contain events such as getting unblocked (channel operations, mutexes, condition variables, semaphores, etc). In the case of a channel event, I would want to see two source locations, one for the action that triggered the unlock, another for the location where the unlocked thread resumed.