-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Allow multiple debug sessions through a single adapter #79
Comments
I like this. Does the protocol need to think about how a client knows whether to route a new request to the same adapter vs creating a new one, or is that up to the client? |
To start with, I think we could leave it up to the client. On the VS Code side, it's pretty easy to leverage the DebugAdapterDescriptorFactory to trap new launches and re-use the same adapter instance. On VS that's a little harder, since there's no similar built in "factory" interface, but since adapters using the PineZorro extension have the ability to handle launching themselves, it's fairly easy to just use that to route everything back through the same adapter instance (which is what we're doing right now). If we end up finding this model useful outside of javascript debugging, it would be nice to have a way to tell the host that we want to re-use the same adapter instance in "flat session". For that we could just use the capabilities event and publish that an adapter can support flat session mode. |
This sounds neat. I like the intention and it'd certainly make some scenarios easier as you mentioned. Stepping between the client and service is super interesting, being able to step through In implementation we may want to have a new preflight request prior to launching, for the client to confirm whether it is able to attach a session to the existing adapter. Adapters won't all support flat sessions for some time, and then there might(?) be scenarios where one adapter is unable to flatten requested sessions into its existing instance. A graceful rejection or a 'method not found' error would cause a new instance of the adapter to start. |
@EricCornelson Thanks for this interesting proposal. My first reaction: With this "optimisation" a single debug adapter can be used to handle multiple sessions if these sessions use the same runtime. This makes sense for a CDP-DA but I'm not aware of many other runtimes that would want to support this. On the other hand I see quite some VS Code restructuring work necessary to support this feature. So before we want to commit to this effort, you must convince me that there are no alternatives. ;-) One alternative is what the Java extension is currently doing: they are running a single DA process that serves multiple debug sessions through different socket connections. In the DA server all sessions are using the same underlying runtime and/or debug target connection. So this approach already "Allows multiple debug sessions through a single adapter". But instead of multiplexing via a new "sessionId" attribute, communication channels of the OS are used. Would this solve the issue you are trying to address? |
The Python adapter is using this model - a single adapter managing several related concurrent debug sessions, with multiple connections to it from VSCode via sockets - for multiprocessing scenarios (although we are still working on that implementation). Having a single channel to talk to the IDE would be marginally simpler on the adapter side, but it's not really a big deal. One related thing that would be nice to have is some way for a session to tell the client to create another session (i.e. effectively, issue a reverse "attach" request, that would result in creation of a new session with the supplied debug configuration). We're using a custom event for this, and it works... but it's not a client-agnostic solution, and requires re-implementing the client part for every IDE. Since debugging multiple related processes is a generic scenario, not really tied to any particular language or IDE, and it needs this bit for the adapter to orchestrate things on the client, it would be nice to have that supported in the core DAP protocol. |
@weinand We actually started our proof of concept using sockets, but the big draw for running everything through a single communication channel is remote debugging in VS. Multiple socket connections are fine for local debugging, and probably even debugging a remote workspace in VS Code, but if the user is debugging a remote process either in a VM or a server, and their workspace is local, it gets trickier. We currently have a way through VSDebugAdapterHost and msvsmon to launch the debug adapter process on the remote machine, where the adapter can then launch the desired program and start debugging, and we communicate with the adapter process via its stdin/stdout through an SSH tunnel. But we can't open a socket connection to a debug server process on the remote machine without either manually doing some port-forwarding manually or dealing with firewall issues (that is, there's nothing built in to VS that would make this easy for us, as far as I know). @andrewcrawley pointed out that if we wanted to go the debug server route, we could do something like launch the first adapter as a debug server, and then for each child debug session in VS launch a "proxy" process that would make the connection to the debug server and then pipe the traffic back through its stdin/stdout. While that could theoretically work, we both agreed that this was a less-than-ideal workaround, as it involves a lot more moving parts, and has more potential for problems to arise. I'm not sure how that particular scenario translates to other adapter projects, but it was the biggest factor for us to go with the "flat session" communication model. @int19h +1 We are doing the same thing RE: the custom "reverse attach" event on the VS side, and have had the same thoughts. I didn't add that piece to the proposal, but maybe that's a better place to start. This is where we are passing the sessionId for each new child session back to the host as well, and my thought is that we're essentially telling the host "we are already managing this child debug session, just letting you know". I'm curious, though, if we added a "reverse attach" event to the protocol, how would hosts be expected to handle that event? Right now, what we're doing with vscode-pwa is we manually send a new "start debugging" command and trap the launch to route it through the already-running adapter. But if we consider that "flat session" is not a thing in the protocol, what should the host do if it gets one of these events? We are essentially notifying the host so it can display the session in the UI and start sending commands to it. So then the question becomes, well how does the host start sending commands to this new session? We could give it a port to connect to, but I'd assume we wouldn't want to force a specific implementation (sockets) for new child sessions, and instead leave that up to the adapter implementation. Just firing a new "start debugging" command doesn't seem to make much sense to me either, because the session is actually already running in the current adapter, right, we just want to connect to it? If we fire a new "start debugging" command, then we are still in the business of having to trap the launch and route it through our existing adapter, either in a So that leads me back to having a "flat session" option, where you can tell the host, "hey, I've connected to this new child session, please represent it for me in the UI and start sending messages to it over the existing connection with this identifier". 😃 I definitely understand that it doesn't make sense to add something to the protocol if it only fits a very narrow use case (especially if there is a workaround), which is also the main reason I drafted up the proposal. I was curious to see if there was any other adapters out there that might benefit from a "flat session" mode, and it sounds like there is at least one other! |
What we do right now is have the adapter send the complete debug configuration in the reverse attach request. The custom event handler in VSCode then starts a new debug session with that configuration. The adapter creates it by remembering the config it was passed in the request for the initial session, and then just changing it to "attach" (if it wasn't already), and adding a property to designate which known debuggee to attach to (by PID in our case). On the VSCode side, we use For remote scenarios, this means that the other machine only needs to have that one port opened. This can then be tunneled via SSH for security purposes - for now, we tell the users to do this manually, but it would certainly be nice to have SSH as a built-in DAP transport directly in VSCode, with a single shared config for all debuggers that need it. So there are really two pieces here that require custom IDE-side code right now. One is the custom event handler to create a new session, and the other one is the adapter factory that directs the DAP connection for that to go over a socket. VSCode kinda sorta has the latter in form of "debugServer" already, but since there's no way to specify the hostname, it's only useful for local debugging, not remote. I forgot about that second part, because we use this approach for all "attach" sessions, not just secondary ones, so we implemented it outside of the multi-session scenario. And yes, you're right - to standardize this in DAP would require it to be aware of the transport layer, since the client can't just spawn adapter to service the reverse request to start a new debug session. Which means that "debugServer" would have to be added to the spec for "launch" and "attach" requests (and extended to accommodate hostname in addition to port), and clients supporting it would be required to implement DAP-over-TCP specifically. So I guess it depends on how the DAP team views the protocol - is it meant to be entirely transport-agnostic in all respects, or is it accidental and can change if requirements demand it? I think there would be some value in having something as common as TCP be explicitly standardized as a transport. Especially since it'd be opt-in for the clients anyway - it's more about having them interpret the connection information in the same manner if they choose to support it. The users would likely also appreciate it, since right now adapters that support remote "attach" over TCP don't really have a single consistent format for debug configuration for that, and each has to document it separately. If it's standardized, it could all go straight into the VSCode generic debugging documentation, where users generally land first when they start looking. And eventually, this would allow for SSH tunneling to be transparently added on top. |
As far as I can see, the multiple session approach is documented in the overview and implemented (with custom messages) in vscode-js-debug. |
Any news regarding this issue? Adding support for multi-session debugging for DAP is rather important, and each debug adapter implemeting its own vision of multi-session support creates a lot of fragmentation. |
Today multi-session debugging happens outside of DAP because that use case must be supported anyway: if I want to debug an application that has processes implemented in C++ and Python, then two debug adapter types are needed and the client (e.g. VS Code) establishes independent sessions with these adapters via debug adapter factories that exist per debug type ("cpp" and "python"). The same approach is used when debugging multiple processes implemented in the same language (where only a single debug adapter type is needed): the client uses a single debug adapter factory to create sessions and it is an implementation detail of the factory (and the debug adapter) whether a single server process is used to implement multiple sessions via multiple communication channels or whether an independent debug adapter process is launched for each session. With this approach the client (e.g. VS Code) does not have to deal with debug adapters that are able to handle multiple sessions. It just creates a DAP session via a factory and does not have to care about multiplexing DAP sessions over a single comm channel. |
@weinand Thank you for the quick reply! 🙂
Considering DAP as the "generalization" of a debugger API, various debuggers ( Multi-language debugging might sound similar to multi-session debugging, but the latter is a basic feature of a debugger (Thus, should be represented in DAP in some way), while the other is a fancier feature that is out of scope to a debugger (For which, VSCode can solve by using multiple DAP sessions).
Such approach has disadvantages, which I believe DAP was designed to solve: for each debug adapter, the client would need to implement its notion of "multi-session", if one is multiplexing on the same connection or creating new connections. Such example is shown in both I'm a huge fan of DAP, but lacking a feature a lot of debuggers implement (multi-processing), defeats the purpose of not reinventing the wheel and write a compatability layer between a DAP server and its UI. |
The main issue with leaving this to the client is that only the Server knows when a new 'target' has become available and how to connect to it. This can be demonstrated by the status quo of de facto implementation in debugpy and vscode-js-debug. both of which use (different, naturally) custom messages and custom client-side code. I think the request is to recognise that use case and standardise a mechanism to achieve it rather than each dap sever crating a custom one and each dap client having not have per-server custom code. In the case of vscode-js-debug, the adapter is not even basically functional without implementing its custom undocumented protocol extensions. |
I suggest that someone creates a concrete spec proposal for a new DAP |
@weinand I'd like to thank you again for your quick responses! I suggest taking a similar approach to
As stated above, such approach would have a lot of benefits:
One downside for such approach that I thought of is the root session transport being congested by many child sessions being multiplexed (locking, etc.). However, DAP doesn't generate a lot of traffic, so I do not believe this would cause issues. Please feel free to comment and advise on the suggestion above 👍 |
Pavel noted rightly that there are two things here: the "reverse attach" request -- a request from the DA for the client to start a new debug session, and the specifying how the communication happens. I think the reverse attach is sensible and fairly simple: a new request like The notion of hierarchy could be brought in through an The request could additionally contain a Why not make it be the real "session ID"? (Which is not a concept of DAP itself, only one from VS Code.) My reasoning is that the session ID should not be assigned by the DA, but also that having it being returned from the DAP call introduces additional ordering complexity, since it must be returned before the new connection is made so that the DA can treat it correctly. We can remove the complexity by having the DA assign an integer, which is also more compact on the protocol than a UUID for instance. With this, the "root" debug session would not have a connection ID passed to its calls, but children would.
This would also need a corresponding |
@connor4312 Very nice!
|
I don't think we need to deal with sessionId.connectionId or anything of that form as the spec already tells a bit about how things can work. It just misses some technical aspects to actually implement it. |
The client also needs to know the initialisation options and various other things, as included in @connor4312's proposal. But remember that the purpose of this particular proposal is to allow multiplexing sessions on a single transport, rather than requiring that all adapters working with multiple sessions use IP for transport. This is (presumably) based on the existing (de facto) implementation in vscode-js-debug (which I understand is actually used by Visual Studio (not code)), according to its author. But I tend to agree that this multiplexing is an additional complexity that's not required for the spec to be functional - it's required for specific clients to be functional. Therefore I tend to agree with @mickaelistria that we only need to specify a
I believe that this is pretty much exactly what debugpy does. Example:
I don't believe that anything needs to be specified wrt to initialise exchange, there's an interesting question about breakpoints (should the client re-send all breakpoints to the new child), but I think that's up to client implementations to decide what works best. Perhaps a future update could include a "file type" or something which might indicate to the client that the child session is in a different language, but that seems rather scope-creepy at this point. I think if we had that (or similar) in the spec, then there would be no need for these
Well, having written all of that, the multiplex-over-stdio does seem to have some key advantages. As the existing multi-session support is already in the specification, I'd be tempted to add both approaches and let the client decide which it wants to use by capabilities. The above
We can of course include the TL;DR - agree mostly with @connor4312's proposal, but (probably) for different reasons. |
@puremourning Although de facto in current implementation of
If a DAP server doesn't want to implement multiplexing support - it's OK, it can disable support for |
The existing (spec'd) multi-session approach isn't going to disappear, not least because it's very convenient for remote debugging, etc. So clients have to implement that anyway; I don't see any significant extra complexity on the client side. Speaking as the author of a client, in my client multiplexing is more challenging than completely separate comms. |
I don't like the fact that in this feature request "starting new DAP sessions from within a debug adapter" and "multiplexing DAP sessions on a single transport" are mixed together. The latter is just an optimisation which does not result in any architectural benefits, whereas the former is an important architectural feature because it allows debug adapters to handle multi-session debugging without need for non-standard code in VS Code debug extensions. If we combine both sub-features into one, I see the problem that a client like VS Code will have a hard time to adopt the full feature because it will require significant changes in lots of places. This means that we end up with a DAP feature that DAs can already use, but which will not be supported by VS Code for quite some time (and therefore still requires the existing approach as a fallback). If we could agree on separating the "multiplexing" then we could focus on the "starting new DAP sessions from within a debug adapter" which I believe will result in a fairly small change in clients (including VS Code). But the tricky part here is to avoid talking about concrete communication channels like "ports" and "hosts". Remember the debug adapter factories available in VS Code's extension APIs allow for sockets, named pipes, and even inline implementations that do not use a communication channel at all because the DA is fully implemented in the extension... |
The specifically already tells about |
@mickaelistria For security reasons some debug extension authors refuse to use |
* Proposal: Allow multiple debug sessions through a single adapter Fixes #79 * Changed wording based on review comment
A bit late to the game, but here are my thoughts. Looking from my own perspective (php-debug) the most important thing is how the DA is spawned from the client (VSCode). Since in my case the first DA instance will listen for incoming TCP connections from PHP/Xdebug and then create a new DA instance for each of them. This means either all of the DAs need to run in the same process (be it VSCode itself or one DA host process) or do something very creative with fork() or some TCP proxying - feels too complex. Also, I feel that for my use case the user experience is better if the "child" debug sessions are actually NOT hierarchically under the primary "listener" debug session. This way the user can "stop listening" but continue to debug the open sessions. I understand that this proposal tries to unify how different clients offer creating a new session from a DA instance, but with all the "implementation detail" out of scope we'll see how it will work in VSCode and then across other clients. Great first steps, happy this is moving along. |
I'm not totally clear on what would change for you in this mode. Looking at php-debug, you use the |
Also, I opened the proposal for multiplexing here: #329 |
I should have been more concrete. What I am talking about is not in the main branch, but a separate feature branch, not yet merged. Here I use the InlineDebugAdapterFactory so that all DA instances run in the same process and can share data in static properties... What I'm trying to say is that Maybe a flag in
Or a filed in the Keeping this consistent across different clients could be a problem... |
Can #333 be looked at before 1.59 is finalized? |
Due to #333, I'll revert this change in main, and we will finalize it in October. I'd like to implement it in vscode during October. |
* Add startDebugging request Fix #79 * Remove whitespace * Update specification.md Co-authored-by: Mathias Fußenegger <mfussenegger@users.noreply.github.com> * Fix wording Co-authored-by: Mathias Fußenegger <mfussenegger@users.noreply.github.com>
Currently, both Visual Studio and Visual Studio Code both support multiple concurrently running debug sessions, and Visual Studio Code supports hierarchical debug sessions which can represent debugging child processes or multiple "global objects" (example).
In this vein, there are some scenarios where it would be useful to be able to have multiple debug sessions running through a single debug adapter, which would allow for some more advanced orchestration between debug targets.
A particularly interesting example of this is when debugging a web page which has associated web workers and service workers. The Chrome DevTools protocol allows for the concept of a "flat session" debug mode, which allows a debugger to simultaneously debug all related pages and workers at the same time through a single connection using a "targetId" to route messages to the correct debug target. Additionally, it looks like this will soon become the default in Chrome DevTools, and the "non-flat" session mode will be deprecated (see crbug.com/991325).
In the above example, debugging a web page through a single "flat session" confers many benefits (e.g. being able to start each child target in a suspended state to allow the debugger to configure breakpoints and other set-up before the child starts running). However, running in "flat session" mode requires debugging these multiple targets through a single connection, something which, today, is hard to do using built-in functionality in the debug adapter protocol (it can be achieved by running in-process in VS Code and leveraging the DebugAdapterDescriptorFactory to share state between sessions, but that solution is clunky, and specific to the VS Code API, and therefor does not translate well to Visual Studio, for example).
Some other examples of scenarios where a "flat session" mode might be useful are:
I've created a PR which, as a first step, adds an optional "sessionId" parameter to each protocol message. If this sounds reasonable, I think it would also be nice to give adapters a way to notify the host that a new child session has been attached, as right now an adapter has to manually call VSCode or Visual Studio APIs to launch a new debug session and then trap it on entry to achieve this.
Let me know what you think!
The text was updated successfully, but these errors were encountered: