Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/lib/lactoserv/dev
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function target-build {
&& env-minimize \
&& lib node-project build-main-module \
--out="${outDir}" --modules-dirs="${srcDir}" \
--runner-script=run --runner-versions[]='20 21 22 23 24' \
--runner-script=run --runner-versions[]='20 21 22 23 24 25' \
lactoserv
)
}
Expand Down
60 changes: 58 additions & 2 deletions src/net-util/export/FullResponse.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,8 +775,12 @@ export class FullResponse extends BaseResponse {
throw new Error(`File changed length during response processing: ${path}`);
}

// TODO: Handle `drain` requests (based on return value of `write()`).
res.write(bytesRead === buffer.length ? buffer : buffer.subarray(0, bytesRead));
const chunk = bytesRead === buffer.length ? buffer : buffer.subarray(0, bytesRead);
const canContinue = res.write(chunk);

if (!canContinue) {
await FullResponse.#waitForDrain(res);
}

at += bytesRead;
remaining -= bytesRead;
Expand Down Expand Up @@ -1030,6 +1034,58 @@ export class FullResponse extends BaseResponse {
return buffer;
}

/**
* Waits for the 'drain' event on a response, or returns immediately if the
* response is already closed/destroyed.
*
* @param {TypeNodeResponse} res The low-level response object.
* @returns {boolean} `true` if drained successfully, `false` if the response
* was closed/destroyed.
* @throws {Error} Any error reported by the response.
*/
static async #waitForDrain(res) {
if (res.closed || res.destroyed) {
const error = res.errored;
if (error) {
throw (error instanceof Error) ? error : new Error(`Response error: ${error}`);
}
return false;
}

const resultMp = new ManualPromise();

const onDrain = () => {
cleanup();
resultMp.resolve(true);
};

const onError = (error) => {
cleanup();
if (error) {
resultMp.reject((error instanceof Error) ? error : new Error(`Response error: ${error}`));
} else {
resultMp.resolve(false);
}
};

const onClose = () => {
cleanup();
resultMp.resolve(false);
};

const cleanup = () => {
res.removeListener('drain', onDrain);
res.removeListener('error', onError);
res.removeListener('close', onClose);
};

res.once('drain', onDrain);
res.once('error', onError);
res.once('close', onClose);

return resultMp.promise;
}

/**
* Cleans up response headers for logging.
*
Expand Down
Loading