Skip to content

Commit

Permalink
[#15] run quickjs in worker threads
Browse files Browse the repository at this point in the history
  • Loading branch information
tamayika committed Jul 17, 2024
1 parent f32cd16 commit c29da90
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 47 deletions.
44 changes: 43 additions & 1 deletion package-lock.json

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

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"repository": {
"url": "https://github.com/tamayika/vscode-any-lint"
},
"version": "0.3.1",
"version": "0.3.2",
"engines": {
"vscode": "^1.91.0"
},
Expand Down Expand Up @@ -244,8 +244,9 @@
"typescript": "^5.5.3"
},
"dependencies": {
"@jitl/quickjs-singlefile-cjs-release-sync": "^0.29.2",
"js-yaml": "^4.1.0",
"quickjs-emscripten": "^0.29.2",
"quickjs-emscripten-core": "^0.29.2",
"quickjs-emscripten-sync": "^1.5.2"
}
}
9 changes: 5 additions & 4 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ export class AnyAction implements vscode.CodeActionProvider {

private async createOpenUriCodeAction(diagnostic: Diagnostic, diagnosticAction: DiagnosticActionOpenUri) {
const title = await this.safeEval(diagnosticAction.title, diagnostic);
if (title === undefined) {
if (title === undefined || typeof title !== "string") {
return;
}
const uri = await this.safeEval(diagnosticAction.uri, diagnostic);
if (uri === undefined) {
if (uri === undefined || typeof uri !== "string") {
return;
}
const action = new vscode.CodeAction(title);
Expand All @@ -77,10 +77,11 @@ export class AnyAction implements vscode.CodeActionProvider {
if (title === undefined || typeof title !== "string") {
return;
}
let comment = await this.safeEval(diagnosticAction.comment, diagnostic);
if (comment === undefined || typeof comment !== "string") {
const commentResult = await this.safeEval(diagnosticAction.comment, diagnostic);
if (commentResult === undefined || typeof commentResult !== "string") {
return;
}
let comment = commentResult;
const action = new vscode.CodeAction(title);
let location: vscode.Range | undefined;
const eol = getDocumentEol(document);
Expand Down
20 changes: 13 additions & 7 deletions src/diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export async function convertResultToDiagnostic(document: vscode.TextDocument, r
return [];
}

function convertResultToDiagnosticByLines(document:vscode.TextDocument, result: string, diagnosticConfiguration: Required<DiagnosticConfigurationLines>, context: Context) {
function convertResultToDiagnosticByLines(document: vscode.TextDocument, result: string, diagnosticConfiguration: Required<DiagnosticConfigurationLines>, context: Context) {
let diagnosticStrings: string[] = [result];
switch (diagnosticConfiguration.type) {
case DiagnosticType.lines:
Expand Down Expand Up @@ -162,7 +162,7 @@ async function convertResultToDiagnosticByObject(document: vscode.TextDocument,
}
for (const resultDiagnostic of resultDiagnostics) {
const file = await safeEval(selectors.file, resultDiagnostic);
if (!file) {
if (!file || typeof file !== "string") {
continue;
}
if (selectors.subDiagnostics) {
Expand Down Expand Up @@ -190,10 +190,13 @@ async function convertResultToDiagnosticByObject(document: vscode.TextDocument,
async function convertDiagnosticObject(document: vscode.TextDocument, result: any, diagnosticConfiguration: Required<DiagnosticConfigurationJSON | DiagnosticConfigurationYAML>, context: Context, parentFile: string) {
const selectors = diagnosticConfiguration.selectors;
const file = await safeEval(selectors.file, result) || parentFile;
if (!file) {
if (!file || typeof file !== "string") {
return;
}
const message = selectors.message ? await safeEval(selectors.message, result) : "";
if (!message || typeof message !== "string") {
return;
}
let startLine = await safeEval(selectors.startLine, result);
if (typeof startLine !== "number") {
return;
Expand Down Expand Up @@ -225,22 +228,25 @@ async function convertDiagnosticObject(document: vscode.TextDocument, result: an
return;
}
if (endColumn === undefined) {
endColumn = document.lineAt(endLine).text.length;
endColumn = document.lineAt(endLine as number).text.length;
} else {
if (!diagnosticConfiguration.columnZeroBased) {
endColumn--;
}
if (!diagnosticConfiguration.columnCharacterBased) {
endColumn = byteBasedToCharacterBased(document.lineAt(endLine).text, endColumn);
endColumn = byteBasedToCharacterBased(document.lineAt(endLine as number).text, endColumn);
}
if (diagnosticConfiguration.endColumnInclusive) {
endColumn++;
(endColumn as number)++;
}
}
const severity = selectors.severity ? await safeEval(selectors.severity, result) : undefined;
if (typeof severity !== "string" && typeof severity !== "undefined") {
return;
}
return new Diagnostic(
file,
new vscode.Range(startLine, startColumn, endLine, endColumn),
new vscode.Range(startLine, startColumn as number, endLine as number, endColumn as number),
message,
diagnosticSeverityMap[severity ? diagnosticConfiguration.severityMap[severity] : diagnosticConfiguration.severity],
diagnosticConfiguration,
Expand Down
87 changes: 61 additions & 26 deletions src/eval.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,70 @@
import { Arena } from "quickjs-emscripten-sync";
import * as path from "path";
import { Worker } from "worker_threads";
import * as url from "url";
import { Context } from "./context";
import { getQuickJS, QuickJSWASMModule } from "quickjs-emscripten";

let quickJS: QuickJSWASMModule | undefined;
const worker = new Worker(
url.pathToFileURL(path.join(__dirname, "/worker/quickjs-worker.js"))
);

export async function safeEval(code: string, $: Context) {
const quickJS = await ensureQuickJS();
const ctx = quickJS.newContext();
const area = new Arena(ctx, { isMarshalable: true });
area.expose({ $: $ });
const ret = area.evalCode(code);
area.dispose();
ctx.dispose();
return ret;
let id = 0;
const idPromises = new Map<number, { resolve: (_: unknown) => void, reject: (_: unknown) => void }>();

function setIdPromise(id: number, resolve: (_: unknown) => void, reject: (_: unknown) => void) {
idPromises.set(id, { resolve, reject });
}

export async function safeEvalDiagnosticAction(code: string, $: Context, $$: unknown) {
const quickJS = await ensureQuickJS();
const ctx = quickJS.newContext();
const area = new Arena(ctx, { isMarshalable: true });
area.expose({ $: $, $$: $$ });
const ret = area.evalCode(code);
area.dispose();
ctx.dispose();
return ret;
function getIdPromise(id: number): { resolve: (_: unknown) => void, reject: (_: unknown) => void } | undefined {
const promise = idPromises.get(id);
if (!promise) {
return;
}
idPromises.delete(id);
return promise;
}

async function ensureQuickJS() {
if (quickJS !== undefined) {
return quickJS;
worker.on("message", ((payload: { id: number, result: unknown } | { id: number, error: unknown }) => {
const promise = getIdPromise(payload.id);
if (!promise) {
return;
}
if ("result" in payload) {
promise.resolve(payload.result);
} else if ("error" in payload) {
promise.reject(payload.error);
}
quickJS = await getQuickJS();
return quickJS;
}));


export async function safeEval(code: string, $: Context) {
const currentId = id++;
const promise = new Promise<unknown>((resolve, reject) => {
setIdPromise(currentId, resolve, reject);
});
worker.postMessage({
id: currentId,
type: "safeEval",
payload: {
code,
$,
},
});
return promise;
}

export async function safeEvalDiagnosticAction(code: string, $: Context, $$: unknown) {
const currentId = id++;
const promise = new Promise<unknown>((resolve, reject) => {
setIdPromise(currentId, resolve, reject);
});
worker.postMessage({
id: currentId,
type: "safeEvalDiagnosticAction",
payload: {
code,
$,
$$,
},
});
return promise;
}
7 changes: 0 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import * as vscode from 'vscode';
import { AnyAction } from './action';
import { ignoreCommand, openUriCommand, Commands, runCommand } from './command';
import { Linter } from './linter';
import { getQuickJS } from "quickjs-emscripten";
import { Arena } from "quickjs-emscripten-sync";

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
Expand All @@ -27,11 +25,6 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand(
runCommand, commands.run
));
(async () => {
const ctx = (await getQuickJS()).newContext();
const arena = new Arena(ctx, { isMarshalable: true });
console.log(arena.evalCode("1 + 1"));
})();
}

// this method is called when your extension is deactivated
Expand Down
Loading

0 comments on commit c29da90

Please sign in to comment.