dwww Home | Show directory contents | Find package

# 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.

Generated by dwww version 1.15 on Fri Jun 21 07:26:25 CEST 2024.