Skip to content

Commit

Permalink
fix #2129: handle corrupted binary executable
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 26, 2022
1 parent 36c070e commit 2151135
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Better handle errors where the esbuild binary executable is corrupted or missing ([#2129](https://github.com/evanw/esbuild/issues/2129))

If the esbuild binary executable is corrupted or missing, previously there was one situation where esbuild's JavaScript API could hang instead of generating an error. This release changes esbuild's library code to generate an error instead in this case.

## 0.14.28

* Add support for some new CSS rules ([#2115](https://github.com/evanw/esbuild/issues/2115), [#2116](https://github.com/evanw/esbuild/issues/2116), [#2117](https://github.com/evanw/esbuild/issues/2117))
Expand Down
7 changes: 5 additions & 2 deletions lib/npm/node.ts
Expand Up @@ -257,7 +257,7 @@ let ensureServiceIsRunning = (): Service => {
writeToStdin(bytes) {
child.stdin.write(bytes, err => {
// Assume the service was stopped if we get an error writing to stdin
if (err) afterClose();
if (err) afterClose(err);
});
},
readFileSync: fs.readFileSync,
Expand All @@ -269,6 +269,9 @@ let ensureServiceIsRunning = (): Service => {
// Assume the service was stopped if we get an error writing to stdin
child.stdin.on('error', afterClose);

// Propagate errors about failure to run the executable itself
child.on('error', afterClose);

const stdin: typeof child.stdin & { unref?(): void } = child.stdin;
const stdout: typeof child.stdout & { unref?(): void } = child.stdout;

Expand Down Expand Up @@ -377,7 +380,7 @@ let runServiceSync = (callback: (service: common.StreamService) => void): void =
maxBuffer: +process.env.ESBUILD_MAX_BUFFER! || 16 * 1024 * 1024,
});
readFromStdout(stdout);
afterClose();
afterClose(null);
};

let randomFileName = () => {
Expand Down
21 changes: 11 additions & 10 deletions lib/shared/common.ts
Expand Up @@ -411,7 +411,7 @@ export interface StreamIn {

export interface StreamOut {
readFromStdout: (data: Uint8Array) => void;
afterClose: () => void;
afterClose: (error: Error | null) => void;
service: StreamService;
}

Expand Down Expand Up @@ -481,7 +481,7 @@ export function createChannel(streamIn: StreamIn): StreamOut {
let pluginCallbacks = new Map<number, PluginCallback>();
let watchCallbacks = new Map<number, WatchCallback>();
let serveCallbacks = new Map<number, ServeCallbacks>();
let isClosed = false;
let closeData: { reason: string } | null = null;
let nextRequestID = 0;
let nextBuildKey = 0;

Expand Down Expand Up @@ -516,20 +516,21 @@ export function createChannel(streamIn: StreamIn): StreamOut {
}
};

let afterClose = () => {
let afterClose = (error: Error | null) => {
// When the process is closed, fail all pending requests
isClosed = true;
closeData = { reason: error ? ': ' + (error.message || error) : '' };
const text = 'The service was stopped' + closeData.reason;
for (let callback of responseCallbacks.values()) {
callback('The service was stopped', null);
callback(text, null);
}
responseCallbacks.clear();
for (let callbacks of serveCallbacks.values()) {
callbacks.onWait('The service was stopped');
callbacks.onWait(text);
}
serveCallbacks.clear();
for (let callback of watchCallbacks.values()) {
try {
callback(new Error('The service was stopped'), null);
callback(new Error(text), null);
} catch (e) {
console.error(e)
}
Expand All @@ -538,7 +539,7 @@ export function createChannel(streamIn: StreamIn): StreamOut {
};

let sendRequest = <Req, Res>(refs: Refs | null, value: Req, callback: (error: string | null, response: Res | null) => void): void => {
if (isClosed) return callback('The service is no longer running', null);
if (closeData) return callback('The service is no longer running' + closeData.reason, null);
let id = nextRequestID++;
responseCallbacks.set(id, (error, response) => {
try {
Expand All @@ -552,7 +553,7 @@ export function createChannel(streamIn: StreamIn): StreamOut {
};

let sendResponse = (id: number, value: protocol.Value): void => {
if (isClosed) throw new Error('The service is no longer running');
if (closeData) throw new Error('The service is no longer running' + closeData.reason);
streamIn.writeToStdin(protocol.encodePacket({ id, isRequest: false, value }));
};

Expand Down Expand Up @@ -1195,7 +1196,7 @@ export function createChannel(streamIn: StreamIn): StreamOut {
if (!rebuild) {
let isDisposed = false;
(rebuild as any) = () => new Promise<types.BuildResult>((resolve, reject) => {
if (isDisposed || isClosed) throw new Error('Cannot rebuild');
if (isDisposed || closeData) throw new Error('Cannot rebuild');
sendRequest<protocol.RebuildRequest, protocol.BuildResponse>(refs, { command: 'rebuild', key },
(error2, response2) => {
if (error2) {
Expand Down

0 comments on commit 2151135

Please sign in to comment.