Skip to content
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

Update JSON-RPC #10265

Closed
wants to merge 1 commit into from
Closed

Update JSON-RPC #10265

wants to merge 1 commit into from

Conversation

colin-grant-work
Copy link
Contributor

@colin-grant-work colin-grant-work commented Oct 12, 2021

What it does

Fixes #10264 by switching to use a fork of vscode-ws-jsonrpc that has been updated to support vscode-jsonrpc@6.

I could use some help, though, as I'm not very familiar with all of the details of the JSON RPC system. I'll make some comments pointing to the places where I'm uncertain of the right move.

How to test

  1. Since this modifies the core of our communication with the backend, thoroughly test... everything, I guess.
  2. In particular, check that interactions with plugins work as expected.

Review checklist

Reminder for reviewers

@colin-grant-work colin-grant-work marked this pull request as draft October 12, 2021 22:37
@colin-grant-work colin-grant-work marked this pull request as ready for review October 13, 2021 17:00
Copy link
Member

@paul-marechal paul-marechal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those changes go in the right direction, but I think we should rather look into replacing vscode-ws-jsonrpc by a in-repo alternative that we can maintain. This should simplify our consumption of vscode-jsonrpc at the same time. cc @eclipse-theia/core

@tsmaeder
Copy link
Contributor

tsmaeder commented Oct 14, 2021

I would tend toward in-sourcing this code: I believe the newest version of vscode-jsonrpc actually has support for sockets/ipc, etc. We would be better served maintaining the bit of glue code needed ourselves.
Using a library introduces a certain transaction cost (updating, providing PR's, etc.) that needs to be offset by savings on writing/maintaining the code in-project. Those savings are too small, IMO.

@colin-grant-work colin-grant-work force-pushed the update/jsonrpc branch 2 times, most recently from 058d32c to db3b907 Compare October 14, 2021 23:20
Copy link
Contributor

@tsmaeder tsmaeder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good, in general, but need to test.

packages/core/package.json Show resolved Hide resolved
packages/core/src/common/messaging/proxy-factory.ts Outdated Show resolved Hide resolved
@paul-marechal
Copy link
Member

I went ahead and pushed commits that replace occurrences of vscode-languageserver-types with vscode-languageserver-protocol. The latter already depends on the former and re-exports everything from it. Since the typings are coupled between those two packages, instead of trying to depend on the -types ourselves, making sure we have the same version as -protocol, we can just consume -protocol alone.

@colin-grant-work
Copy link
Contributor Author

@paul-marechal, would you to / like me to see about removing some of the code copied from vscode-ws-jsonrpc / monaco-jsonrpc in favor of functionality drawn directly vscode-jsonrpc, or are you happy with the current state of this PR as an improvement on the existing situation?

@paul-marechal
Copy link
Member

Rebased on master. I tried to untangle what I could with regards to our logical channels over websockets, as well as this weird BareMessageConnection (previously IConnection).

IIUC IConnection is a remnant of when we dealt with LSP directly. We needed something to route JSON-RPC messages from websockets to process pipes. I don't like the IConnection name as it is not as generic as it sounds. It is closer to a MessageConnection but without the helper methods like sendRequest or sendNotification, hence the new name BareMessageConnection. Feedback welcome on the naming.

From there, I simply made helper methods to get both MessageConnection and BareMessageConnection from a Channel. Once this was done, wiring our WebSocketChannel to JSON-RPC worked right away.

I don't think I looked at everything @colin-grant-work touched, as I only focused on the core messaging systems.

@paul-marechal paul-marechal dismissed their stale review October 15, 2021 23:26

No longer depending on vscode-ws-jsonrpc.

Copy link
Member

@msujew msujew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started testing this PR. Most of it looks good, however I can't seem to start a debug session with the vscode-js-debug extension. The debug session does not start in the frontend and I get the following error:

Uncaught (in promise) SyntaxError: Unexpected token o in JSON at position 1 
at JSON.parse (<anonymous>)
at PluginDebugAdapterSession.write (/workspace/theia/packages/plugin-ext/lib/plugin/node/debug/plugin-debug-adapter-session.js:67:52)

@paul-marechal
Copy link
Member

@msujew good catch, this is now fixed. There is still an error being thrown when stopping the session but it already happens on master so that makes it out of scope for this PR.

Copy link
Member

@msujew msujew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find any more regressions introduced by the changes. Besides the one remark the code looks good to me as well.

Anyway, I'll let others review/test this as well before actually approving.

packages/core/src/common/messaging/channel.ts Outdated Show resolved Hide resolved
@paul-marechal
Copy link
Member

paul-marechal commented Oct 22, 2021

@colin-grant-work @tsmaeder I rebased and finalized those changes.

@tsmaeder looking at all this code made me conclude that something is off regarding the way messaging is handled with plugins... Mostly I don't really understand the use of vscode-jsonrpc's APIs in order to not actually make JSON-RPC go through it. It was really awkward to read and follow. The biggest smells (that I noticed) being the following places:

export interface PluginMessage extends Message {
/**
* Bogus JSON-RPC version because we don't actually implement JSON-RPC here.
*/
jsonrpc: '0.0'
/**
* Actual string payload being transmitted.
*/
content: string
}

send(content: string): void {
// vscode-jsonrpc's MessageReader/Writer expect to send JSON-RPC messages.
// Use a bogus jsonrpc version and pass along the `content` to send.
// `content` here is opaque: it could be any string.
const message: PluginMessage = { jsonrpc: '0.0', content };
this.connection.writer.write(message);
}
onMessage(cb: (data: string) => void): void {
this.connection.reader.listen((message: PluginMessage) => cb(message.content));
}

It really feels like we are shoehorning this custom protocol down the poor vscode-jsonrpc APIs...

Please tell me if I misunderstood anything.

@colin-grant-work
Copy link
Contributor Author

@tsmaeder, it seems like there's some overlap between the work here and the work you've been doing on the debug system. Would you mind taking a look at this so we can merge if appropriate, and you can then use some of the new messaging equipment in your PR?

Comment on lines 264 to 275
// Extract the cancellation token if it is the last argument passed to the method to call over JSON-RPC.
let token: CancellationToken | undefined;
if (CancellationToken.is(args[args.length - 1])) {
token = args.pop();
}
// Convert the list of arguments into a structure that allows us to conserve undefined values.
const params: any[] = this.createJsonRpcParameters(args);
// Push back the token if there was one.
// `vscode-jsonrpc` will remove the `CancellationToken` out of `params` when is is the last element.
if (token) {
params.push(token);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could get rid of this logic if we assumed nobody should design their APIs over RPC using undefined params.

The createJsonRpcParameters method is a mechanism to properly propagate undefined parameters over JSON-RPC, because undefined values cannot be transmitted verbatim over JSON.

e.g. JSON.stringify(['a', undefined, 2]) returns '["a",null,2]'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it's not really clear what the motivation for these here changes are: is this fixing a known bug? If yes, which one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I mentioned, undefined values are transformed into null without the type system knowing.

/** Returns storage path for given workspace */
getHostStoragePath(workspaceUri: string | undefined, rootUris: string[]): Promise<string | undefined>;

Here workspaceUri is actually string | null when used over RPC. We got "lucky" here as the only implementation checks for truthiness to detect the type rather than some workspaceUri === undefined which would not work.

One solution would be for interfaces meant to be used over RPC to almost never use undefined in their API.

@vince-fugnitto vince-fugnitto added dependencies pull requests that update a dependency file jsonrpc issues related to jsonrpc quality issues related to code and application quality labels Nov 5, 2021
Copy link
Contributor

@tsmaeder tsmaeder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really wish we could have done the changing of the imports to 'vscode-languageserver-protocol' in a separate PR: 88 files is very big when "real changes" are insterspersed with just changed imports.

The only real change I'm requesting is the renaming of TheiaMessagingConnection: I just find the name very confusing. There are many things in this area of code that I disapprove of, but they were there before so 🤷 .

In the long run, I would like to get rid of jsonrpc anyway: we already have a remote proxy invocation framework in our code and I don't think we need two: the only thing I can see missing is the ability to read partial messages, but that could quite simply be retrofitted.

* This is close to `vscode_jsonrpc.MessageConnection` but without all the JSON-RPC
* specific methods such as `sendRequest`, `sendNotification`, etc.
*/
export interface TheiaMessageConnection extends Disposable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this interface suggests that it is a special kind of MessageConnection, which is simply not the case. This makes it hard to understand the related code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just simply keep it as IConnection? It's just as non-descriptive, but at least not misleading ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since its job is to forward messages, how about RPCMessageRelay?

packages/core/package.json Show resolved Hide resolved
Comment on lines 264 to 275
// Extract the cancellation token if it is the last argument passed to the method to call over JSON-RPC.
let token: CancellationToken | undefined;
if (CancellationToken.is(args[args.length - 1])) {
token = args.pop();
}
// Convert the list of arguments into a structure that allows us to conserve undefined values.
const params: any[] = this.createJsonRpcParameters(args);
// Push back the token if there was one.
// `vscode-jsonrpc` will remove the `CancellationToken` out of `params` when is is the last element.
if (token) {
params.push(token);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it's not really clear what the motivation for these here changes are: is this fixing a known bug? If yes, which one?

*
* @deprecated since 1.19.0
*/
export const PluginWebSocketChannel = PluginChannel;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need a deprecation cycle here: it's not "official" API and I doubt someone actually used this implementation detai.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very fine with that, will remove.

*/
export class PluginWebSocketChannel implements IWebSocket {
constructor(protected readonly connection: PluginConnection) { }
export class PluginChannel implements Channel<string> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're wrapping a string channel around a json rcp connection which wraps a string channel. That does not really make sense, IMO. I got rid of that all in #10333. So I wouldn't sweat the small stuff here.

Copy link
Member

@paul-marechal paul-marechal Nov 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did what I must to get it to compile as before, I'd be more than happy to merge #10333 after this!

callback(params, connection);
});
}

forward(spec: string, callback: (params: MessagingService.PathParams, connection: IConnection) => void): void {
forward(spec: string, callback: (params: MessagingService.PathParams, connection: TheiaMessageConnection) => void): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this even ever used? I didn't find any usages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not anymore but despite the amount of changes in this PR I tried to only touch what was broken/bad.

This method is not bothering anyone so I'd keep it for now :)

@tsmaeder
Copy link
Contributor

tsmaeder commented Nov 5, 2021

@colin-grant-work do we need more testing on this PR?

@colin-grant-work
Copy link
Contributor Author

@colin-grant-work do we need more testing on this PR?

@tsmaeder, more testing never hurts. It seems that @msujew was happy with his last run through, and I haven't noticed any problems, but you may be aware of use cases that we're not hitting.

@tsmaeder
Copy link
Contributor

Btw: does the new version help with #9514?

@colin-grant-work
Copy link
Contributor Author

Btw: does the new version help with #9514?

@tsmaeder, I haven't checked, but it would surprise me if it had much effect on the performance - I don't think there was that much extraneous code running before, and I don't think there's much less now. We still have to JSON-ify everything and send it over the wire (short or long) to the other end. There's a little bit of argument processing on our side and in the package, and that certainly adds a little overhead, but I don't think that's changed much. Someone please bring in a performance tracer and correct me :-).

@tsmaeder tsmaeder dismissed their stale review November 24, 2021 08:02

The rename was done.

@msujew
Copy link
Member

msujew commented Nov 29, 2021

@colin-grant-work @paul-marechal I assume a rebase is in order due to #10333. I would retest this afterwards and approve if possible. We'd have a few weeks after merging to find possible regressions before the next release.

Updates to latest version of vscode-languageserver-protocol
Removes vscode-monaco-jsonrpc dependency
Replaces references to vscode-languageserver-types with
    references to vscode-languageserver-protocol
Refactors existing code to match new interfaces

Co-authored-by: Paul Maréchal <[email protected]
Co-authored-by: Colin Grant <[email protected]>
@colin-grant-work
Copy link
Contributor Author

I'm going to close this for now, since it looks like there's more interest in rewriting our message layer than in updating this package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies pull requests that update a dependency file jsonrpc issues related to jsonrpc quality issues related to code and application quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update JSON-RPC and Related Dependencies
5 participants