Skip to content

Commit a99ee84

Browse files
committed
Refactor to use abortController
1 parent 200460f commit a99ee84

1 file changed

Lines changed: 99 additions & 71 deletions

File tree

server/src/incrementalCompilation.ts

Lines changed: 99 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as utils from "./utils";
44
import { performance } from "perf_hooks";
55
import * as p from "vscode-languageserver-protocol";
66
import * as cp from "node:child_process";
7+
import { promisify } from "node:util";
78
import semver from "semver";
89
import * as os from "os";
910
import config, { send } from "./config";
@@ -16,6 +17,8 @@ import { getCurrentCompilerDiagnosticsForFile } from "./server";
1617
import { NormalizedPath } from "./utils";
1718
import { getLogger } from "./logger";
1819

20+
const execFilePromise = promisify(cp.execFile);
21+
1922
const INCREMENTAL_FOLDER_NAME = "___incremental";
2023
const INCREMENTAL_FILE_FOLDER_LOCATION = path.join(
2124
c.compilerDirPartialPath,
@@ -59,8 +62,8 @@ export type IncrementallyCompiledFileInfo = {
5962
/** The trigger token for the currently active compilation. */
6063
triggerToken: number;
6164
} | null;
62-
/** Listeners for when compilation of this file is killed. List always cleared after each invocation. */
63-
killCompilationListeners: Array<() => void>;
65+
/** Mechanism to kill the currently active compilation. */
66+
abortCompilation: (() => void) | null;
6467
/** Project specific information. */
6568
project: {
6669
/** The root path of the project (normalized to match projectsFiles keys). */
@@ -86,6 +89,19 @@ const hasReportedFeatureFailedError: Set<NormalizedPath> = new Set();
8689
const originalTypeFileToFilePath: Map<NormalizedPath, NormalizedPath> =
8790
new Map();
8891

92+
/**
93+
* Cancels the currently active compilation for an entry.
94+
* Clears the timeout, aborts the compilation, and resets state.
95+
*/
96+
function cancelActiveCompilation(entry: IncrementallyCompiledFileInfo): void {
97+
if (entry.compilation != null) {
98+
clearTimeout(entry.compilation.timeout);
99+
entry.abortCompilation?.();
100+
entry.compilation = null;
101+
entry.abortCompilation = null;
102+
}
103+
}
104+
89105
export function incrementalCompilationFileChanged(changedPath: NormalizedPath) {
90106
const filePath = originalTypeFileToFilePath.get(changedPath);
91107
if (filePath != null) {
@@ -96,9 +112,7 @@ export function incrementalCompilationFileChanged(changedPath: NormalizedPath) {
96112
);
97113
if (entry.compilation != null) {
98114
getLogger().log("[watcher] Was compiling, killing");
99-
clearTimeout(entry.compilation.timeout);
100-
entry.killCompilationListeners.forEach((cb) => cb());
101-
entry.compilation = null;
115+
cancelActiveCompilation(entry);
102116
}
103117
cleanUpIncrementalFiles(
104118
entry.file.sourceFilePath,
@@ -335,7 +349,7 @@ function triggerIncrementalCompilationOfFile(
335349
buildRewatch: null,
336350
buildNinja: null,
337351
compilation: null,
338-
killCompilationListeners: [],
352+
abortCompilation: null,
339353
codeActions: [],
340354
};
341355

@@ -352,25 +366,16 @@ function triggerIncrementalCompilationOfFile(
352366

353367
if (incrementalFileCacheEntry == null) return;
354368
const entry = incrementalFileCacheEntry;
355-
if (entry.compilation != null) {
356-
clearTimeout(entry.compilation.timeout);
357-
entry.killCompilationListeners.forEach((cb) => cb());
358-
entry.killCompilationListeners = [];
359-
}
369+
cancelActiveCompilation(entry);
360370
const triggerToken = performance.now();
361371
const timeout = setTimeout(() => {
362372
compileContents(entry, fileContent, send, onCompilationFinished);
363373
}, 20);
364374

365-
if (entry.compilation != null) {
366-
entry.compilation.timeout = timeout;
367-
entry.compilation.triggerToken = triggerToken;
368-
} else {
369-
entry.compilation = {
370-
timeout,
371-
triggerToken,
372-
};
373-
}
375+
entry.compilation = {
376+
timeout,
377+
triggerToken,
378+
};
374379
}
375380
function verifyTriggerToken(
376381
filePath: NormalizedPath,
@@ -689,66 +694,89 @@ async function compileContents(
689694
entry.buildSystem === "bsb"
690695
? entry.project.rootPath
691696
: path.resolve(entry.project.rootPath, c.compilerDirPartialPath);
697+
692698
getLogger().log(
693699
`About to invoke bsc from \"${cwd}\", used ${entry.buildSystem}`,
694700
);
695701
getLogger().log(
696702
`${entry.project.bscBinaryLocation} ${callArgs.map((c) => `"${c}"`).join(" ")}`,
697703
);
698-
const process = cp.execFile(
699-
entry.project.bscBinaryLocation,
700-
callArgs,
701-
{ cwd },
702-
async (error, _stdout, stderr) => {
703-
if (!error?.killed) {
704-
getLogger().log(
705-
`Recompiled ${entry.file.sourceFileName} in ${
706-
(performance.now() - startTime) / 1000
707-
}s`,
708-
);
709-
} else {
710-
getLogger().log(
711-
`Compilation of ${entry.file.sourceFileName} was killed.`,
712-
);
713-
}
714-
let hasIgnoredErrorMessages = false;
715-
if (
716-
!error?.killed &&
717-
triggerToken != null &&
718-
verifyTriggerToken(entry.file.sourceFilePath, triggerToken)
719-
) {
720-
getLogger().log("Resetting compilation status.");
721-
// Reset compilation status as this compilation finished
722-
entry.compilation = null;
723-
const { result, codeActions } = await utils.parseCompilerLogOutput(
724-
`${stderr}\n#Done()`,
725-
);
726704

727-
// reverify: Token may have changed during the await above
728-
if (!verifyTriggerToken(entry.file.sourceFilePath, triggerToken)) {
729-
getLogger().log(
730-
`Discarding stale compilation results for ${entry.file.sourceFileName} (token mismatch after parsing)`,
731-
);
732-
return;
733-
}
734-
735-
processAndPublishDiagnostics(
736-
entry,
737-
result,
738-
codeActions,
739-
stderr,
740-
callArgs,
741-
send,
742-
);
743-
}
744-
onCompilationFinished?.();
745-
},
746-
);
747-
entry.killCompilationListeners.push(() => {
748-
process.kill("SIGKILL");
749-
});
705+
// Create AbortController for this compilation
706+
const abortController = new AbortController();
707+
const { signal } = abortController;
708+
709+
// Store abort function directly on the entry
710+
entry.abortCompilation = () => {
711+
getLogger().log(`Aborting compilation of ${entry.file.sourceFileName}`);
712+
abortController.abort();
713+
};
714+
715+
try {
716+
const { stdout, stderr } = await execFilePromise(
717+
entry.project.bscBinaryLocation,
718+
callArgs,
719+
{ cwd, signal },
720+
);
721+
722+
getLogger().log(
723+
`Recompiled ${entry.file.sourceFileName} in ${
724+
(performance.now() - startTime) / 1000
725+
}s`,
726+
);
727+
728+
// Verify token after async operation
729+
if (
730+
triggerToken != null &&
731+
!verifyTriggerToken(entry.file.sourceFilePath, triggerToken)
732+
) {
733+
getLogger().log(
734+
`Discarding stale compilation results for ${entry.file.sourceFileName} (token changed)`,
735+
);
736+
return;
737+
}
738+
739+
getLogger().log("Resetting compilation status.");
740+
// Reset compilation status as this compilation finished
741+
entry.compilation = null;
742+
entry.abortCompilation = null;
743+
744+
const { result, codeActions } = await utils.parseCompilerLogOutput(
745+
`${stderr}\n#Done()`,
746+
);
747+
748+
// Re-verify again after second async operation
749+
if (
750+
triggerToken != null &&
751+
!verifyTriggerToken(entry.file.sourceFilePath, triggerToken)
752+
) {
753+
getLogger().log(
754+
`Discarding stale compilation results for ${entry.file.sourceFileName} (token changed after parsing)`,
755+
);
756+
return;
757+
}
758+
759+
processAndPublishDiagnostics(
760+
entry,
761+
result,
762+
codeActions,
763+
stderr,
764+
callArgs,
765+
send,
766+
);
767+
} catch (error: any) {
768+
if (error.name === "AbortError") {
769+
getLogger().log(
770+
`Compilation of ${entry.file.sourceFileName} was aborted.`,
771+
);
772+
} else {
773+
throw error;
774+
}
775+
}
750776
} catch (e) {
751777
console.error(e);
778+
} finally {
779+
onCompilationFinished?.();
752780
}
753781
}
754782

0 commit comments

Comments
 (0)