From 200e43c426bade3ab229c11b92365d783c8b80e7 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Sat, 29 Jun 2024 15:04:30 +0200 Subject: [PATCH 1/2] buildx(history): detach dial-stdio process Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- src/buildx/history.ts | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/buildx/history.ts b/src/buildx/history.ts index 31a4448..a8c1b6e 100644 --- a/src/buildx/history.ts +++ b/src/buildx/history.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import {ChildProcessByStdio, spawn} from 'child_process'; +import {spawn} from 'child_process'; import fs from 'fs'; -import {Readable, Writable} from 'node:stream'; import os from 'os'; import path from 'path'; import * as core from '@actions/core'; @@ -93,9 +92,17 @@ export class History { await Exec.exec('mkfifo', [buildxOutFifoPath]); const buildxCmd = await this.buildx.getCommand(['--builder', builderName, 'dial-stdio']); - const buildxDialStdioProc = History.spawn(buildxCmd.command, buildxCmd.args); + + core.info(`[command]${buildxCmd.command} ${buildxCmd.args.join(' ')}`); + const buildxDialStdioProc = spawn(buildxCmd.command, buildxCmd.args, { + stdio: ['pipe', 'pipe', 'inherit'], + detached: true + }); fs.createReadStream(buildxInFifoPath).pipe(buildxDialStdioProc.stdin); buildxDialStdioProc.stdout.pipe(fs.createWriteStream(buildxOutFifoPath)); + buildxDialStdioProc.on('exit', code => { + core.info(`Process "buildx dial-stdio" exited with code ${code}`); + }); const tmpDockerbuildFilename = path.join(outDir, 'rec.dockerbuild'); const summaryFilename = path.join(outDir, 'summary.json'); @@ -112,13 +119,17 @@ export class History { ebargs.push(`--gid=${process.getgid()}`); } // prettier-ignore - const dockerRunProc = History.spawn('docker', [ + const dockerRunArgs = [ 'run', '--rm', '-i', '-v', `${Buildx.refsDir}:/buildx-refs`, '-v', `${outDir}:/out`, opts.image || History.EXPORT_TOOL_IMAGE, ...ebargs - ]); + ] + core.info(`[command]docker ${dockerRunArgs.join(' ')}`); + const dockerRunProc = spawn('docker', dockerRunArgs, { + stdio: ['pipe', 'pipe', 'inherit'] + }); fs.createReadStream(buildxOutFifoPath).pipe(dockerRunProc.stdin); dockerRunProc.stdout.pipe(fs.createWriteStream(buildxInFifoPath)); dockerRunProc.on('close', code => { @@ -133,9 +144,12 @@ export class History { } }); dockerRunProc.on('error', err => { - core.error(`Error executing buildx dial-stdio: ${err}`); + core.error(`Error executing "docker run": ${err}`); reject(err); }); + dockerRunProc.on('exit', code => { + core.info(`Process "docker run" exited with code ${code}`); + }); }).catch(err => { throw err; }); @@ -162,11 +176,4 @@ export class History { refs: refs }; } - - private static spawn(command: string, args?: ReadonlyArray): ChildProcessByStdio { - core.info(`[command]${command}${args ? ` ${args.join(' ')}` : ''}`); - return spawn(command, args || [], { - stdio: ['pipe', 'pipe', 'inherit'] - }); - } } From ff35e30b017f118985a9a58d39b1da360f4675ff Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Sat, 29 Jun 2024 17:42:31 +0200 Subject: [PATCH 2/2] buildx(history): improve child process termination and exit code handling Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- src/buildx/history.ts | 52 +++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/buildx/history.ts b/src/buildx/history.ts index a8c1b6e..744549e 100644 --- a/src/buildx/history.ts +++ b/src/buildx/history.ts @@ -14,10 +14,11 @@ * limitations under the License. */ -import {spawn} from 'child_process'; +import {ChildProcessByStdio, spawn} from 'child_process'; import fs from 'fs'; import os from 'os'; import path from 'path'; +import {Readable, Writable} from 'stream'; import * as core from '@actions/core'; import {Buildx} from './buildx'; @@ -91,22 +92,29 @@ export class History { }); await Exec.exec('mkfifo', [buildxOutFifoPath]); - const buildxCmd = await this.buildx.getCommand(['--builder', builderName, 'dial-stdio']); - - core.info(`[command]${buildxCmd.command} ${buildxCmd.args.join(' ')}`); - const buildxDialStdioProc = spawn(buildxCmd.command, buildxCmd.args, { + const buildxDialStdioCmd = await this.buildx.getCommand(['--builder', builderName, 'dial-stdio']); + core.info(`[command]${buildxDialStdioCmd.command} ${buildxDialStdioCmd.args.join(' ')}`); + const buildxDialStdioProc = spawn(buildxDialStdioCmd.command, buildxDialStdioCmd.args, { stdio: ['pipe', 'pipe', 'inherit'], detached: true }); + let buildxDialStdioKilled = false; fs.createReadStream(buildxInFifoPath).pipe(buildxDialStdioProc.stdin); buildxDialStdioProc.stdout.pipe(fs.createWriteStream(buildxOutFifoPath)); - buildxDialStdioProc.on('exit', code => { - core.info(`Process "buildx dial-stdio" exited with code ${code}`); + buildxDialStdioProc.on('exit', (code, signal) => { + buildxDialStdioKilled = true; + if (signal) { + core.info(`Process "buildx dial-stdio" was killed with signal ${signal}`); + } else { + core.info(`Process "buildx dial-stdio" exited with code ${code}`); + } }); const tmpDockerbuildFilename = path.join(outDir, 'rec.dockerbuild'); const summaryFilename = path.join(outDir, 'summary.json'); + let dockerRunProc: ChildProcessByStdio | undefined; + let dockerRunProcKilled = false; await new Promise((resolve, reject) => { const ebargs: Array = ['--ref-state-dir=/buildx-refs', `--node=${builderName}/${nodeName}`]; for (const ref of refs) { @@ -127,7 +135,7 @@ export class History { ...ebargs ] core.info(`[command]docker ${dockerRunArgs.join(' ')}`); - const dockerRunProc = spawn('docker', dockerRunArgs, { + dockerRunProc = spawn('docker', dockerRunArgs, { stdio: ['pipe', 'pipe', 'inherit'] }); fs.createReadStream(buildxOutFifoPath).pipe(dockerRunProc.stdin); @@ -140,19 +148,35 @@ export class History { resolve(); } } else { - reject(new Error(`Process "docker run" exited with code ${code}`)); + reject(new Error(`Process "docker run" closed with code ${code}`)); } }); dockerRunProc.on('error', err => { core.error(`Error executing "docker run": ${err}`); reject(err); }); - dockerRunProc.on('exit', code => { - core.info(`Process "docker run" exited with code ${code}`); + dockerRunProc.on('exit', (code, signal) => { + dockerRunProcKilled = true; + if (signal) { + core.info(`Process "docker run" was killed with signal ${signal}`); + } else { + core.info(`Process "docker run" exited with code ${code}`); + } + }); + }) + .catch(err => { + throw err; + }) + .finally(() => { + if (buildxDialStdioProc && !buildxDialStdioKilled) { + core.debug('Force terminating "buildx dial-stdio" process'); + buildxDialStdioProc.kill('SIGKILL'); + } + if (dockerRunProc && !dockerRunProcKilled) { + core.debug('Force terminating "docker run" process'); + dockerRunProc.kill('SIGKILL'); + } }); - }).catch(err => { - throw err; - }); let dockerbuildFilename = `${GitHub.context.repo.owner}~${GitHub.context.repo.repo}~${refs[0].substring(0, 6).toUpperCase()}`; if (refs.length > 1) {