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

Avoid registering the same scheme multiple times #543

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

urbanfly
Copy link

@urbanfly urbanfly commented Dec 13, 2024

When multiple ParameterizedLanguageServer instances are created in the same extension, the same schemes are registered multiple times - failing.

This PR avoids registering a scheme multiple times by filtering the previously registered schemes.

rejected promise not handled within 1 second: Error: a provider for the scheme 'zip' is already registered
extensionHostProcess.js:155
stack trace: Error: a provider for the scheme 'zip' is already registered
    at o.registerFileSystemProvider (...\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:161:94201)
    at Object.registerFileSystemProvider (...\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:171:48861)
    at ...\node_modules\@usethesource\rascal-vscode-dsl-lsp-server\lib\fs\RascalFileSystemProviders.js:51:1
    at Array.forEach (<anonymous>)
    at RascalFileSystemProvider.registerSchemes (...\node_modules\@usethesource\rascal-vscode-dsl-lsp-server\lib\fs\RascalFileSystemProviders.js:51:1)
    at ...\node_modules\@usethesource\rascal-vscode-dsl-lsp-server\lib\lsp\RascalLSPConnection.js:56:1
    at runNextTicks (node:internal/process/task_queues:60:5)
    at processImmediate (node:internal/timers:449:9)
    at process.callbackTrampoline (node:internal/async_hooks:130:17)

VS Code API docs say:

There can only be one provider per scheme and an error is being thrown when a scheme has been claimed by another provider or when it is reserved.

Copy link
Member

@jurgenvinju jurgenvinju left a comment

Choose a reason for hiding this comment

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

This is right on this side. But on the other side in the java URIResolverRegistry it is ok to overwrite a scheme registration (but only logical ones).

@DavyLandman
Copy link
Member

DavyLandman commented Dec 16, 2024

Yes this is an annoying error message, but not one that actually hurts us, as it's on a promise that we don't wait on, but I don't think this is the right fix. As it's in essence a race between parallel registrations of the VFS on the VS Code side.

In regular rascal mode:

  1. Rascal
  2. parametic server

most cases, 1. wins (as it tends to get started first). you don't see this message

In deployed mode:

  1. parametric server DSL 1
  2. parametric server DSL 2
  3. ...

These are all started in parallel, and the 2 instences don't know of each other, or at least, they don't know if they're the only one thats there.

(there is also the edge case of where a scheme might already by another vs code extensions, but let's ignore that).

So, to make sure rascal schemes work in VS Code (so you can open a lib://... or a |project://.. URI in VS Code and the contents are correct) we have to register which rascal schemes are available to VS Code. So we call the registerFileSystemProvider fuction in VS Code, and forward it to the java process. The function notes:

There can only be one provider per scheme and an error is being thrown when a scheme has been claimed by another provider or when it is reserved.

And the only way we could check if maybe someone else already did register a certain scheme is the isWritableFileSystem function, which returns true/false/undefined. where undefined means: we don't know about this scheme. This could still get into a race, but would reduce the chances of it.

I see 2 strategies to get rid of this message:

  1. check isWritableFileSystem just before registerFileSystemProvider
  2. swallow the exception that registerFileSystemProvider throws.

So to be clear, the current fix is a workaround, that will work in some settings (for example if all the parametric servers share the same instance of the VFS server) but it won't work in a different setting where there exists multiple rascal based deployed extensions, where they do not share the VFS client refence and thus, the list of already registered schemas.

Copy link
Member

@DavyLandman DavyLandman left a comment

Choose a reason for hiding this comment

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

While it's an okay workaround, and will indeed reduce the chances of the exception appearing, I think we should do something that's also save against other kinds of collisions (see my comment)

rascal-vscode-extension/src/lsp/RascalLSPConnection.ts Outdated Show resolved Hide resolved
rascal-vscode-extension/src/lsp/RascalLSPConnection.ts Outdated Show resolved Hide resolved
@urbanfly
Copy link
Author

but not one that actually hurts us

It hurts a little bit. We are in the process of adding telemetry and these constantly show up as unhandlederror events. There is no way (that I know of) to avoid these errors as a consumer of this package.

@DavyLandman
Copy link
Member

DavyLandman commented Dec 16, 2024

but not one that actually hurts us

It hurts a little bit. We are in the process of adding telemetry and these constantly show up as unhandlederror events. There is no way (that I know of) to avoid these errors as a consumer of this package.

Ah, yes that's annoying, so catching it might be the easiest? Or do the check first (but still might loose the race)? Both should be a small fix in the code?

Are you okay updating the PR with these fixes?

@urbanfly
Copy link
Author

Are you okay updating the PR with these fixes?

Yes, I will.

@DavyLandman
Copy link
Member

Thanks @urbanfly. I've been thinking about the code a bit more,it's not just a pure race, right now we just always register them, without checking if we've already done it. My classification of a race was more: if you want to solve this, you get into messy races.

@urbanfly
Copy link
Author

So, to make sure rascal schemes work in VS Code (so you can open a lib://... or a |project://.. URI in VS Code and the contents are correct) we have to register which rascal schemes are available to VS Code. So we call the registerFileSystemProvider function in VS Code, and forward it to the java process.

Can this be an optional feature that the consumer can opt out of w/ a new parameter on ParameterizedLanguageServer?

Some ext. probably don't need this ability. And if there are multiple ParameterizedLanguageServer and only one of them has registered the scheme - will it work reliably anyway?

@urbanfly
Copy link
Author

I see 2 strategies to get rid of this message:

  1. check isWritableFileSystem just before registerFileSystemProvider
  2. swallow the exception that registerFileSystemProvider throws.

isWritableFileSystem is already being checked just before registerFileSystemProvider

schemes
.filter(s => !this.protectedSchemes.includes(s))
.filter(isUnknownFileSystem)
.forEach(s => vscode.workspace.registerFileSystemProvider(s, this));

function isUnknownFileSystem(scheme : string) : boolean {
return vscode.workspace.fs.isWritableFileSystem(scheme) === undefined;
}

@DavyLandman
Copy link
Member

Can this be an optional feature that the consumer can opt out of w/ a new parameter on ParameterizedLanguageServer?

The cases that break would be very subtle, I rather we don't get into that. It really depends on the kind of locs you're using, and sending to VS Code.

isWritableFileSystem is already being checked just before registerFileSystemProvider

Ah right, I thought I did something like that already. Okay, so it's a proper race now, between that check and the subsequent register. So just catch the exception is the wisest thing, we could even skip that whole preflight check and just catch the error?

That should solve it once and or all.

@urbanfly
Copy link
Author

urbanfly commented Dec 20, 2024

I changed the PR to now attempt to register all schemes and log (info) when they succeed and log (error) when they fail. These log messages show up in the language-specific OutputChannel.

Example of log output

[Info  - 4:28:30 PM] Scheme registered: zip
[Info  - 4:28:30 PM] Scheme registered: std
[Info  - 4:28:30 PM] Scheme registered: memory
[Info  - 4:28:30 PM] Scheme registered: lib
[Info  - 4:28:30 PM] Scheme registered: jar
[Info  - 4:28:30 PM] Scheme registered: compressed
[Info  - 4:28:30 PM] Scheme registered: PATH
[Info  - 4:28:30 PM] Scheme registered: cwd
[Info  - 4:28:30 PM] Scheme registered: tmp
[Info  - 4:28:30 PM] Scheme registered: project
[Info  - 4:28:30 PM] Scheme registered: home
[Info  - 4:28:30 PM] Scheme registered: target
[Info  - 4:28:30 PM] Scheme registered: jar+std
[Info  - 4:28:30 PM] Scheme registered: jar+file
[Info  - 4:28:30 PM] Scheme registered: jar+memory
[Info  - 4:28:30 PM] Scheme registered: jar+lib
[Info  - 4:28:30 PM] Scheme registered: jar+http
[Info  - 4:28:30 PM] Scheme registered: jar+https
[Info  - 4:28:30 PM] Scheme registered: jar+PATH
[Info  - 4:28:30 PM] Scheme registered: jar+cwd
[Info  - 4:28:30 PM] Scheme registered: jar+tmp
[Info  - 4:28:30 PM] Scheme registered: jar+project
[Info  - 4:28:30 PM] Scheme registered: jar+home
[Info  - 4:28:30 PM] Scheme registered: jar+target

or

[Error - 4:28:30 PM] Unable to register scheme: zip
Error: a provider for the scheme 'zip' is already registered
[Error - 4:28:30 PM] Unable to register scheme: std
Error: a provider for the scheme 'std' is already registered
[Error - 4:28:30 PM] Unable to register scheme: memory
Error: a provider for the scheme 'memory' is already registered
[Error - 4:28:30 PM] Unable to register scheme: lib
Error: a provider for the scheme 'lib' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar
Error: a provider for the scheme 'jar' is already registered
[Error - 4:28:30 PM] Unable to register scheme: compressed
Error: a provider for the scheme 'compressed' is already registered
[Error - 4:28:30 PM] Unable to register scheme: PATH
Error: a provider for the scheme 'PATH' is already registered
[Error - 4:28:30 PM] Unable to register scheme: cwd
Error: a provider for the scheme 'cwd' is already registered
[Error - 4:28:30 PM] Unable to register scheme: tmp
Error: a provider for the scheme 'tmp' is already registered
[Error - 4:28:30 PM] Unable to register scheme: project
Error: a provider for the scheme 'project' is already registered
[Error - 4:28:30 PM] Unable to register scheme: home
Error: a provider for the scheme 'home' is already registered
[Error - 4:28:30 PM] Unable to register scheme: target
Error: a provider for the scheme 'target' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+std
Error: a provider for the scheme 'jar+std' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+file
Error: a provider for the scheme 'jar+file' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+memory
Error: a provider for the scheme 'jar+memory' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+lib
Error: a provider for the scheme 'jar+lib' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+http
Error: a provider for the scheme 'jar+http' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+https
Error: a provider for the scheme 'jar+https' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+PATH
Error: a provider for the scheme 'jar+PATH' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+cwd
Error: a provider for the scheme 'jar+cwd' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+tmp
Error: a provider for the scheme 'jar+tmp' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+project
Error: a provider for the scheme 'jar+project' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+home
Error: a provider for the scheme 'jar+home' is already registered
[Error - 4:28:30 PM] Unable to register scheme: jar+target
Error: a provider for the scheme 'jar+target' is already registered
[Warn  - 4:28:30 PM] At least one of the expected schemes failed to register.
Expected schemes: zip,std,file,memory,lib,http,jar,compressed,https,PATH,cwd,tmp,project,home,target

This does change the behavior slightly - when the original code failed to register zip, it would not attempt to register any other schemes; the async continuation would throw and be unhandled. This change will attempt to register all schemes - even if one of them fails.

@urbanfly urbanfly force-pushed the feature/avoid-zip-errors branch from b8981ee to df97a61 Compare December 20, 2024 21:44
@urbanfly urbanfly requested a review from DavyLandman December 20, 2024 22:05
Copy link
Member

@DavyLandman DavyLandman left a comment

Choose a reason for hiding this comment

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

Looks good, let's just try and reduce the logging output a bit? People get confused when there are many log messages that say something broke. (I fully understand it's useful for debugging this fix).

I like the behavior that we continue even if one failed, much nicer 👍🏼

new RascalFileSystemProvider(client).registerSchemes(schemes);
const allRegistered = new RascalFileSystemProvider(client).tryRegisterSchemes(schemes);
if (!allRegistered) {
client.warn(`At least one of the expected schemes failed to register.\nExpected schemes: ${schemes}`);
Copy link
Member

Choose a reason for hiding this comment

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

not sure if this should be a warning, as we expect races? So sometimes it happens, and that's fine?

this.client.info(`Scheme registered: ${s}`);
return true;
} catch (error) {
this.client.error(`Unable to register scheme: ${s}\n${error}`);
Copy link
Member

Choose a reason for hiding this comment

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

Can we tweak this a bit to reflect that most likely you can ignore this error?

.filter(s => !this.protectedSchemes.includes(s))
// we add support for schemes that look inside a jar
.concat(schemes
Copy link
Member

Choose a reason for hiding this comment

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

ah yes, that's nice :) just concat them 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants