Exit cleanly after flushing stdout

Summary:
Using `process.exit()` stops the node process without waiting for the event loop to finish, so when using async i/o, which is what happens when piped, if the output is buffered, the process can terminate before it finishes flushing the buffer.

This means you only get some of the output and the JSON is malformed.

This fixes it by calling `process.exit()` inside the flushed callback.

Reviewed By: passy

Differential Revision: D15624806

fbshipit-source-id: ea540ed5a40fb1811e5b705b190da96c8e54730d
This commit is contained in:
John Knox
2019-06-06 01:56:19 -07:00
committed by Facebook Github Bot
parent 873475405a
commit 92edb82e13
4 changed files with 38 additions and 15 deletions

View File

@@ -95,14 +95,19 @@ function shouldExportMetric(metrics): boolean {
return true;
}
function outputAndExit(output: string): void {
process.stdout.write(output, () => {
process.exit(0);
});
}
async function earlyExitActions(
userArguments: UserArguments,
originalConsole: typeof global.console,
): Promise<void> {
if (userArguments.listDevices) {
const devices = await listDevices();
originalConsole.log(devices);
process.exit();
outputAndExit(devices.toString());
}
}
@@ -118,12 +123,12 @@ async function exitActions(
metrics,
pluginsClassMap(store.getState()),
);
originalConsole.log(payload);
outputAndExit(payload.toString());
} catch (error) {
console.error(error);
}
process.exit();
}
}
if (exit == 'sigint') {
process.on('SIGINT', async () => {
try {
@@ -133,16 +138,15 @@ async function exitActions(
store,
state.pluginStates,
);
originalConsole.log(payload);
outputAndExit(payload.toString());
} else {
const {serializedString, errorArray} = await exportStore(store);
errorArray.forEach(console.error);
originalConsole.log(serializedString);
outputAndExit(serializedString);
}
} catch (e) {
console.error(e);
}
process.exit();
});
}
}
@@ -222,15 +226,13 @@ async function startFlipper(userArguments: UserArguments) {
const state = store.getState();
exportMetricsWithoutTrace(state, state.pluginStates)
.then(payload => {
originalConsole.log(payload);
process.exit();
outputAndExit(payload || '');
})
.catch(console.error);
} else {
exportStore(store)
.then(({serializedString}) => {
originalConsole.log(serializedString);
process.exit();
outputAndExit(serializedString);
})
.catch(console.error);
}
@@ -248,9 +250,11 @@ async function startFlipper(userArguments: UserArguments) {
await earlyExitActions(userArguments, originalConsole);
dispatcher(store, logger);
const cleanupDispatchers = dispatcher(store, logger);
await storeModifyingActions(userArguments, originalConsole, store);
await exitActions(userArguments, originalConsole, store);
await cleanupDispatchers();
}

View File

@@ -17,9 +17,10 @@ import user from './user';
import type {Logger} from '../fb-interfaces/Logger.js';
import type {Store} from '../reducers/index.js';
import type {Dispatcher} from './types';
export default (store: Store, logger: Logger) =>
[
export default function(store: Store, logger: Logger): () => Promise<void> {
const dispatchers: Array<Dispatcher> = [
application,
androidDevice,
iOSDevice,
@@ -29,4 +30,11 @@ export default (store: Store, logger: Logger) =>
notifications,
plugins,
user,
].forEach(fn => fn(store, logger));
];
const globalCleanup = dispatchers
.map(dispatcher => dispatcher(store, logger))
.filter(Boolean);
return () => {
return Promise.all(globalCleanup).then(() => {});
};
}

View File

@@ -90,4 +90,5 @@ export default (store: Store, logger: Logger) => {
server.close();
});
}
return server.close;
};

10
src/dispatcher/types.js Normal file
View File

@@ -0,0 +1,10 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import type {Store} from '../reducers/index.js';
import type {Logger} from '../fb-interfaces/Logger';
export type Dispatcher = (store: Store, logger: Logger) => ?() => Promise<void>;