1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
# Subprocess debugging
## Terminology
_Debuggee process_ - the process that is being debugged.
_IDE_ - VSCode or other DAP client.
_Debug server_ - pydevd with debugpy wrapper; hosted inside the debuggee process,
one for each.
_Debug adapter_ - debugpy adapter that mediates between IDE and server.
_IDE listener port_ - port opened by the adapter, on which it listens for incoming
connections from the IDE.
_Server listener port_ - port opened by the adapter, on which it listens for incoming
connections from the servers.
_Adapter listener port_ - port opened by the server, on which it listens for incoming
connection from the adapter.
## "launch" scenario
1. User starts debugging (F5) with "launch" debug config.
1. User code spawns child process.
1. User stops debugging.
```mermaid
sequenceDiagram
# Install "GitHub + Mermaid" from the Chrome Web Store to render the diagram
participant IDE
participant Adapter
participant Debuggee_1
participant Debuggee_2
Note left of IDE: user starts<br/>debugging
IDE ->> Adapter: spawn and connect over stdio
IDE ->>+ Adapter: request "launch"
Adapter ->>+ Debuggee_1: spawn and pass server listener port (cmdline)
Debuggee_1 -->>- Adapter: connect to server listener port
Adapter ->>+ Debuggee_1: request "initialize", "launch"
activate Debuggee_1
note right of Debuggee_1: debug session begins
Debuggee_1 -->>- Adapter: respond to "initialize", "launch"
Adapter -->>- IDE: respond to "launch"
loop every message between IDE and Debuggee_1
Note over IDE,Debuggee_1: propagate message
end
Note right of Debuggee_1: user code spawns<br/>child process
Debuggee_1 ->>+ Debuggee_2: spawn and pass server listener port (cmdline)
Debuggee_2 ->>- Adapter: connect to server listener port
Adapter ->>+ Debuggee_2: request "pydevd_systemInfo"
Debuggee_2 -->>- Adapter: respond to "pydevd_systemInfo"
Adapter ->>+ IDE: "ptvsd_subprocess" event:
IDE ->>- Adapter: connect to IDE listener port
IDE ->>+ Adapter: request "attach" to Debuggee_2
Adapter ->>+ Debuggee_2: request "initialize", "attach"
activate Debuggee_2
note right of Debuggee_2: debug session begins
Debuggee_2 -->>- Adapter: respond to "initialize", "attach"
Adapter -->>- IDE: respond to "attach"
loop every message between IDE and Debuggee_2
Note over IDE,Debuggee_2: propagate message
end
Note left of IDE: user stops debugging
IDE -X+ Adapter: request "disconnect" from Debuggee_1
Note over Adapter: implies "terminate"
Adapter -X+ Debuggee_2: request "terminate"
Debuggee_2 -->>- Adapter: confirm "terminate"
deactivate Debuggee_2
Adapter ->> IDE: "exited" event for Debuggee_2
Adapter -X+ Debuggee_1: request "terminate"
Debuggee_1 -->>- Adapter: confirm "terminate"
deactivate Debuggee_1
Adapter ->> IDE: "exited" event for Debuggee_1
Adapter -->>- IDE: confirm "disconnect" from Debuggee_1
```
## "attach" scenario
1. User starts debuggee process with debug server in it (debugpy command line or `debugpy.enable_attach()`).
1. User starts debugging (F5) with "attach" debug config.
1. User code spawns child process.
1. User disconnects from debuggee.
1. User reconnects to debuggee.
```mermaid
sequenceDiagram
# Install "GitHub + Mermaid" from the Chrome Web Store to render the diagram
participant IDE
participant Adapter
participant Debuggee_1
participant Debuggee_2
Note left of Debuggee_1: user spawns<br/>debuggee
Debuggee_1 ->>+ Adapter: spawn
Adapter ->> Debuggee_1: pass server listener port (stdout)
deactivate Adapter
activate Debuggee_1
Debuggee_1 ->> Adapter: connect to server listener port
deactivate Debuggee_1
Note left of IDE: user starts<br/>debugging
IDE ->> Adapter: connect to IDE listener port
IDE ->>+ Adapter: request "attach"
Adapter ->>+ Debuggee_1: request "initialize", "attach"
activate Debuggee_1
note right of Debuggee_1: debug session begins
Debuggee_1 -->>- Adapter: respond to "initialize", "attach"
Adapter -->>- IDE: respond to "attach"
loop every message between IDE and Debuggee_1
Note over IDE,Debuggee_1: propagate message
end
Note right of Debuggee_1: user code spawns<br/>child process
Debuggee_1 ->>+ Debuggee_2: spawn and pass server listener port (cmdline)
Debuggee_2 ->>- Adapter: connect to server listener port
Adapter ->>+ Debuggee_2: request "pydevd_systemInfo"
Debuggee_2 -->>- Adapter: respond to "pydevd_systemInfo"
Adapter ->>+ IDE: "ptvsd_subprocess" event
IDE ->>- Adapter: connect to IDE listener port
IDE ->>+ Adapter: request "attach" to Debuggee_2
Adapter ->>+ Debuggee_2: request "initialize", "attach"
activate Debuggee_2
note right of Debuggee_2: debug session begins
Debuggee_2 -->>- Adapter: respond to "initialize", "attach"
Adapter -->>- IDE: respond to "attach"
loop every message between IDE and Debuggee_2
Note over IDE,Debuggee_2: propagate message
end
Note left of IDE: user detaches IDE
IDE ->>+ Adapter: request "disconnect" from Debuggee_1
Adapter ->>+ Debuggee_2: request "disconnect"
Debuggee_2 -->>- Adapter: confirm "disconnect"
deactivate Debuggee_2
note right of Debuggee_2: debug session ends
Adapter ->> IDE: "terminated" event for Debuggee_2
Note over Adapter,Debuggee_2: TCP connection is maintained
Adapter ->>+ Debuggee_1: request "disconnect"
Debuggee_1 ->> Adapter: "terminated" event
Adapter ->> IDE: "terminated" event for Debuggee_1
Debuggee_1 -->>- Adapter: confirm "disconnect"
deactivate Debuggee_1
note right of Debuggee_1: debug session ends
Note over Adapter,Debuggee_1: TCP connection is maintained
Adapter -->>- IDE: confirm "disconnect" from Debuggee_1
Note over Adapter: continues running
Note left of IDE: User re-attaches IDE<br/>(same host/port)
IDE ->> Adapter: connect to IDE listener port
IDE ->>+ Adapter: request "attach"
Adapter ->>+ Debuggee_1: request "initialize", "attach"
activate Debuggee_1
note right of Debuggee_1: debug session begins
Debuggee_1 -->>- Adapter: respond to "initialize", "attach"
Adapter ->>+ IDE: "ptvsd_subprocess" event
Adapter -->>- IDE: respond to "attach"
loop every message between IDE and Debuggee_1
Note over IDE,Debuggee_1: propagate message
end
IDE ->>- Adapter: connect to IDE listener port
IDE ->>+ Adapter: request "attach" to Debuggee_2
Adapter ->>+ Debuggee_2: request "initialize", "attach"
activate Debuggee_2
note right of Debuggee_2: debug session begins
Debuggee_2 -->>- Adapter: respond to "initialize", "attach"
Adapter -->>- IDE: respond to "attach"
loop every message between IDE and Debuggee_2
Note over IDE,Debuggee_2: propagate message
end
Note right of Debuggee_2: user code exits
Debuggee_2 -X- Debuggee_2: exits
Adapter ->> IDE: "exited" event for Debuggee_2
Note right of Debuggee_1: user code exits
Debuggee_1 -X- Debuggee_1: exits
Adapter ->> IDE: "exited" event for Debuggee_1
Adapter -X Adapter: exits
```
## Important points
### How does the adapter know that connection from the server is for a subprocess?
By counting connections. The first one is for the root process, all others are for
subprocesses of that process.
### How does the adapter track server connections?
It creates a `Session` instance as soon as the server establishes a socket connection,
and maintains it until the corresponding debuggee process exits. Whenever the IDE
disconnects, the state of the instance is reset.
### How does the IDE know which subprocess to connect to?
It receives a "ptvsd_subprocess" event from the adapter (using the connection for the
root process), which contains host and port on which the adapter is listening for new
connections from the IDE, and PID of the subprocess. It then connects to the specified
host and port, and sends an "attach" request with "processId" from the event.
### How does the adapter know that connection from the IDE is for a specific subprocess?
The first connection is always for the root process. All subsequent connections are
for subprocesses, and must have "processId" specified in the "attach" request. The
adapter keeps track of PID for all processes that it tracks, and uses the PID specified
in the "attach" request to look up the corresponding `Session`.
### How does the server know that IDE has connected or disconnected?
The adapter sends an "initialized" request to the server for every one it receives from
the IDE, and sends a "disconnected" request every time the IDE disconnects (even if it
doesn't send one itself). The server uses those events to keep track of logical debug
sessions, even though the TCP connection is the same throughout the lifetime of the
debuggee. This allows it to enable/disable tracing, continue running if it was stopped
at a breakpoint etc.
|