commit 97ba1a3a46a1f3e1839162df339e21c951c96ad1
parent 4f57f90c265de2774990c22c21e7277024c8176a
Author: Rob Wu <rob@robwu.nl>
Date: Tue, 7 Oct 2025 16:31:07 +0000
Bug 1991950 - Hold onto buffer while IO is pending r=gstoll
Differential Revision: https://phabricator.services.mozilla.com/D267719
Diffstat:
2 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/toolkit/modules/subprocess/subprocess_shared_win.js b/toolkit/modules/subprocess/subprocess_shared_win.js
@@ -88,6 +88,7 @@ Object.assign(win32, {
ERROR_BROKEN_PIPE: 109,
ERROR_INSUFFICIENT_BUFFER: 122,
ERROR_ABANDONED_WAIT_0: 735,
+ ERROR_IO_INCOMPLETE: 996,
ERROR_IO_PENDING: 997,
FILE_ATTRIBUTE_NORMAL: 0x00000080,
diff --git a/toolkit/modules/subprocess/subprocess_win.worker.js b/toolkit/modules/subprocess/subprocess_win.worker.js
@@ -105,11 +105,20 @@ class Pipe extends BasePipe {
debug(`Failed to associate IOCP: ${ctypes.winLastError}`);
}
+ // this.buffer is set to an ArrayBuffer, which should not be reused nor
+ // released until the IO methods (ReadFile or WriteFile) have acknowledged
+ // completion, or reported an error other than ERROR_IO_PENDING.
this.buffer = null;
+ // Whether this.buffer is part of a pending IO operation.
+ this.bufferIsPendingIO = false;
+ // When close(force = true) is called while IO is pending, we notify
+ // read()/write() callers of completion but internally we await a IOCP
+ // message for this pipe before closing the pipe for real.
+ this.awaitingBufferClose = false;
}
hasPendingIO() {
- return !!this.pending.length;
+ return !!this.pending.length || this.bufferIsPendingIO;
}
maybeClose() {}
@@ -138,6 +147,12 @@ class Pipe extends BasePipe {
}
this.pending.length = 0;
+ if ((this.bufferIsPendingIO &&= this.#checkIfBufferIsStillPendingIO())) {
+ // We cannot release the pipe (specifically this.buffer) until the
+ // pending ReadFile/WriteFile operation on the buffer completed.
+ this.awaitingBufferClose = true;
+ return this.closedPromise;
+ }
this.buffer = null;
if (!this.closed) {
@@ -161,6 +176,18 @@ class Pipe extends BasePipe {
onError() {
this.close(true);
}
+
+ #checkIfBufferIsStillPendingIO() {
+ let numberOfBytesTransferred = win32.DWORD();
+ let ok = libc.GetOverlappedResult(
+ this.handle,
+ this.overlapped.address(),
+ numberOfBytesTransferred.address(),
+ false
+ );
+ // Ok or error other than ERROR_IO_INCOMPLETE means that I/O completed.
+ return !ok && ctypes.winLastError === win32.ERROR_IO_INCOMPLETE;
+ }
}
class InputPipe extends Pipe {
@@ -189,7 +216,7 @@ class InputPipe extends Pipe {
false
);
- if (!ok) {
+ if (!ok && ctypes.winLastError !== win32.ERROR_IO_INCOMPLETE) {
this.onError();
}
}
@@ -226,6 +253,7 @@ class InputPipe extends Pipe {
*/
readBuffer(count) {
this.buffer = new ArrayBuffer(count);
+ this.bufferIsPendingIO = true;
let ok = libc.ReadFile(
this.handle,
@@ -239,6 +267,7 @@ class InputPipe extends Pipe {
!ok &&
(!this.process.handle || ctypes.winLastError !== win32.ERROR_IO_PENDING)
) {
+ this.bufferIsPendingIO = ctypes.winLastError === win32.ERROR_IO_PENDING;
this.onError();
} else {
io.updatePollEvents();
@@ -326,6 +355,7 @@ class OutputPipe extends Pipe {
*/
writeBuffer(buffer) {
this.buffer = buffer;
+ this.bufferIsPendingIO = true;
let ok = libc.WriteFile(
this.handle,
@@ -336,6 +366,7 @@ class OutputPipe extends Pipe {
);
if (!ok && ctypes.winLastError !== win32.ERROR_IO_PENDING) {
+ this.bufferIsPendingIO = false;
this.onError();
} else {
io.updatePollEvents();
@@ -849,7 +880,8 @@ io = {
debug(`IOCP notification for unknown pipe: ${pipeId}`);
continue;
}
- if (deqWinErr === win32.ERROR_BROKEN_PIPE) {
+ pipe.bufferIsPendingIO = false;
+ if (deqWinErr === win32.ERROR_BROKEN_PIPE || pipe.awaitingBufferClose) {
pipe.onError();
continue;
}