sdk
sdk copied to clipboard
Allow DTD clients to know when a service/method is available/unavailable
With a connection to DTD you can call method foo on service bar but you can't in advance tell if that service/method are available. You also can't tell if that service/method cease to be available (for example because the client that had registered that service/method disconnected).
@bkonyi I thought we might be able to borrow from vm_service(/dart_service_protocol_shared) for this (since it already has something similar), but as far as I can tell, that's not implemented in the Dart code and is handled by the VM itself. I presume we'd need something specific to DTD.
I think it's fairly simple (send an event when a method is registered/unregistered), but there are a few different ways we can do this:
- Send events only for the service (this would result in fewer events, but I don't think it's granular enough)
- Send events for service/method combination
One of the things we want to use this for is detecting the services available to the DevTools sidebar from the editor. The postMessage implementation has a bunch of flags:
bool get supportsGetDevices;
bool get supportsSelectDevice;
bool get supportsHotReload;
bool get supportsHotRestart;
bool get supportsOpenDevToolsPage;
bool get supportsOpenDevToolsExternally;
Many of these would map nicely onto (2) because they describe methods that are callable. However there are some flags like supportsOpenDevToolsExternally which are actually describing the capability within a method - for example whether the forceExternal parameter is available on the openDevTools method. This seems harder to represent via events like this, so I wonder if we should do something differently - for example allowing DevTools to call an editor.getCapabilities method that provides these flags.. this could either be done as an editor implementation detail, or maybe could be a DTD-defined way that other services could be consistent with. Any thoughts @kenzieschmoll @bkonyi?
A couple thoughts:
- It seems like we need a "DtdService" or "Service" stream available on DTD by default. Any client should be able to listen to this stream to receive events like
ServiceRegisteredandServiceUnregistered. An example of an event like this might look like:{ 'type': 'ServiceRegistered' 'args': { 'service': 'editor', 'method': 'foo.bar', } - I think we also need an API that a DTD client can call to
getRegisteredServicesor similar. This API can take an optional parameter to specify a single DTD client, like 'editor' or 'analysis_server', etc.
If we had both of these, we could avoid having to implement stream history like DDS does because each client could follow the workflow of:
- Call
getRegisteredServicesupon connection to DTD - Initialize the listener to the 'DtdService' or 'Service' stream thereafter to pick up changes to registered services.
I think we also need an API that a DTD client can call to
getRegisteredServicesor similar.
Do we need this too? I think when a client calls streamListen for a stream, we could also just have the DTD server enumerate all registered services and send the events for those immediately?
I feel like it might simplify the client work slightly (you already need to handle the events arriving just after you started listening, so this avoids an extra API), but I haven't tried to implement it yet.
The other thing noted above is capabilities. I'm wondering whether we could make registerService take an optional caapbilities map that is included in these events, which would allow us to indicate which flags/behaviours might be available for a service:
await clientA.registerService(
'editor',
'openDevToolsPage',
(params) async {
// implementation
},
capabilities: {
'supportsPreferDebugSession': true,
},
);
Results in:
{
'type': 'ServiceRegistered' // (though this be ServiceRegistered when really it's a method?)
'args': {
'service': 'editor',
'method': 'openDevToolsPage',
'capabilities': {
'supportsPreferDebugSession': true.
}
}
WDYT?
I think we also need an API that a DTD client can call to
getRegisteredServicesor similar.Do we need this too? I think when a client calls
streamListenfor a stream, we could also just have the DTD server enumerate all registered services and send the events for those immediately?
I think this is probably the most robust way to do this. If clients have to subscribe to a stream and also manually request the current set of registered service it's really easy to get the ordering wrong, introducing a race condition that could result in the client possibly missing a newly registered service. If we always send the full set of registered services and methods upon stream subscription, there's only a single request needed by the client and we only need to worry about synchronization within DTD
The other thing noted above is capabilities. I'm wondering whether we could make
registerServicetake an optionalcaapbilitiesmap that is included in these events, which would allow us to indicate which flags/behaviours might be available for a service:await clientA.registerService( 'editor', 'openDevToolsPage', (params) async { // implementation }, capabilities: { 'supportsPreferDebugSession': true, }, );Results in:
{ 'type': 'ServiceRegistered' // (though this be ServiceRegistered when really it's a method?) 'args': { 'service': 'editor', 'method': 'openDevToolsPage', 'capabilities': { 'supportsPreferDebugSession': true. } }WDYT?
I think this sounds reasonable and is low-risk. However, is there any situation where the capabilities might be different for a given service method? If not, can the capabilities not be inferred?
I think this sounds reasonable and is low-risk. However, is there any situation where the capabilities might be different for a given service method? If not, can the capabilities not be inferred?
This is to support the methods capabilities changing over time. For example the editor provides an openDevToolsPage() method. We recently added some new functionality that allows passing additional flags (requiresDebugSession and prefersDebugSession) to dictate whether the editor will try to provide a VM Service URI to the DevTools page being opened. Capabilities are to allow the client (eg. DevTools sidebar) to know before calling the method whether or not those flags are supported (because we don't know if we're running inside a new or old version of the VS Code plugin).
Another option would be to register methods with new names for each change, but I think that explode quickly.
That makes sense. Allowing for services to provide capabilities SGTM.
Thanks - I made a start on this and have pushed what I have so far https://dart-review.googlesource.com/c/sdk/+/372940 in case anyone has chance to review and check it's going in the right direction before I resume tomorrow :)