From 92edb82e133e04cff03a22f070a85c279fae5274 Mon Sep 17 00:00:00 2001 From: John Knox Date: Thu, 6 Jun 2019 01:56:19 -0700 Subject: [PATCH] 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 --- headless/index.js | 28 ++++++++++++++++------------ src/dispatcher/index.js | 14 +++++++++++--- src/dispatcher/server.js | 1 + src/dispatcher/types.js | 10 ++++++++++ 4 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 src/dispatcher/types.js diff --git a/headless/index.js b/headless/index.js index 13636b05a..5224602a1 100644 --- a/headless/index.js +++ b/headless/index.js @@ -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 { if (userArguments.listDevices) { const devices = await listDevices(); - originalConsole.log(devices); - process.exit(); + outputAndExit(devices.toString()); } } @@ -118,11 +123,11 @@ async function exitActions( metrics, pluginsClassMap(store.getState()), ); - originalConsole.log(payload); + outputAndExit(payload.toString()); } catch (error) { console.error(error); + process.exit(); } - process.exit(); } if (exit == 'sigint') { process.on('SIGINT', async () => { @@ -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(); } diff --git a/src/dispatcher/index.js b/src/dispatcher/index.js index f5bceb4bf..93a72e5a1 100644 --- a/src/dispatcher/index.js +++ b/src/dispatcher/index.js @@ -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 { + const dispatchers: Array = [ 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(() => {}); + }; +} diff --git a/src/dispatcher/server.js b/src/dispatcher/server.js index 1266b6521..3057bac8f 100644 --- a/src/dispatcher/server.js +++ b/src/dispatcher/server.js @@ -90,4 +90,5 @@ export default (store: Store, logger: Logger) => { server.close(); }); } + return server.close; }; diff --git a/src/dispatcher/types.js b/src/dispatcher/types.js new file mode 100644 index 000000000..f36871c83 --- /dev/null +++ b/src/dispatcher/types.js @@ -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;