Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion MANPAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ To actually see the notifications in your GUI session, you need to have
running as your user.

#### -N /PATH/TO/SCRIPT
Run the given script for each process killed. Must be an absolute path.
Run the given script for each process killed, afterwards. Must be an absolute path.

Within the script, information about the killed process can be obtained via the
following environment variables:
Expand All @@ -139,6 +139,20 @@ WARNING: `EARLYOOM_NAME` can contain spaces, newlines, special characters
and is controlled by the user, or it can be empty! Make sure that your
notification script can handle that!

#### -P /PATH/TO/SCRIPT
Run the given script for each process killed, beforehand. Must be an absolute path.

See `-N`, it behaves in the same way except being run before the process is killed.

Note that there is a small delay (200 milliseconds) in killing the chosen victim
to give some room for this program to be spawned and do something meaningful.
The invoked program has to be very fast to gather information from the running
process before it gets killed.

Any such delay, and the extra resources taken by the spawned process, always
take some toll and further stress the already stressed system. Therefore, the
invoked process should be as lean and fast as possible.

#### -g
Kill all processes that have same process group id (PGID) as the process
with excessive memory usage.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ running as your user.

Additionally, earlyoom can execute a script for each process killed, providing
information about the process via the `EARLYOOM_PID`, `EARLYOOM_UID` and
`EARLYOOM_NAME` environment variables. Pass `-N /path/to/script` to enable.
`EARLYOOM_NAME` environment variables. Pass `-N /path/to/script` to enable
after the process is killed, or `-P /path/to/script` to be invoked before.

Warning: In case of dryrun mode, the script will be executed in rapid
succession, ensure you have some sort of rate-limit implemented.
Expand Down Expand Up @@ -241,6 +242,7 @@ Usage: ./earlyoom [OPTION]...
-S SIZE[,KILL_SIZE] set free swap minimum to SIZE KiB
-n enable d-bus notifications
-N /PATH/TO/SCRIPT call script after oom kill
-P /PATH/TO/SCRIPT call script before oom kill
-g kill all processes within a process group
-d, --debug enable debugging messages
-v print version information and exit
Expand Down
24 changes: 24 additions & 0 deletions kill.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
// At most 1 notification per second when --dryrun is active
#define NOTIFY_RATELIMIT 1

// Sleep for this amount of milliseconds when invoking the pre-hook (otherwise
// when the pre-hook gets spawned, it doesn't have time to act)
#define PREHOOK_STARTUP_SLEEP_MS 200U

static bool isnumeric(char* str)
{
int i = 0;
Expand Down Expand Up @@ -138,6 +142,15 @@ static void notify_process_killed(const poll_loop_args_t* args, const procinfo_t
}
}

static void kill_process_prehook(const poll_loop_args_t* args, const procinfo_t* victim)
{
if (args->kill_process_prehook) {
// reuse notify_ext() to invoke, functionally it's the same as invoking
// for notification
notify_ext(args->kill_process_prehook, victim);
}
}

#if defined(__NR_pidfd_open) && defined(__NR_process_mrelease)
#define HAVE_MRELEASE
#else
Expand Down Expand Up @@ -544,6 +557,17 @@ void kill_process(const poll_loop_args_t* args, int sig, const procinfo_t* victi
victim->cmdline);
}

// Invoke program BEFORE killing a process. There is a small risk that there
// is not enough memory to spawn it, warn; and a brief period of sleep to
// let the program be able to start and do something meaningful.
if (sig != 0) {
warn("going to invoke program before killing: %s\n", args->kill_process_prehook);
kill_process_prehook(args, victim);

warn("sleeping for %ums to allow the prehook to act\n", PREHOOK_STARTUP_SLEEP_MS);
usleep(PREHOOK_STARTUP_SLEEP_MS * 1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a set sleep here, this should probably be a waitpid( <pre-hook-pid> ) call with a timeout.

That also allows an error to be printed for cases where the pre-hook timed out. And avoid any output if everything behaved as expected.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed for both.

Do you have a suggestion for the wait with timeout? https://man7.org/linux/man-pages/man2/signalfd.2.html ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not familiar with what environment earlyoom needs to compile against. But if we can assume a Linux kernel > 5.3 I think the simplest and most robust approach would be:

https://man7.org/linux/man-pages/man2/pidfd_open.2.html

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost three years since 30f968b , I think pidfd_open is ok.

}

int res = kill_wait(args, victim->pid, sig);
int saved_errno = errno;

Expand Down
4 changes: 3 additions & 1 deletion kill.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ typedef struct {
double swap_kill_percent;
/* send d-bus notifications? */
bool notify;
/* Path to script for programmatic notifications (or NULL) */
/* Path to script for programmatic notifications after killing (or NULL) */
char* notify_ext;
/* Path to script/binary for to execute before killing (or NULL) */
char* kill_process_prehook;
/* kill all processes within a process group */
bool kill_process_group;
/* do not kill processes owned by root */
Expand Down
13 changes: 12 additions & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ static void startup_selftests(poll_loop_args_t* args)
warn("%s: -N: notify script '%s' is not executable: %s\n", __func__, args->notify_ext, strerror(errno));
}
}
if (args->kill_process_prehook) {
if (args->kill_process_prehook[0] != '/') {
warn("%s: -P: pre-hook program '%s' is not an absolute path, disabling -P\n", __func__, args->kill_process_prehook);
args->kill_process_prehook = NULL;
} else if (access(args->kill_process_prehook, X_OK)) {
warn("%s: -P: pre-hook program '%s' is not executable: %s\n", __func__, args->kill_process_prehook, strerror(errno));
}
}

#ifdef PROFILE_FIND_LARGEST_PROCESS
struct timespec t0 = { 0 }, t1 = { 0 };
Expand Down Expand Up @@ -147,7 +155,7 @@ int main(int argc, char* argv[])
meminfo_t m = parse_meminfo();

int c;
const char* short_opt = "m:s:M:S:kingN:dvr:ph";
const char* short_opt = "m:s:M:S:kingN:P:dvr:ph";
struct option long_opt[] = {
{ "prefer", required_argument, NULL, LONG_OPT_PREFER },
{ "avoid", required_argument, NULL, LONG_OPT_AVOID },
Expand Down Expand Up @@ -229,6 +237,9 @@ int main(int argc, char* argv[])
case 'N':
args.notify_ext = optarg;
break;
case 'P':
args.kill_process_prehook = optarg;
break;
case 'd':
enable_debug = 1;
break;
Expand Down
Loading