Improve streamToBuffer error handling and memory safety (#17255)

## Description

This PR improves error handling and memory safety for the streamToBuffer
utility function.

### Changes
- Add proper cleanup of event listeners to prevent memory leaks
- Add checks for already-ended streams to handle edge cases gracefully
- Add protection against multiple resolve/reject calls with isResolved
flag
- Add handling for close events with appropriate error messages
- Improve robustness when streams are destroyed or closed externally

### Impact
This fixes potential memory leaks and race conditions when streams are
cancelled, destroyed, or closed before completion.

### Code Statistics
- 1 file changed
- ~53 lines added, 9 lines modified
- ~95% code changes (functional improvements)

---------

Co-authored-by: GitTensor Miner <miner@gittensor.io>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
This commit is contained in:
Angel98518
2026-01-21 00:19:47 +08:00
committed by GitHub
parent 351e2030ff
commit 9cf88df67b
2 changed files with 138 additions and 9 deletions
@@ -4,16 +4,66 @@ export const streamToBuffer = async (stream: Readable): Promise<Buffer> => {
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
if (stream.readableEnded) {
reject(new Error('Stream has already ended'));
stream.on('end', () => {
resolve(Buffer.concat(chunks));
});
return;
}
stream.on('error', (error) => {
reject(error);
});
if (!stream.readable) {
reject(new Error('Stream is not readable'));
return;
}
let isResolved = false;
const cleanup = () => {
stream.removeListener('data', onData);
stream.removeListener('end', onEnd);
stream.removeListener('error', onError);
stream.removeListener('close', onClose);
};
const onData = (chunk: Buffer) => {
if (!isResolved) {
chunks.push(chunk);
}
};
const onEnd = () => {
if (!isResolved) {
isResolved = true;
cleanup();
resolve(Buffer.concat(chunks));
}
};
const onError = (error: Error) => {
if (!isResolved) {
isResolved = true;
cleanup();
reject(error);
}
};
const onClose = () => {
if (!isResolved) {
if (stream.readableEnded) {
isResolved = true;
cleanup();
resolve(Buffer.concat(chunks));
} else {
isResolved = true;
cleanup();
reject(new Error('Stream closed before end'));
}
}
};
stream.on('data', onData);
stream.on('end', onEnd);
stream.on('error', onError);
stream.on('close', onClose);
});
};