• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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