-
Notifications
You must be signed in to change notification settings - Fork 317
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
Automatic reconnect with Language Server. #9691
Conversation
app/gui2/shared/dataServer.ts
Outdated
/// <reference lib="DOM" /> | ||
/* eslint-env browser */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this correct? This ordinarily will in node or JVM, so using DOM types seems invalid.
}) | ||
} | ||
|
||
private scheduleInitializationAfterConnect() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be scheduleConnectAndInitialize
? The current name doesn't actually communicate that this does the "connect" part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this is valid: this function does not connect, because it does not make the reconnect (the websocket underneath does), only listens for "open" event to start initialization after.
app/gui2/shared/languageServer.ts
Outdated
throw new LsRpcError(error, method, params) | ||
return Err(new LsRpcError(error, method, params)) | ||
} else { | ||
return Err(new LsRpcError(`Unspecified error: ${error}`, method, params)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For errors that we actually don't expect to ever happen, I'd rather keep this as a throw error
, treating it basically as a panic
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we expect some problems not being rpc responses (network errors, timeouts), and there is no documentation about those in open-rpc (at least I cannot find any), so I think it's save to catch all Errors
. non-Error values I will rethrow.
@@ -254,6 +254,9 @@ test('Visualization preview: user visualization selection', async ({ page }) => | |||
await input.fill('4') | |||
await expect(input).toHaveValue('4') | |||
await expect(locate.jsonVisualization(page)).toExist() | |||
// Before picking table vis, we must make sure we wan't be hit by debounced visualization update | |||
// during the process. | |||
await page.waitForTimeout(250) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would strongly prefer any other way to check for expected final state than by doing a timeout. The timeout times quickly add up to a significant total testing runtime, and also it has a high probability of false negatives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found another way; the preview presents now the expression, so we may wait until it shows the actually written code. I also fixed the bug where the previews always interpreted received object as a string.
groups.error.log('Cannot read component groups') | ||
console.error('Continuing without gruops') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those two logs could be merged, or one can be removed.
console.error(`Unexpected LS connection error:`, error) | ||
}) | ||
transport.on('error', (error) => console.error('Language Server transport error:', error)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is significant, but this event handler is not being cleared in the onAbort
.
I would actually recommend using handleObserve
or similar method to register such handlers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case the transport is "owned" by LanguageServer object and it's closed on dispose; it should not be used anywhere else (and if it is, or there are errors after closing, we ought to see them).
@@ -344,6 +344,8 @@ export type Notifications = { | |||
'file/rootAdded': (param: {}) => void | |||
'file/rootRemoved': (param: {}) => void | |||
'refactoring/projectRenamed': (param: {}) => void | |||
'transport/closed': () => void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are those actual notifications related to language server protocol? If not, I would prefer those to be in a separate type definition. It can still be merged at the usage point as the ObservableV2
parameter.
Applied review and fix some tests. Fix the display of visualization preview, by the way. Now I have problems with AI completion test, but I think the code is ready to re-review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few last small nits.
@@ -39,7 +39,7 @@ onMounted(() => setTimeout(() => rootNode.value?.querySelector('button')?.focus( | |||
<div | |||
ref="rootNode" | |||
class="VisualizationSelector" | |||
@focusout="$event.relatedTarget == null && emit('hide')" | |||
@focusout="$event.relatedTarget == null && (console.log('FOCUSOUT', new Error()), emit('hide'))" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stray log
app/gui2/shared/util/data/result.ts
Outdated
| { ok: true; value: T } | ||
| { ok: false; error: ResultError<E> } | ||
|
||
export function Ok<T>(data: T): Result<T, never> { | ||
export function Ok(): Result<void, never> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be undefined
, not void
. We actually guarantee that return type is undefined. void
is not exactly the same as undefined
when used in function return position, it should be treated more like "the return type can be anything, and you shouldn't look at it". We do use Promise<void>
as a return from many functions already, but there that semantic mostly makes sense (e.g. in LS requests, which we kinda assume return nothing interesting, but no guarantees what it exactly is).
See https://www.typescriptlang.org/docs/handbook/2/functions.html#assignability-of-functions
Today's typescript is a bit more strict about this type than it used to, but there are still some subtle ways where it can bite you.
projectStore.executionContext.on('executionComplete', () => toastExecutionFailed.dismiss()) | ||
projectStore.lsRpcConnection.client.onError((e) => | ||
toastLspError.show(`Language server error: ${e}`), | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove comma and reformat
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is the reformatter who puts this comma.
this.state satisfies never | ||
return Ok() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it was originally like that, but it would be better to use assertNever
here. Now we have it in shared
, so it shouldn't be a problem to change.
}) | ||
} | ||
default: { | ||
this.state satisfies never |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another place for assertNever
.
Pull Request Description
Fixes #8520
If the websocket is closed not by us, we automatically try to reconnect with it, and initialize the protocol again. Restoring state (execution contexts, attached visualizations) is not part of this PR.
It's a part of making IDE work after hibernation (or LS crash).
Important Notes
It required somewhat heavy refactoring:
Promise<LanguageServer>
- each method will just wait for (re)connection and initialization.net
src's module was partially moved to shared's counterpart (with tests). MergedexponentialBackoff
implementations, which also brought me toChecklist
Please ensure that the following checklist has been satisfied before submitting the PR:
[ ] Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.Scala,
Java,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.
[ ] Unit tests have been written where possible../run ide build
.