Skip to content

Commit

Permalink
Add --force-after-timeout / -t option (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yanis Benson committed Jul 10, 2021
1 parent d8105ff commit 7eeda5d
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 14 deletions.
14 changes: 10 additions & 4 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ const cli = meow(`
$ fkill [<pid|name|:port> …]
Options
--force -f Force kill
--verbose -v Show process arguments
--silent -s Silently kill and always exit with code 0
--force -f Force kill
--verbose -v Show process arguments
--silent -s Silently kill and always exit with code 0
--force-after-timeout <N>, -t <N> Force kill processes which didn't exit after N seconds
Examples
$ fkill 1337
Expand Down Expand Up @@ -39,14 +40,19 @@ const cli = meow(`
silent: {
type: 'boolean',
alias: 's'
},
forceAfterTimeout: {
type: 'number',
alias: 't'
}
}
});

if (cli.input.length === 0) {
require('./interactive').init(cli.flags);
} else {
const promise = fkill(cli.input, {...cli.flags, ignoreCase: true});
const forceAfterTimeout = cli.flags.forceAfterTimeout === undefined ? undefined : cli.flags.forceAfterTimeout * 1000;
const promise = fkill(cli.input, {...cli.flags, forceAfterTimeout, ignoreCase: true});

if (!cli.flags.force) {
promise.catch(error => {
Expand Down
83 changes: 78 additions & 5 deletions interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,41 @@ const escExit = require('esc-exit');
const cliTruncate = require('cli-truncate');
const pidFromPort = require('pid-port');
const fkill = require('fkill');
const processExists = require('process-exists');

const isWindows = process.platform === 'win32';
const commandLineMargins = 4;

const PROCESS_EXITED_MIN_INTERVAL = 5;
const PROCESS_EXITED_MAX_INTERVAL = 1280;

const delay = ms => new Promise(resolve => {
setTimeout(resolve, ms);
});

const processExited = async (pid, timeout) => {
const endTime = Date.now() + timeout;
let interval = PROCESS_EXITED_MIN_INTERVAL;
if (interval > timeout) {
interval = timeout;
}

let exists;

do {
await delay(interval); // eslint-disable-line no-await-in-loop

exists = await processExists(pid); // eslint-disable-line no-await-in-loop

interval *= 2;
if (interval > PROCESS_EXITED_MAX_INTERVAL) {
interval = PROCESS_EXITED_MAX_INTERVAL;
}
} while (Date.now() < endTime && exists);

return !exists;
};

const nameFilter = (input, process_) => {
const isPort = input[0] === ':';

Expand Down Expand Up @@ -131,6 +162,52 @@ const handleFkillError = async processes => {
}
};

const DEFAULT_EXIT_TIMEOUT = 3000;

const performKillSequence = async processes => {
if (!Array.isArray(processes)) {
processes = [processes];
}

let didSurvive;
let hadError;
try {
await fkill(processes);
const exited = await Promise.all(processes.map(process => processExited(process, DEFAULT_EXIT_TIMEOUT)));
didSurvive = processes.filter((_, i) => !exited[i]);
} catch (error) {
didSurvive = processes;
hadError = error;
}

if (didSurvive.length === 0) {
return;
}

const suffix = didSurvive.length > 1 ? 'es' : '';
const problemText = hadError ? `Error killing process${suffix}.` : `Process${suffix} didn't exit in ${DEFAULT_EXIT_TIMEOUT}ms.`;

if (process.stdout.isTTY === false) {
console.error(`${problemText} Try \`fkill --force ${didSurvive.join(' ')}\``);
process.exit(1); // eslint-disable-line unicorn/no-process-exit
}

const answer = await inquirer.prompt([{
type: 'confirm',
name: 'forceKill',
message: `${problemText} Would you like to use the force?`
}]);

if (!answer.forceKill) {
return;
}

await fkill(processes, {
force: true,
ignoreCase: true
});
};

const listProcesses = async (processes, flags) => {
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'));

Expand All @@ -142,11 +219,7 @@ const listProcesses = async (processes, flags) => {
source: async (answers, input) => filterProcesses(input, processes, flags)
}]);

try {
await fkill(answer.processes);
} catch {
handleFkillError(answer.processes);
}
performKillSequence(answer.processes);
};

const init = async flags => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"chalk": "^4.1.0",
"cli-truncate": "^2.1.0",
"esc-exit": "^2.0.2",
"fkill": "^7.1.0",
"fkill": "^7.2.1",
"inquirer": "^7.3.3",
"inquirer-autocomplete-prompt": "^1.3.0",
"meow": "^8.1.0",
Expand Down
9 changes: 5 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ $ npm install --global fkill-cli
$ fkill --help
Usage
$ fkill [<pid|name> …]
$ fkill [<pid|name|:port> …]
Options
--force -f Force kill
--verbose -v Show process arguments
--silent -s Silently kill and always exit with code 0
--force, -f Force kill
--verbose, -v Show process arguments
--silent, -s Silently kill and always exit with code 0
--force-timeout <N>, -t <N> Force kill processes which didn't exit after N seconds
Examples
$ fkill 1337
Expand Down

0 comments on commit 7eeda5d

Please sign in to comment.