## 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>
70 lines
1.5 KiB
TypeScript
70 lines
1.5 KiB
TypeScript
import { type Readable } from 'stream';
|
|
|
|
export const streamToBuffer = async (stream: Readable): Promise<Buffer> => {
|
|
const chunks: Buffer[] = [];
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (stream.readableEnded) {
|
|
reject(new Error('Stream has already ended'));
|
|
|
|
return;
|
|
}
|
|
|
|
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);
|
|
});
|
|
};
|