From 9e38473a47d066907cacb7d43e33c176268ecde9 Mon Sep 17 00:00:00 2001 From: uki00a Date: Mon, 19 Apr 2021 00:14:42 +0900 Subject: [PATCH] fix: Prevent the "operation canceled" error to occur --- node/child_process.ts | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/node/child_process.ts b/node/child_process.ts index b2034ada6ba6..cee81ee596d5 100644 --- a/node/child_process.ts +++ b/node/child_process.ts @@ -128,8 +128,8 @@ export class ChildProcess extends EventEmitter { this.#spawned.then(async () => { // The 'exit' and 'close' events must be emitted after the 'spawn' event. this.emit("exit", this.exitCode, status.signal ?? null); - this.kill(); await this._waitForChildStreamsToClose(); + this.kill(); this.emit("close", this.exitCode, status.signal ?? null); }); })(); @@ -153,7 +153,6 @@ export class ChildProcess extends EventEmitter { if (this.#process.stdin) { assert(this.stdin); ensureClosed(this.#process.stdin); - this.stdin.destroy(); } if (this.#process.stdout) { ensureClosed(this.#process.stdout); @@ -177,21 +176,15 @@ export class ChildProcess extends EventEmitter { private async _waitForChildStreamsToClose(): Promise { const promises = [] as Array>; if (this.stdin && !this.stdin.destroyed) { - const promise = deferred(); - this.stdin.once("close", () => promise.resolve()); - promises.push(promise); + assert(this.stdin); + this.stdin.destroy(); + promises.push(waitForStreamToClose(this.stdin)); } if (this.stdout && !this.stdout.destroyed) { - const promise = deferred(); - this.stdout.resume(); // Ensure bufferred data will be consumed. - this.stdout.once("close", () => promise.resolve()); - promises.push(promise); + promises.push(waitForReadableToClose(this.stdout)); } if (this.stderr && !this.stderr.destroyed) { - const promise = deferred(); - this.stderr.resume(); // Ensure buffered data will be consumed. - this.stderr.once("close", () => promise.resolve()); - promises.push(promise); + promises.push(waitForReadableToClose(this.stderr)); } await Promise.all(promises); } @@ -419,6 +412,30 @@ function normalizeStdioOption( } } +function waitForReadableToClose(readable: Readable): Promise { + readable.resume(); // Ensure bufferred data will be consumed. + return waitForStreamToClose(readable); +} + +function waitForStreamToClose(stream: Stream): Promise { + const promise = deferred(); + const cleanup = () => { + stream.removeListener("close", onClose); + stream.removeListener("error", onError); + }; + const onClose = () => { + cleanup(); + promise.resolve(); + }; + const onError = (err: Error) => { + cleanup(); + promise.reject(err); + }; + stream.once("close", onClose); + stream.once("error", onError); + return promise; +} + /** * This function is based on https://github.com/nodejs/node/blob/fc6426ccc4b4cb73076356fb6dbf46a28953af01/lib/child_process.js#L504-L528. * Copyright Joyent, Inc. and other Node contributors. All rights reserved. MIT license.