@@ -275,14 +275,7 @@ export function initializeNodeSystem(): StartInput {
275
275
sys . gc = ( ) => global . gc ?.( ) ;
276
276
}
277
277
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 ) ;
286
279
287
280
const localeStr = ts . server . findArgument ( "--locale" ) ;
288
281
if ( localeStr ) {
@@ -668,3 +661,60 @@ function startNodeSession(options: StartSessionOptions, logger: ts.server.Logger
668
661
return combinePaths ( normalizeSlashes ( homePath ) , cacheFolder ) ;
669
662
}
670
663
}
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