Skip to content

debugpy process doesn't terminate after debug session ends or on disconnect request #672

Closed

Description

Environment data

  • debugpy version: 1.4.0
  • OS and version: Linux 5.13.4
  • Python version (& distribution if applicable, e.g. Anaconda): 3.9.6
  • Using VS Code or Visual Studio: Neither, using nvim-dap

Actual behavior

If the debug adapter is started using python -m debugpy.adapter it remains running indefinitely. It doesn't terminate at the end of a debug session and it also doesn't terminate if it receives a disconnect event.

If the debug adapter is started using python -m debugpy --listen localhost:5678 --wait-for-client somefile.py the debug adapter exits after the debug session ends. If the client sends a disconnect request, the debug adapter doesn't exist.

Expected behavior

According to the specification, there are two modes, single session mode and multi session mode.

single session mode: in this mode, the development tool starts a debug adapter as a standalone process and communicates with it through stdin and stdout. At the end of the debug session the debug adapter is terminated. For concurrent debug sessions, the development tool starts multiple debug adapters.

Given that debugpy communicates over stdin and stdout if started via python -m debugpy.adapter without --listen argument I'd expect that is uses the single session mode - in that case it should automatically terminate after the debug session ends.

Update: Point below is resolved

On the other hand, if the debug adapter is started manually using the --listen argument, I'd expect that it runs in multi session mode:

multi session mode: in this mode, the development tool does not start the debug adapter but assumes that it is already running and that it listens on a specific port for connections attempts. For every debug session, the development tool initiates a new communication session on a specific port and disconnects at the end of the session.

In that case, it is supposed to keep running? This may be tricky given that it runs the debugee itself, but I'd expect that it terminates if it receives a disconnect request. Currently it remains running if it receives a disconnect request.

The ‘disconnect’ request is sent from the client to the debug adapter in order to stop debugging.

It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter.

(From https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect)

Steps to reproduce:

  • Setup neovim with nvim-dap and configure the debugpy debug adapter
  • Debug a file
  • ps aux | grep debugpy
Here is the full log of a session
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:742 ]	"request"	{
  arguments = {
    adapterID = "nvim-dap",
    clientId = "neovim",
    clientname = "neovim",
    columnsStartAt1 = true,
    linesStartAt1 = true,
    locale = "en_US.UTF-8",
    pathFormat = "path",
    supportsRunInTerminalRequest = true,
    supportsVariableType = true
  },
  command = "initialize",
  seq = 0,
  type = "request"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  body = {
    category = "telemetry",
    data = {
      packageVersion = "1.4.0"
    },
    output = "ptvsd"
  },
  event = "output",
  seq = 1,
  type = "event"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  body = {
    category = "telemetry",
    data = {
      packageVersion = "1.4.0"
    },
    output = "debugpy"
  },
  event = "output",
  seq = 2,
  type = "event"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  body = {
    exceptionBreakpointFilters = { {
        default = false,
        filter = "raised",
        label = "Raised Exceptions"
      }, {
        default = true,
        filter = "uncaught",
        label = "Uncaught Exceptions"
      } },
    supportsClipboardContext = true,
    supportsCompletionsRequest = true,
    supportsConditionalBreakpoints = true,
    supportsConfigurationDoneRequest = true,
    supportsDebuggerProperties = true,
    supportsDelayedStackTraceLoading = true,
    supportsEvaluateForHovers = true,
    supportsExceptionInfoRequest = true,
    supportsExceptionOptions = true,
    supportsFunctionBreakpoints = true,
    supportsGotoTargetsRequest = true,
    supportsHitConditionalBreakpoints = true,
    supportsLogPoints = true,
    supportsModulesRequest = true,
    supportsSetExpression = true,
    supportsSetVariable = true,
    supportsStepInTargetsRequest = true,
    supportsTerminateDebuggee = true,
    supportsValueFormattingOptions = true
  },
  command = "initialize",
  request_seq = 0,
  seq = 3,
  success = true,
  type = "response"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:742 ]	"request"	{
  arguments = {
    console = "integratedTerminal",
    name = "Launch file",
    program = "~/workspace/tmp/py/foo.py",
    request = "launch",
    type = "python"
  },
  command = "launch",
  seq = 1,
  type = "request"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  arguments = {
    args = { "~/.virtualenvs/tools/bin/python", "~/.virtualenvs/tools/lib/python3.9/site-packages/debugpy/launcher", "46003", "--", "~/workspace/tmp/py/foo.py" },
    cwd = "~/workspace/tmp/py",
    env = {},
    kind = "integrated",
    title = "Python Debug Console"
  },
  command = "runInTerminal",
  seq = 4,
  type = "request"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:48 ]	"run_in_terminal"	{
  args = { "~/.virtualenvs/tools/bin/python", "~/.virtualenvs/tools/lib/python3.9/site-packages/debugpy/launcher", "46003", "--", "~/workspace/tmp/py/foo.py" },
  cwd = "~/workspace/tmp/py",
  env = {},
  kind = "integrated",
  title = "Python Debug Console"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:762 ]	"response"	{
  body = {
    processId = 329948
  },
  command = "runInTerminal",
  request_seq = 4,
  seq = 2,
  success = true,
  type = "response"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  event = "initialized",
  seq = 5,
  type = "event"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:742 ]	"request"	{
  arguments = {
    filters = { "uncaught" }
  },
  command = "setExceptionBreakpoints",
  seq = 3,
  type = "request"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  command = "setExceptionBreakpoints",
  request_seq = 3,
  seq = 6,
  success = true,
  type = "response"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:742 ]	"request"	{
  command = "configurationDone",
  seq = 4,
  type = "request"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  command = "configurationDone",
  request_seq = 4,
  seq = 7,
  success = true,
  type = "response"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  command = "launch",
  request_seq = 1,
  seq = 8,
  success = true,
  type = "response"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  body = {
    isLocalProcess = true,
    name = "~/workspace/tmp/py/foo.py",
    pointerSize = 64,
    startMethod = "launch",
    systemProcessId = 329954
  },
  event = "process",
  seq = 9,
  type = "event"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  body = {
    reason = "started",
    threadId = 1
  },
  event = "thread",
  seq = 10,
  type = "event"
}
[ DEBUG ] 2021-07-22T15:12:13Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  body = {
    reason = "exited",
    threadId = 1
  },
  event = "thread",
  seq = 11,
  type = "event"
}
[ DEBUG ] 2021-07-22T15:12:14Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  body = {
    exitCode = 0
  },
  event = "exited",
  seq = 12,
  type = "event"
}
[ DEBUG ] 2021-07-22T15:12:14Z+0200 ] ...fig/nvim/pack/plugins/start/nvim-dap/lua/dap/session.lua:510 ]	{
  event = "terminated",
  seq = 13,
  type = "event"
}

Activity

  1. karthiknadig commented on Jul 22, 2021

    @karthiknadig
    Member

    Mathias Fußenegger (@mfussenegger) when you use --listen you are using attach mode. From the specification:

    The ‘disconnect’ request is sent from the client to the debug adapter in order to stop debugging.
    
    It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter.
    
    If the debuggee has been started with the ‘launch’ request, the ‘disconnect’ request terminates the debuggee.
    
    If the ‘attach’ request was used to connect to the debuggee, ‘disconnect’ does not terminate the debuggee.
    
    This behavior can be controlled with the ‘terminateDebuggee’ argument (if supported by the debug adapter).
    

    If the ‘attach’ request was used to connect to the debuggee, ‘disconnect’ does not terminate the debuggee.

    Unless you use the terminateDebuggee set on the DisconnectArguments request, we keep the session running. This is to allow use cases where re-connection to the same process is needed. Note that this is exposed via supportsTerminateDebuggee capability.

  2. mfussenegger commented on Jul 22, 2021

    @mfussenegger
    ContributorAuthor

    Sorry right - the second point about --listen was wrong. I must have mixed up something in my setup while I was investigating the issue.

    But the issue about the debug adapter not terminating in the stdin/stdout mode remains.

  3. mfussenegger commented on Jul 23, 2021

    @mfussenegger
    ContributorAuthor

    I've adapted nvim-dap to send a SIGTERM to the process after the session terminated.
    From my side this could be closed, unless you still want to change debugpy to exit itself if the session terminates in launch / single session mode

  4. int19h commented on Aug 2, 2021

    @int19h
    Contributor

    If I remember correctly, this is by design, because VSCode didn't like it when the adapter exited by itself while the connection is still open (even after "disconnect"). However, it should exit as soon as connection is closed - which in case of working over stdio is represented by the other side closing the corresponding pipe fd/handle, thus indicating that no further messages will be sent. Can you try this?

  5. judej commented on Oct 13, 2021

    @judej

    Closing stale issue. Please reopen if neeeded

  6. wildmoremoremore commented on Nov 4, 2026

    @wildmoremoremore

    On Windows Vim with Vimspector, sending a successful DAP disconnect request did not make the standalone debugpy adapter process exit. With debugpy 1.8.19, the process was still running after 10 seconds when I tested initialize followed by disconnect.

    In Vimspector's Vim job backend, I changed vimspector#internal#job#StopDebugSession() to close the adapter channel's stdin after the DAP disconnect response:
    let channel = job_getchannel(job)
    if ch_status(channel) ==# 'open'
    call ch_close_in(channel)
    endif
    Then I wait briefly for the adapter to exit and keep a forced-stop timeout as a fallback. With this change, debugpy received EOF and exited naturally with status 0 about 30 ms after the successful disconnect response.

    This may help other Vim clients that launch python -m debugpy.adapter over stdio: after disconnect, close the adapter's stdin so the stdio server can finish shutting down.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions