From 0d443d4648323a9a75685713faf5f20ba402382f Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Sun, 26 Jan 2025 16:12:12 +0100 Subject: [PATCH] fix process close --- src/app.ts | 45 +++++++++++++++++++++++++++++++++++---------- src/throttle.ts | 19 ++++++++++--------- src/utils.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/app.ts b/src/app.ts index 386183b..78fe20a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,7 +11,12 @@ import { stopThrottle, throttleLauncher, } from './throttle' -import { logger, registerExitHandler, resolvePackagePath } from './utils' +import { + getProcessChildren, + logger, + registerExitHandler, + resolvePackagePath, +} from './utils' const log = logger('throttler') @@ -48,14 +53,29 @@ async function main(): Promise { showHelpOrVersion() const config = loadConfig(process.argv[2]) - const abortControllers = new Set() + const pids = new Set() await startThrottle(config.throttleConfig) const stop = async (): Promise => { log.info('Stopping...') - for (const controller of abortControllers) { - controller.abort('stop') + for (const pid of pids) { + const childPids = await getProcessChildren(pid) + log.debug(`Killing process ${pid} and children: ${childPids}`) + try { + process.kill(pid, 'SIGKILL') + } catch (err: unknown) { + log.debug(`Error killing process ${pid}: ${(err as Error).stack}`) + } + for (const childPid of childPids) { + try { + process.kill(childPid, 'SIGKILL') + } catch (err: unknown) { + log.debug( + `Error killing child process ${childPid}: ${(err as Error).stack}`, + ) + } + } } await stopThrottle() process.exit(0) @@ -67,19 +87,21 @@ async function main(): Promise { : [] for (const c of commands) { const { session, command } = c + const shortName = command.split(' ')[0] const index = getSessionThrottleIndex(session || 0) const launcher = await throttleLauncher(command, index) try { - const abort = new AbortController() const proc = spawn(launcher, { shell: false, - stdio: ['ignore', 'ignore', 'pipe'], + stdio: ['ignore', 'pipe', 'pipe'], detached: false, - signal: abort.signal, }) - abortControllers.add(abort) + if (proc.pid) pids.add(proc.pid) + proc.stdout.on('data', data => { + log.info(`[${shortName}][stdout]`, data.toString().trim()) + }) proc.stderr.on('data', data => { - log.info('[stderr]', data.toString().trim()) + log.info(`[${shortName}][stderr]`, data.toString().trim()) }) proc.on('error', err => { if (err.message.startsWith('The operation was aborted')) return @@ -87,7 +109,10 @@ async function main(): Promise { }) proc.once('exit', code => { log.info(`Command "${command}" exited with code: ${code || 0}`) - abortControllers.delete(abort) + if (proc.pid) pids.delete(proc.pid) + /* fs.promises.unlink(launcher).catch(err => { + log.warn(`Error unlinking "${launcher}": ${(err as Error).stack}`) + }) */ }) } catch (err: unknown) { log.error(`Error running command "${command}": ${(err as Error).stack}`) diff --git a/src/throttle.ts b/src/throttle.ts index fd514f6..bb8c8ea 100644 --- a/src/throttle.ts +++ b/src/throttle.ts @@ -466,7 +466,6 @@ export async function throttleLauncher( const config = throttleConfig[index] const mark = index + 1 const launcherPath = `/tmp/throttler-launcher-${index}` - const wrapperPath = `/tmp/throttler-launcher-${index}-wrapper` const group = `throttler${index}` const filters = `${config.protocol ? `-p ${config.protocol}` : ''}\ ${config.skipSourcePorts ? ` -m multiport ! --sports ${config.skipSourcePorts}` : ''}\ @@ -475,8 +474,8 @@ ${config.filter ? ` ${config.filter}` : ''}` await fs.promises.writeFile( launcherPath, `#!/bin/bash -getent group ${group} || sudo -n addgroup --system ${group} -sudo -n adduser $USER ${group} +getent group ${group} >/dev/null || sudo -n addgroup --system ${group} +sudo -n adduser $USER ${group} --quiet rule=$(sudo -n iptables -t mangle -L OUTPUT --line-numbers | grep "owner GID match ${group}" | awk '{print $1}') if [ -n "$rule" ]; then @@ -488,13 +487,15 @@ fi sudo -n iptables -t mangle -L PREROUTING | grep -q "CONNMARK restore" || sudo -n iptables -t mangle -I PREROUTING 1 -j CONNMARK --restore-mark sudo -n iptables -t mangle -L POSTROUTING | grep -q "CONNMARK save" || sudo -n iptables -t mangle -I POSTROUTING 1 -j CONNMARK --save-mark -cat < ${wrapperPath} -#!/bin/bash -exec ${executablePath} $@ -EOF -chmod +x ${wrapperPath} +function stop() { + echo "Stopping throttler" +} +trap stop SIGINT SIGTERM -exec sg ${group} -c ${wrapperPath}`, +echo "running: ${executablePath} $@" +exec newgrp ${group} < { } export async function checkNetworkInterface(device: string): Promise { + if (device === 'lo') return await runShellCommand(`ip route | grep -q "dev ${device}"`) } @@ -220,3 +221,29 @@ SIGNALS.forEach(event => process.exit(0) }), ) + +export async function getProcessChildren(pid: number): Promise { + log.debug(`getProcessChildren pid=${pid}`) + const pids = [] + try { + const p = await runShellCommand(`pgrep -P ${pid}`) + for (const pid of p.stdout.trim().split('\n').map(Number)) { + pids.push(pid) + try { + const childPids = await getProcessChildren(pid) + for (const p of childPids) { + pids.push(p) + } + } catch (err) { + log.debug( + `Error getting process ${pid} children: ${(err as Error).message}`, + ) + } + } + } catch (err) { + log.debug( + `Error getting process ${pid} children: ${(err as Error).message}`, + ) + } + return pids +}