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

Decrease input-lag for large queries #158

Merged
merged 41 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
83cd50e
proof of concept
OskarDamkjaer Dec 8, 2023
83791e1
merge main
OskarDamkjaer Dec 8, 2023
afdb51e
works again
OskarDamkjaer Dec 8, 2023
43b0980
fix overuse of main channel
OskarDamkjaer Dec 11, 2023
a5ba796
mellan
OskarDamkjaer Dec 11, 2023
52b4641
Merge branch 'main' into worker_merge_main
OskarDamkjaer Dec 20, 2023
a2f8950
tests work
OskarDamkjaer Dec 20, 2023
6fd0862
fix worker thread for node as well
OskarDamkjaer Dec 20, 2023
e83ed7f
self review
OskarDamkjaer Dec 20, 2023
fc8ece5
self review
OskarDamkjaer Dec 21, 2023
7b4e531
self review
OskarDamkjaer Dec 21, 2023
21801d3
fix build
OskarDamkjaer Dec 21, 2023
5f6fe46
tests
OskarDamkjaer Jan 3, 2024
300cbc3
fix e2e test
OskarDamkjaer Jan 3, 2024
5521f65
merge semantic analsysis and syntax errors again
OskarDamkjaer Jan 4, 2024
df31cfb
review comments
OskarDamkjaer Jan 4, 2024
d5d1734
self review
OskarDamkjaer Jan 4, 2024
6365924
fix tests
OskarDamkjaer Jan 4, 2024
7e33da2
worker pool-ish for vscode
OskarDamkjaer Jan 5, 2024
44fcd2b
smarter 'pool' mgmt
OskarDamkjaer Jan 5, 2024
b6dbbc0
kindaworks
OskarDamkjaer Jan 8, 2024
136f890
works
OskarDamkjaer Jan 9, 2024
4c94483
cleanup new parser adapter
OskarDamkjaer Jan 9, 2024
8bd4792
restore pkg json
OskarDamkjaer Jan 9, 2024
626a6e3
workerpool
OskarDamkjaer Jan 10, 2024
ba5ac35
worker pool for client as well
OskarDamkjaer Jan 10, 2024
c5a30f0
cleanup workers
OskarDamkjaer Jan 10, 2024
6e557dc
fix errors
OskarDamkjaer Jan 10, 2024
4b9fbd0
fix tests
OskarDamkjaer Jan 10, 2024
aa9ec16
fix todos
OskarDamkjaer Jan 10, 2024
6f81b7e
fix build
OskarDamkjaer Jan 10, 2024
8119bc5
test-are-green
OskarDamkjaer Jan 12, 2024
ec8bece
works for vite
OskarDamkjaer Jan 12, 2024
293406a
fixlint
OskarDamkjaer Jan 15, 2024
2850ebe
self review
OskarDamkjaer Jan 22, 2024
4652c2d
re-add proper build
OskarDamkjaer Jan 22, 2024
2ccf192
fix input lag even when parse is fast
OskarDamkjaer Jan 22, 2024
e282f70
add changeset
OskarDamkjaer Jan 22, 2024
d89924b
merge conflicts
OskarDamkjaer Jan 22, 2024
4062fce
fix e2e test
OskarDamkjaer Jan 24, 2024
cbfb3ce
pr comments
OskarDamkjaer Jan 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/pink-chefs-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@neo4j-cypher/react-codemirror-playground': patch
'@neo4j-cypher/language-support': patch
'@neo4j-cypher/react-codemirror': patch
'neo4j-cypher-vscode-extension': patch
'@neo4j-cypher/language-server': patch
---

Moves semantic analysis to a separate worker file
89 changes: 71 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@
},
"dependencies": {
"@neo4j-cypher/language-support": "2.0.0-next.2",
"lodash.debounce": "^4.0.8",
"neo4j-driver": "^5.3.0",
"vscode-languageserver": "^8.1.0",
"vscode-languageserver-textdocument": "^1.0.8"
"vscode-languageserver-textdocument": "^1.0.8",
"workerpool": "^9.0.4"
},
"scripts": {
"build": "tsc -b && npm run bundle && npm run make-executable",
Expand All @@ -47,6 +49,7 @@
"watch": "tsc -b -w"
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.9",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are the types needed here if we have the whole package in the dependencies?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The lodash project doesn't have any types, they are provided by the "@types" package from microsoft who add types to untyped libraries, that's why it's imported separately. The reason it's in devDependencies is because the types are only needed at the build step

"esbuild": "^0.19.4"
}
}
14 changes: 14 additions & 0 deletions packages/language-server/src/lint-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { validateSemantics } from '@neo4j-cypher/language-support';
OskarDamkjaer marked this conversation as resolved.
Show resolved Hide resolved
import workerpool from 'workerpool';

workerpool.worker({ validateSemantics });

type LinterArgs = Parameters<typeof validateSemantics>;

export type LinterTask = workerpool.Promise<
ReturnType<typeof validateSemantics>
>;

export type LintWorker = {
validateSemantics: (...args: LinterArgs) => LinterTask;
};
63 changes: 63 additions & 0 deletions packages/language-server/src/linting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
findEndPosition,
parserWrapper,
validateSyntax,
} from '@neo4j-cypher/language-support';
import debounce from 'lodash.debounce';
import { join } from 'path';
import { Diagnostic, TextDocumentChangeEvent } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import workerpool from 'workerpool';
import { LinterTask, LintWorker } from './lint-worker';

const pool = workerpool.pool(join(__dirname, 'lint-worker.js'), {
minWorkers: 2,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need 2 workers? I think you explained me this already.

I tried including a console.log of the pool.stats() at the beginning of the linting here and at the beginning of the semantic linting and I can see 1 occupied worker, 1 idle one at most.

{totalWorkers: 2, busyWorkers: 1, idleWorkers: 1, pendingTasks: 0, activeTasks: 1}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The intention is to always have an idle worker available, so that we can avoid the startup times of spawning a new thread. My reasoning is that when one thread is busy, we can use the idle one without startup cost and then the old busy thread will be terminated and respawned so it's ready for the next call

I've not measured the start up time/performance gains though

workerTerminateTimeout: 2000,
});

let lastSemanticJob: LinterTask | undefined;

async function rawLintDocument(
change: TextDocumentChangeEvent<TextDocument>,
sendDiagnostics: (diagnostics: Diagnostic[]) => void,
) {
const { document } = change;

const query = document.getText();
if (query.length === 0) {
return;
}

const syntaxErrors = validateSyntax(query, {});

sendDiagnostics(syntaxErrors);

if (syntaxErrors.length === 0) {
try {
if (lastSemanticJob !== undefined && !lastSemanticJob.resolved) {
void lastSemanticJob.cancel();
}

const proxyWorker = (await pool.proxy()) as unknown as LintWorker;
lastSemanticJob = proxyWorker.validateSemantics(query);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

could pass positions here to avoid doing the the findposition in the main thread

const result = await lastSemanticJob;

sendDiagnostics(
result.map((el) => findEndPosition(el, parserWrapper.parsingResult)),
);
} catch (err) {
if (!(err instanceof workerpool.Promise.CancellationError)) {
console.error(err);
}
}
}
}

export const lintDocument = debounce(rawLintDocument, 600, {
leading: false,
trailing: true,
});

export const cleanupWorkers = () => {
void pool.terminate();
};
33 changes: 17 additions & 16 deletions packages/language-server/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
createConnection,
Diagnostic,
DidChangeConfigurationNotification,
InitializeResult,
ProposedFeatures,
Expand All @@ -11,10 +12,7 @@ import {

import { TextDocument } from 'vscode-languageserver-textdocument';

import {
syntaxColouringLegend,
validateSyntax,
} from '@neo4j-cypher/language-support';
import { syntaxColouringLegend } from '@neo4j-cypher/language-support';
import { Neo4jSchemaPoller } from '@neo4j-cypher/schema-poller';
import { doAutoCompletion } from './autocompletion';
import { doSignatureHelp } from './signatureHelp';
Expand All @@ -23,6 +21,8 @@ import { Neo4jSettings } from './types';

const connection = createConnection(ProposedFeatures.all);

import { cleanupWorkers, lintDocument } from './linting';

// Create a simple text document manager.
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);

Expand Down Expand Up @@ -73,18 +73,14 @@ connection.onInitialized(() => {
);
});

// Trigger the syntactic errors highlighting on every document change
documents.onDidChangeContent((change) => {
const document = change.document;
const diagnostics = validateSyntax(
document.getText(),
neo4jSdk.metadata?.dbSchema ?? {},
);
void connection.sendDiagnostics({
uri: document.uri,
diagnostics: diagnostics,
});
});
documents.onDidChangeContent((change) =>
lintDocument(change, (diagnostics: Diagnostic[]) => {
void connection.sendDiagnostics({
uri: change.document.uri,
diagnostics,
});
}),
);

// Trigger the syntax colouring
connection.languages.semanticTokens.on(
Expand Down Expand Up @@ -121,4 +117,9 @@ connection.onDidChangeConfiguration(
);

documents.listen(connection);

connection.listen();

connection.onExit(() => {
cleanupWorkers();
});
Loading
Loading