-
Notifications
You must be signed in to change notification settings - Fork 325
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
Fixes for Language Server sync server #8514
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
c89ca73
WIP: Copy exponential backoff to `shared/`
somebody1234 1d6d324
Merge branch 'develop' into wip/sb/ls-retries
somebody1234 0a801dd
Debug logs; print some errors
somebody1234 671f135
Add more error messages
somebody1234 1c5ada3
Remove debug logs
somebody1234 b1a0c50
Add another error message
somebody1234 819206c
WIP: More fixes/cleanup
somebody1234 90f1bb9
Still cannot find bug; refactors
somebody1234 b00d99c
More refactoring
somebody1234 98d6d5b
Finally avoid crash; refactoring
somebody1234 320d115
Clean up LS error logs; add `onFailure` handler
somebody1234 3952bf6
Address review
somebody1234 de43435
Address review
somebody1234 7e14757
Merge branch 'develop' into wip/sb/ls-retries
somebody1234 2d2c9c9
Address review; fix error
somebody1234 86b9001
Remove debugging parameter from language server
somebody1234 7540924
Update root `.prettierignore`
somebody1234 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { rangeEncloses, rangeIntersects, type ContentRange } from 'shared/yjsModel' | ||
import { expect, test } from 'vitest' | ||
|
||
type RangeTest = { a: ContentRange; b: ContentRange } | ||
|
||
const equalRanges: RangeTest[] = [ | ||
{ a: [0, 0], b: [0, 0] }, | ||
{ a: [0, 1], b: [0, 1] }, | ||
{ a: [-5, 5], b: [-5, 5] }, | ||
] | ||
|
||
const totalOverlap: RangeTest[] = [ | ||
{ a: [0, 1], b: [0, 0] }, | ||
{ a: [0, 2], b: [2, 2] }, | ||
{ a: [-1, 1], b: [1, 1] }, | ||
{ a: [0, 2], b: [0, 1] }, | ||
{ a: [-10, 10], b: [-3, 7] }, | ||
{ a: [0, 5], b: [1, 2] }, | ||
{ a: [3, 5], b: [3, 4] }, | ||
] | ||
|
||
const reverseTotalOverlap: RangeTest[] = totalOverlap.map(({ a, b }) => ({ a: b, b: a })) | ||
|
||
const noOverlap: RangeTest[] = [ | ||
{ a: [0, 1], b: [2, 3] }, | ||
{ a: [0, 1], b: [-1, -1] }, | ||
{ a: [5, 6], b: [2, 3] }, | ||
{ a: [0, 2], b: [-2, -1] }, | ||
{ a: [-5, -3], b: [9, 10] }, | ||
{ a: [-3, 2], b: [3, 4] }, | ||
] | ||
|
||
const partialOverlap: RangeTest[] = [ | ||
{ a: [0, 3], b: [-1, 1] }, | ||
{ a: [0, 1], b: [-1, 0] }, | ||
{ a: [0, 0], b: [-1, 0] }, | ||
{ a: [0, 2], b: [1, 4] }, | ||
{ a: [-8, 0], b: [0, 10] }, | ||
] | ||
|
||
test.each([...equalRanges, ...totalOverlap])('Range $a should enclose $b', ({ a, b }) => | ||
expect(rangeEncloses(a, b)).toBe(true), | ||
) | ||
test.each([...noOverlap, ...partialOverlap, ...reverseTotalOverlap])( | ||
'Range $a should not enclose $b', | ||
({ a, b }) => expect(rangeEncloses(a, b)).toBe(false), | ||
) | ||
test.each([...equalRanges, ...totalOverlap, ...reverseTotalOverlap, ...partialOverlap])( | ||
'Range $a should intersect $b', | ||
({ a, b }) => expect(rangeIntersects(a, b)).toBe(true), | ||
) | ||
test.each([...noOverlap])('Range $a should not intersect $b', ({ a, b }) => | ||
expect(rangeIntersects(a, b)).toBe(false), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { wait } from 'lib0/promise' | ||
|
||
export interface BackoffOptions<E> { | ||
maxRetries?: number | ||
retryDelay?: number | ||
retryDelayMultiplier?: number | ||
retryDelayMax?: number | ||
/** Called when the promise throws an error, and the next retry is about to be attempted. | ||
* When this function returns `false`, the backoff is immediately aborted. When this function | ||
* is not provided, the backoff will always continue until the maximum number of retries | ||
* is reached. * */ | ||
onBeforeRetry?: ( | ||
error: E, | ||
retryCount: number, | ||
maxRetries: number, | ||
delay: number, | ||
) => boolean | void | ||
/** Called right before returning. */ | ||
onSuccess?: (retryCount: number) => void | ||
/** Called after the final retry, right before throwing an error. | ||
* Note that `onBeforeRetry` is *not* called on the final retry, as there is nothing after the | ||
somebody1234 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* final retry. */ | ||
onFailure?: (error: E, retryCount: number) => void | ||
} | ||
|
||
const defaultBackoffOptions: Required<BackoffOptions<unknown>> = { | ||
maxRetries: 3, | ||
retryDelay: 1000, | ||
retryDelayMultiplier: 2, | ||
retryDelayMax: 10000, | ||
onBeforeRetry: () => {}, | ||
onSuccess: () => {}, | ||
onFailure: () => {}, | ||
} | ||
|
||
/** Retry a failing promise function with exponential backoff. */ | ||
export async function exponentialBackoff<T, E>( | ||
f: () => Promise<T>, | ||
backoffOptions?: BackoffOptions<E>, | ||
): Promise<T> { | ||
const { | ||
maxRetries, | ||
retryDelay, | ||
retryDelayMultiplier, | ||
retryDelayMax, | ||
onBeforeRetry, | ||
onSuccess, | ||
onFailure, | ||
} = { | ||
...defaultBackoffOptions, | ||
...backoffOptions, | ||
} | ||
for ( | ||
let retries = 0, delay = retryDelay; | ||
; | ||
retries += 1, delay = Math.min(retryDelayMax, delay * retryDelayMultiplier) | ||
) { | ||
try { | ||
const result = await f() | ||
onSuccess(retries) | ||
return result | ||
} catch (error) { | ||
if (retries >= maxRetries) { | ||
onFailure(error as E, retries) | ||
throw error | ||
} | ||
if (onBeforeRetry(error as E, retries, maxRetries, delay) === false) throw error | ||
await wait(delay) | ||
} | ||
} | ||
} | ||
|
||
export function defaultOnBeforeRetry( | ||
description: string, | ||
): NonNullable<BackoffOptions<any>['onBeforeRetry']> { | ||
return (error, retryCount, maxRetries, delay) => { | ||
console.error( | ||
'Could not ' + | ||
description + | ||
` (${retryCount}/${maxRetries} retries), retrying after ${delay}ms...`, | ||
) | ||
console.error(error) | ||
} | ||
} | ||
|
||
export function defaultOnFailure( | ||
description: string, | ||
): NonNullable<BackoffOptions<any>['onFailure']> { | ||
return (error, retryCount) => { | ||
console.error( | ||
'Could not ' + description + ` (${retryCount}/${retryCount} retries), throwing error.`, | ||
) | ||
console.error(error) | ||
} | ||
} | ||
|
||
export function defaultOnSuccess( | ||
description: string, | ||
): NonNullable<BackoffOptions<any>['onSuccess']> { | ||
return (retryCount) => { | ||
if (retryCount === 0) return | ||
console.info( | ||
'Successfully ' + | ||
description + | ||
` after ${retryCount} ${retryCount === 1 ? 'failure' : 'failures'}.`, | ||
) | ||
} | ||
} | ||
|
||
/** @param successDescription Should be in past tense, without an initial capital letter. | ||
* @param errorDescription Should be in present tense, without an initial capital letter. */ | ||
export function printingCallbacks(successDescription: string, errorDescription: string) { | ||
return { | ||
onBeforeRetry: defaultOnBeforeRetry(errorDescription), | ||
onSuccess: defaultOnSuccess(successDescription), | ||
onFailure: defaultOnFailure(errorDescription), | ||
} satisfies BackoffOptions<unknown> | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 was the
FileReadResult
type, but improperly named. According to docs it does have a nestedcontents
field. So it was probably allright, bar the incorrect name.https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#result-5
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.
actually i discovered this was incorrect, when i was implementing this:
enso/app/gui2/ydoc-server/languageServerSession.ts
Lines 585 to 596 in 2d2c9c9
on that note... i'm not 100% sure about this. it doesn't affect the fix if it's removed - it's entirely possible that I've misunderstood what
reload
is supposed to doThere 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.
note that there is a
FileContents
type: https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#filecontentsalso note that
*Result
types were basically only added so that theResult
interfaces are valid TS code - in GUI2 these should correspond toresponse.*