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