mfussenegger / nvim-dap

Debug Adapter Protocol client implementation for Neovim

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[php] terminating session is incomplete

JSteitz opened this issue · comments

Debug adapter definition and debug configuration

dap.adapters.php = {
  type = "executable",
  command = "node",
  args = { "--no-deprecation", vim.fn.stdpath("data") .. "/dap/vscode-php-debug/out/phpDebug.js" },
}

dap.configurations.php = {
  {
    name = "Launch Xdebug",
    type = "php",
    request = "launch",
    port = 9003,
    stopOnEntry = false,
    pathMappings = {
      ...
    },
    xdebugSettings = {
      max_children = 1024,
      max_data = 1000000,
      show_hidden = 1
    }
  }
}

Debug adapter version

No response

Steps to Reproduce

  1. Open any php project to debug
  2. Run continue()
  3. Run terminate()

Expected Result

Session is terminated and the terminate event is fired.

Actual Result

It looks like the session is not completely terminated and the terminate event is not fired.

What I see, is that the node process is terminated, but when I try to run continue() again, I'm prompted with an existing session in which I have to decide what to do (terminate, disconnect, etc…)
Picking terminate or disconnect results in a 'disconnect' timed out... message.

The only workarounds for me currently are:

  1. to call close() after terminate() but this does not fire required events
  2. or I close neovim and start from scratch (not really practical)
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:715 ]	"Spawning debug adapter"	{
  args = { "--no-deprecation", "/home/jura/.local/share/nvim/dap/vscode-php-debug/out/phpDebug.js" },
  command = "node",
  type = "executable"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:887 ]	"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 ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:563 ]	{
  body = {
    exceptionBreakpointFilters = { {
        filter = "Notice",
        label = "Notices"
      }, {
        filter = "Warning",
        label = "Warnings"
      }, {
        filter = "Error",
        label = "Errors"
      }, {
        filter = "Exception",
        label = "Exceptions"
      }, {
        filter = "*",
        label = "Everything"
      } },
    supportTerminateDebuggee = true,
    supportsConditionalBreakpoints = true,
    supportsConfigurationDoneRequest = true,
    supportsDelayedStackTraceLoading = false,
    supportsEvaluateForHovers = true,
    supportsFunctionBreakpoints = true,
    supportsHitConditionalBreakpoints = true,
    supportsLogPoints = true,
    supportsSetVariable = true
  },
  command = "initialize",
  request_seq = 0,
  seq = 1,
  success = true,
  type = "response"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:887 ]	"request"	{
  arguments = {
    name = "Launch Xdebug",
    pathMappings = {
      // path mappings are working, just hiding from public
    },
    port = 9003,
    request = "launch",
    stopOnEntry = false,
    type = "php",
    xdebugSettings = {
      max_children = 1024,
      max_data = 1000000,
      show_hidden = 1
    }
  },
  command = "launch",
  seq = 1,
  type = "request"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:563 ]	{
  command = "launch",
  request_seq = 1,
  seq = 2,
  success = true,
  type = "response"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:563 ]	{
  event = "initialized",
  seq = 3,
  type = "event"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:887 ]	"request"	{
  arguments = {
    filters = {}
  },
  command = "setExceptionBreakpoints",
  seq = 2,
  type = "request"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:563 ]	{
  body = {
    breakpoints = {}
  },
  command = "setExceptionBreakpoints",
  request_seq = 2,
  seq = 4,
  success = true,
  type = "response"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:887 ]	"request"	{
  command = "configurationDone",
  seq = 3,
  type = "request"
}
[ DEBUG ] 2022-03-10T16:19:54Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:563 ]	{
  command = "configurationDone",
  request_seq = 3,
  seq = 5,
  success = true,
  type = "response"
}
[ DEBUG ] 2022-03-10T16:19:58Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:887 ]	"request"	{
  arguments = {
    restart = false,
    terminateDebuggee = true
  },
  command = "disconnect",
  seq = 4,
  type = "request"
}
[ DEBUG ] 2022-03-10T16:19:58Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:563 ]	{
  command = "disconnect",
  request_seq = 4,
  seq = 6,
  success = true,
  type = "response"
}
[ INFO ] 2022-03-10T16:19:58Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:734 ]	"Closed all handles"
[ INFO ] 2022-03-10T16:19:58Z+0100 ] ...nvim/site/pack/packer/start/nvim-dap/lua/dap/session.lua:737 ]	"Process closed"	28987	false

Based on my interpretation of the specification I'd think that's an issue with the debug adapter, it should at least send the terminated event if the debugee stops.

I can improve the behavior of running terminate again a second time, so that it clears the stale session, but that won't fix the root cause.

Yes, I agree with that.
I'll open a ticket on the extension side too.

Hi. I think that the problem stems from the fact that the php debug adapter presents different DBGp sessions as DAP threads.
I do agree that the events aren't used correctly. If the debugged PHP process exits, DA should send Exited Event. I am not sure about Terminated Event as a consequence of Disconnect Request.

I'll try and see.

I definitely can't send Terminated Event in case of a singled DBGp session end, since the DAP needs to continue and can accept more DBGp sessions.

Sadly the Protocol does not have a message that would allow the DAP to tell the IDE to initiate another DAP session. This is possible in VS Code, but uses their native Extension API, so in other IDEs, separate DBGp sessions can only be represented as DAP threads....

If the debugged PHP process exits, DA should send Exited Event. I am not sure about Terminated Event as a consequence of Disconnect Request.

Based on the "Debug session end" section in https://microsoft.github.io/debug-adapter-protocol/overview I'd assume that terminated event always needs to be sent.

See:

Debuggee launched: if a debug adapter supports the terminate request, the development tool uses it to terminate the debuggee gracefully, i.e. it gives the debuggee a chance to cleanup everything before terminating. If the debuggee does not terminate but continues to run (or hits a breakpoint), the debug session will continue, but if the development tool tries again to terminate the debuggee, it will then use the disconnect request to end the debug session unconditionally. The disconnect request is expected to terminate the debuggee (and any child processes) forcefully.

In all situations where a debug adapter wants to end the debug session, a terminated event must be fired.

If the debuggee has ended (and the debug adapter is able to detect this), an optional exited event can be issued to return the exit code to the development tool.

Regarding multiple sessions:

Sadly the Protocol does not have a message that would allow the DAP to tell the IDE to initiate another DAP session. This is possible in VS Code, but uses their native Extension API, so in other IDEs, separate DBGp sessions can only be represented as DAP threads....

I'd really love to see some effort to get this into the specification. vscode-js-debug has a custom extension for it. debugpy also has a custom extension (which is of course different).

Would be unfortunate if php came up with yet another way to do things.

I've created a PR xdebug/vscode-php-debug#763 to address the issue, However perhaps the nvim-dap should handle the closing of DAP channel (not sure if it's TCP or PIPE or STDIO in this case) as a terminal event and clean everything up...

Is anyone able to pull the code, compile it and test it?

Now that I implemented the change I went over some old code that touched this subject and it caused me to re-read the spec. It is indeed vague. The key text is, as you said before,

In all situations where a debug adapter wants to end the debug session, a terminated event must be fired.

My tests show that if the DA sent TerminatedEvent to the IDE, the IDE will then send a DisconnectRequest (tested with launch, not attach).

I read the text as "for the DA to tell the IDE to stop debugging, send the TerminatedEvent".

So in this sense the change in DA does not make sense. nvim-dap should handle session cleanup when receiving DisconnectResponse.

Any change we can get a confirmation @isidorn ? Is the DA supposed to send TerminatedEvent after receiving DisconnectRequest?

Thanks!

I read the text as "for the DA to tell the IDE to stop debugging, send the TerminatedEvent".

Oh interesting. I guess some clarification for the specification would be good, because some debug adapters do send an event if the client sends a disconnect.

I opened a PR that would solve this on the nvim-dap side by closing the session once a disconnected response is received: #481

@zobo I think the DA should send the TerminatedEvent after receiving the DisconnectRequest. But @weinand can correct me here.

But @weinand can correct me here.

That'd be great. It works either way, but sending TerminatedEvent after receiving DisconnectRequest - potentially because we already sent a TerminatedEvent earlier, seems redundant.

From the spec:

The TerminatedEvent event indicates that debugging of the debuggee has terminated.

Possible reasons why the debugging has terminated:

  • the program has run to its end
  • the debugging was terminated by the user (which typically results in a disconnect request sent to the DA).
  • any other (maybe custom) way to end the debugging.

In general DAP events are used to indicate some asynchronous state change in the debuggee or DA.
Events are not associated with requests so a client should not make the assumption that a specific request (e.g. disconnect) triggers a state change (e.g. to "terminated"). Clients should only use events for tracking state changes.
(yes, for historical reasons there is one exception...)

This means that the terminated event must be sent even if the state change was the result of a disconnect request.

Sending more than one terminated event does not make any sense because "debugging of the debuggee" cannot "stop more than once". This means that a DA should not blindly send the terminated event at the end of the disconnect request. It should only send a terminated event if "debugging of the debuggee" has really ended.

Hi Andre! Thanks so much for your insight.

In my case the situation is a bit more complex, since the DAP session encapsulates more Xdebug sessions, And the DAP session will stay active as long as DA is listening for Xdebug sessions. So Disconnect directly leads to adapter shutdown, where as a terminated Xdebug session will lead to a Thread Exited Event.

This is also in line with the spec for DisconnectRequest.

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.

@mfussenegger I'd suggest you also handle DA process exit with dap().set_session(nil).

Honestly I'd prefer to leave the PHP DA as is since it works correctly with VS Code and the Spec isn't that explicit - especially if compared to VS Code implementation.

Thanks for all the inputs. I changed the handling in nvim-dap to cleanup the session already on the disconnect response.