@@ -275,14 +275,7 @@ export function initializeNodeSystem(): StartInput {
275275 sys . gc = ( ) => global . gc ?.( ) ;
276276 }
277277
278- let cancellationToken : ts . server . ServerCancellationToken ;
279- try {
280- const factory = require ( "./cancellationToken.js" ) ;
281- cancellationToken = factory ( sys . args ) ;
282- }
283- catch {
284- cancellationToken = ts . server . nullCancellationToken ;
285- }
278+ const cancellationToken = createCancellationToken ( sys . args ) ;
286279
287280 const localeStr = ts . server . findArgument ( "--locale" ) ;
288281 if ( localeStr ) {
@@ -668,3 +661,60 @@ function startNodeSession(options: StartSessionOptions, logger: ts.server.Logger
668661 return combinePaths ( normalizeSlashes ( homePath ) , cacheFolder ) ;
669662 }
670663}
664+
665+ function pipeExists ( name : string ) : boolean {
666+ // Unlike statSync, existsSync doesn't throw an exception if the target doesn't exist.
667+ // A comment in the node code suggests they're stuck with that decision for back compat
668+ // (https://github.com/nodejs/node/blob/9da241b600182a9ff400f6efc24f11a6303c27f7/lib/fs.js#L222).
669+ // Caveat: If a named pipe does exist, the first call to existsSync will return true, as for
670+ // statSync. Subsequent calls will return false, whereas statSync would throw an exception
671+ // indicating that the pipe was busy. The difference is immaterial, since our statSync
672+ // implementation returned false from its catch block.
673+ return fs . existsSync ( name ) ;
674+ }
675+
676+ function createCancellationToken ( args : string [ ] ) : ts . server . ServerCancellationToken {
677+ let cancellationPipeName : string | undefined ;
678+ for ( let i = 0 ; i < args . length - 1 ; i ++ ) {
679+ if ( args [ i ] === "--cancellationPipeName" ) {
680+ cancellationPipeName = args [ i + 1 ] ;
681+ break ;
682+ }
683+ }
684+ if ( ! cancellationPipeName ) {
685+ return ts . server . nullCancellationToken ;
686+ }
687+ // cancellationPipeName is a string without '*' inside that can optionally end with '*'
688+ // when client wants to signal cancellation it should create a named pipe with name=<cancellationPipeName>
689+ // server will synchronously check the presence of the pipe and treat its existence as indicator that current request should be canceled.
690+ // in case if client prefers to use more fine-grained schema than one name for all request it can add '*' to the end of cancellationPipeName.
691+ // in this case pipe name will be build dynamically as <cancellationPipeName><request_seq>.
692+ if ( cancellationPipeName . charAt ( cancellationPipeName . length - 1 ) === "*" ) {
693+ const namePrefix = cancellationPipeName . slice ( 0 , - 1 ) ;
694+ if ( namePrefix . length === 0 || namePrefix . includes ( "*" ) ) {
695+ throw new Error ( "Invalid name for template cancellation pipe: it should have length greater than 2 characters and contain only one '*'." ) ;
696+ }
697+ let perRequestPipeName : string | undefined ;
698+ let currentRequestId : number ;
699+ return {
700+ isCancellationRequested : ( ) => perRequestPipeName !== undefined && pipeExists ( perRequestPipeName ) ,
701+ setRequest ( requestId : number ) {
702+ currentRequestId = requestId ;
703+ perRequestPipeName = namePrefix + requestId ;
704+ } ,
705+ resetRequest ( requestId : number ) {
706+ if ( currentRequestId !== requestId ) {
707+ throw new Error ( `Mismatched request id, expected ${ currentRequestId } , actual ${ requestId } ` ) ;
708+ }
709+ perRequestPipeName = undefined ;
710+ } ,
711+ } ;
712+ }
713+ else {
714+ return {
715+ isCancellationRequested : ( ) => pipeExists ( cancellationPipeName ) ,
716+ setRequest : ( _requestId : number ) : void => void 0 ,
717+ resetRequest : ( _requestId : number ) : void => void 0 ,
718+ } ;
719+ }
720+ }
0 commit comments