@@ -4,6 +4,7 @@ import * as utils from "./utils";
44import { performance } from "perf_hooks" ;
55import * as p from "vscode-languageserver-protocol" ;
66import * as cp from "node:child_process" ;
7+ import { promisify } from "node:util" ;
78import semver from "semver" ;
89import * as os from "os" ;
910import config , { send } from "./config" ;
@@ -16,6 +17,8 @@ import { getCurrentCompilerDiagnosticsForFile } from "./server";
1617import { NormalizedPath } from "./utils" ;
1718import { getLogger } from "./logger" ;
1819
20+ const execFilePromise = promisify ( cp . execFile ) ;
21+
1922const INCREMENTAL_FOLDER_NAME = "___incremental" ;
2023const 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();
8689const 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+
89105export 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}
375380function 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