From b66ab7fb6513cf8327706b25a97fd33bec24b268 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Tue, 25 Jun 2024 12:17:48 -0400 Subject: [PATCH] Refactor Docker container launching code Put subprocess and parent process code in the same file. --- run.ts | 72 ++------------------------------- runBinary.ts | 33 +++++++++++++++ launchSearch.ts => runDocker.ts | 65 ++++++++++++++++++++++------- 3 files changed, 87 insertions(+), 83 deletions(-) create mode 100644 runBinary.ts rename launchSearch.ts => runDocker.ts (53%) diff --git a/run.ts b/run.ts index 5011870..aa97ccb 100644 --- a/run.ts +++ b/run.ts @@ -12,13 +12,12 @@ import { install } from './install.js' import { mkdtemp } from 'fs/promises' import { mkdirP, temp } from './paths.js' import rimraf from 'rimraf' -import { spawn, untilTerminated } from './processes.js' import type { SandboxEngine } from './engines.js' import { UnexpectedResolveError, neverResolve } from './promises.js' -import { fork } from 'child_process' -import { fileURLToPath } from 'url' +import { launchBinary } from './runBinary.js' +import { launchDocker } from './runDocker.js' -type SearchEngineLauncherFunction = ( +export type SearchEngineLauncherFunction = ( props: T & { options: string[] dataDir: string @@ -31,71 +30,6 @@ type SearchEngineLauncherFunction = ( waitUntilStopped: () => Promise }> -const launchBinary: SearchEngineLauncherFunction<{ bin: string }> = async ({ - bin, - dataDir, - logsDir, - options, -}) => { - const args = [...options, `path.data=${dataDir}`, `path.logs=${logsDir}`].map( - (opt) => `-E${opt}` - ) - - console.log('Spawning', bin, ...args) - const child = await spawn(bin, args, { - stdio: ['ignore', 'ignore', 'inherit'], - }) - - return { - async kill() { - console.log('Killing child process') - child.kill() - }, - async waitUntilStopped() { - await untilTerminated(child) - }, - } -} - -const launchDocker: SearchEngineLauncherFunction = async ({ - dataDir, - logsDir, - engine, - port, - options, -}) => { - const argv = { - dataDir, - logsDir, - engine, - port, - options, - } - // FIXME: fork accepts either a string or URL as the first argument. @types/node has defined the modulePath type as string only. - // new URL(import.meta.url) may be used in place of __filename once this has been updated. - // see: https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/69812 - const __filename = fileURLToPath(import.meta.url) - const subprocess = fork(__filename, [ - 'launch-docker-subprocess', - JSON.stringify(argv), - ]) - - return { - async kill() { - console.log('Killing Docker container') - subprocess.send({ action: 'kill' }) - }, - async waitUntilStopped() { - return new Promise((resolve) => { - subprocess.on('exit', () => { - console.log('Docker container exited') - resolve() - }) - }) - }, - } -} - export async function launch({ port, engine, diff --git a/runBinary.ts b/runBinary.ts new file mode 100644 index 0000000..8f8ff2e --- /dev/null +++ b/runBinary.ts @@ -0,0 +1,33 @@ +/*! + * Copyright © 2023 United States Government as represented by the + * Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { spawn, untilTerminated } from './processes.js' +import { type SearchEngineLauncherFunction } from './run.js' + +export const launchBinary: SearchEngineLauncherFunction<{ + bin: string +}> = async ({ bin, dataDir, logsDir, options }) => { + const args = [...options, `path.data=${dataDir}`, `path.logs=${logsDir}`].map( + (opt) => `-E${opt}` + ) + + console.log('Spawning', bin, ...args) + const child = await spawn(bin, args, { + stdio: ['ignore', 'ignore', 'inherit'], + }) + + return { + async kill() { + console.log('Killing child process') + child.kill() + }, + async waitUntilStopped() { + await untilTerminated(child) + }, + } +} diff --git a/launchSearch.ts b/runDocker.ts similarity index 53% rename from launchSearch.ts rename to runDocker.ts index 484872c..b20f5be 100644 --- a/launchSearch.ts +++ b/runDocker.ts @@ -7,23 +7,13 @@ */ import Dockerode from 'dockerode' +import { fork } from 'child_process' +import { fileURLToPath } from 'url' +import { type SearchEngineLauncherFunction } from './run.js' const [, , command, jsonifiedArgs] = process.argv if (command === 'launch-docker-subprocess') { - const dockerContainer = await launchDockerSearch() - - const signals = ['message', 'SIGTERM', 'SIGINT'] - signals.forEach((signal) => { - process.on(signal, async () => { - await dockerContainer.kill() - }) - }) - - await dockerContainer.wait() -} - -async function launchDockerSearch() { const { dataDir, logsDir, engine, port, options } = JSON.parse(jsonifiedArgs) const Image = engine === 'elasticsearch' @@ -49,5 +39,52 @@ async function launchDockerSearch() { const stream = await container.attach({ stream: true, stderr: true }) stream.pipe(process.stderr) await container.start() - return container + + const signals = ['message', 'SIGTERM', 'SIGINT'] + signals.forEach((signal) => { + process.on(signal, async () => { + await container.kill() + }) + }) + + await container.wait() +} + +export const launchDocker: SearchEngineLauncherFunction = async ({ + dataDir, + logsDir, + engine, + port, + options, +}) => { + const argv = { + dataDir, + logsDir, + engine, + port, + options, + } + // FIXME: fork accepts either a string or URL as the first argument. @types/node has defined the modulePath type as string only. + // new URL(import.meta.url) may be used in place of __filename once this has been updated. + // see: https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/69812 + const __filename = fileURLToPath(import.meta.url) + const subprocess = fork(__filename, [ + 'launch-docker-subprocess', + JSON.stringify(argv), + ]) + + return { + async kill() { + console.log('Killing Docker container') + subprocess.send({ action: 'kill' }) + }, + async waitUntilStopped() { + return new Promise((resolve) => { + subprocess.on('exit', () => { + console.log('Docker container exited') + resolve() + }) + }) + }, + } }