1/// <reference types="node"/> 2 3import fs = require("fs"); 4 5interface ServerCancellationToken { 6 isCancellationRequested(): boolean; 7 setRequest(requestId: number): void; 8 resetRequest(requestId: number): void; 9} 10 11function pipeExists(name: string): boolean { 12 // Unlike statSync, existsSync doesn't throw an exception if the target doesn't exist. 13 // A comment in the node code suggests they're stuck with that decision for back compat 14 // (https://github.com/nodejs/node/blob/9da241b600182a9ff400f6efc24f11a6303c27f7/lib/fs.js#L222). 15 // Caveat: If a named pipe does exist, the first call to existsSync will return true, as for 16 // statSync. Subsequent calls will return false, whereas statSync would throw an exception 17 // indicating that the pipe was busy. The difference is immaterial, since our statSync 18 // implementation returned false from its catch block. 19 return fs.existsSync(name); 20} 21 22function createCancellationToken(args: string[]): ServerCancellationToken { 23 let cancellationPipeName: string | undefined; 24 for (let i = 0; i < args.length - 1; i++) { 25 if (args[i] === "--cancellationPipeName") { 26 cancellationPipeName = args[i + 1]; 27 break; 28 } 29 } 30 if (!cancellationPipeName) { 31 return { 32 isCancellationRequested: () => false, 33 setRequest: (_requestId: number): void => void 0, 34 resetRequest: (_requestId: number): void => void 0 35 }; 36 } 37 // cancellationPipeName is a string without '*' inside that can optionally end with '*' 38 // when client wants to signal cancellation it should create a named pipe with name=<cancellationPipeName> 39 // server will synchronously check the presence of the pipe and treat its existence as indicator that current request should be canceled. 40 // 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. 41 // in this case pipe name will be build dynamically as <cancellationPipeName><request_seq>. 42 if (cancellationPipeName.charAt(cancellationPipeName.length - 1) === "*") { 43 const namePrefix = cancellationPipeName.slice(0, -1); 44 if (namePrefix.length === 0 || namePrefix.indexOf("*") >= 0) { 45 throw new Error("Invalid name for template cancellation pipe: it should have length greater than 2 characters and contain only one '*'."); 46 } 47 let perRequestPipeName: string | undefined; 48 let currentRequestId: number; 49 return { 50 isCancellationRequested: () => perRequestPipeName !== undefined && pipeExists(perRequestPipeName), 51 setRequest(requestId: number) { 52 currentRequestId = requestId; 53 perRequestPipeName = namePrefix + requestId; 54 }, 55 resetRequest(requestId: number) { 56 if (currentRequestId !== requestId) { 57 throw new Error(`Mismatched request id, expected ${currentRequestId}, actual ${requestId}`); 58 } 59 perRequestPipeName = undefined; 60 } 61 }; 62 } 63 else { 64 return { 65 isCancellationRequested: () => pipeExists(cancellationPipeName!), // TODO: GH#18217 66 setRequest: (_requestId: number): void => void 0, 67 resetRequest: (_requestId: number): void => void 0 68 }; 69 } 70} 71export = createCancellationToken; 72