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