Skip to content

Commit

Permalink
Merge pull request #170 from r-lib/feature/kill-with-grace
Browse files Browse the repository at this point in the history
`ps_kill()`: `SIGTERM` first, then `SIGKILL` after a grace period
  • Loading branch information
gaborcsardi authored Aug 31, 2024
2 parents 954f516 + d0bcd7a commit d3104ee
Show file tree
Hide file tree
Showing 17 changed files with 389 additions and 26 deletions.
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
* `ps_handle()` now allowes a non-integer object as the pid, as long as
its value is integer.

* `ps_kill()` can now kill multiple processes concurrently.

* `ps_kill()` has a new `grace` argument. On Unix, if this argument is
not zero, then `ps_kill()` first sends a `TERM` signal, and waits for
the processes to quit gracefully, via `ps_wait()`. The processes that
are still alive after the grace period are then killed with `SIGKILL`.

# ps 1.7.7

* `ps_cpu_times()` values are now correct on newer arm64 macOS.
Expand Down
67 changes: 58 additions & 9 deletions R/low-level.R
Original file line number Diff line number Diff line change
Expand Up @@ -714,12 +714,24 @@ ps_terminate <- function(p = ps_handle()) {
.Call(psll_terminate, p)
}

#' Kill a process
#' Kill one or more processes
#'
#' Kill the current process with SIGKILL preemptively checking
#' whether PID has been reused. On Windows it uses `TerminateProcess()`.
#' Kill the process with SIGKILL preemptively checking whether PID has
#' been reused. On Windows it uses `TerminateProcess()`.
#'
#' @param p Process handle.
#' Note that since ps version 1.8, `ps_kill()` does not error if the
#' `p` process (or some processes if `p` is a list) are already terminated.
#'
#' @param p Process handle, or a list of process handles.
#' @param grace Grace period, in milliseconds, used on Unix. If it is not
#' zero, then `ps_kill()` first sends a `SIGTERM` signal to all processes
#' in `p`. If some proccesses do not terminate withing `grace`
#' milliseconds after the `SIGTERM` signal, `ps_kill()` kills them by
#' sending `SIGKILL` signals.
#' @return Character vector, with one element for each process handle in
#' `p`. If the process was already dead before `ps_kill()` tried to kill
#' it, the corresponding return value is `"dead"`. If `ps_kill()` just
#' killed it, it is `"killed"`.
#'
#' @family process handle functions
#' @export
Expand All @@ -732,9 +744,47 @@ ps_terminate <- function(p = ps_handle()) {
#' ps_is_running(p)
#' px$get_exit_status()

ps_kill <- function(p = ps_handle()) {
assert_ps_handle(p)
.Call(psll_kill, p)
ps_kill <- function(p = ps_handle(), grace = 200) {
p <- assert_ps_handle_or_handle_list(p)
grace <- assert_grace(grace)
if (ps_os_type()[["WINDOWS"]]) {
res <- lapply(p, function(pp) {
tryCatch({
if (ps_is_running(pp)) {
.Call(psll_kill, pp, 0L)
"killed"
} else {
"dead"
}
}, error = function(e) {
if (inherits(e, "no_such_process")) "dead" else e
})
})
} else {
res <- call_with_cleanup(psll_kill, p, grace)
}

ok <- map_lgl(res, is.character)
if (all(ok)) {
unlist(res)
} else {
for (i in which(!ok)) {
class(res[[i]]) <- res[[i]][[2]]
}
pids <- map_int(res[!ok], "[[", "pid")
nms <- map_chr(p[!ok], function(pp) {
tryCatch(ps_name(pp), error = function(e) "???")
})
pmsg <- paste0(pids, " (", nms, ")", collapse = ", ")
err <- structure(
list(
message = paste0("Failed to kill some processes: ", pmsg),
results = res
),
class = c("ps_error", "error", "condition")
)
stop(err)
}
}

#' List of child processes (process objects) of the process. Note that
Expand Down Expand Up @@ -1210,8 +1260,7 @@ ps_set_cpu_affinity <- function(p = ps_handle(), affinity) {
#' ps_wait(list(p1$as_ps_handle(), p2$as_ps_handle()), 1000)

ps_wait <- function(p, timeout = -1) {
if (!is.list(p)) p <- list(p)
assert_ps_handle_list(p)
p <- assert_ps_handle_or_handle_list(p)
timeout <- assert_integer(timeout)
call_with_cleanup(psll_wait, p, timeout)
}
22 changes: 22 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,18 @@ assert_pid <- function(x) {
" is not a process id (integer scalar)"))
}

assert_grace <- function(x) {
if (is.integer(x) && length(x) == 1 && !is.na(x) && x >= 0) return(x)
if (is.numeric(x) && length(x) == 1 && !is.na(x) && x >= 0) {
xi <- as.integer(x)
# if x is non-zero, then return non-zero
if (xi == 0 && x > 0) return(1)
return(as.integer(x))
}
stop(ps__invalid_argument(match.call()$x,
" is not a non-negative integer"))
}

assert_time <- function(x) {
if (inherits(x, "POSIXct")) return()
stop(ps__invalid_argument(match.call()$x,
Expand All @@ -159,6 +171,16 @@ assert_ps_handle_list <- function(x) {
))
}

assert_ps_handle_or_handle_list <- function(p) {
if (!is.list(p)) {
assert_ps_handle(p)
p <- list(p)
} else {
assert_ps_handle_list(p)
}
p
}

assert_flag <- function(x) {
if (is.logical(x) && length(x) == 1 && !is.na(x)) return()
stop(ps__invalid_argument(match.call()$x,
Expand Down
26 changes: 21 additions & 5 deletions man/ps_kill.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 81 additions & 5 deletions src/api-posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <unistd.h>
#include <sys/statvfs.h>
#include <sys/resource.h>
#include <string.h>

#include "ps-internal.h"

Expand Down Expand Up @@ -79,11 +80,86 @@ SEXP psll_terminate(SEXP p) {
}


SEXP psll_kill(SEXP p) {
SEXP res, s;
PROTECT(s = ScalarInteger(SIGKILL));
PROTECT(res = psll_send_signal(p, s));
UNPROTECT(2);
SEXP psll_kill(SEXP p, SEXP grace) {
R_xlen_t i, num_handles = Rf_xlength(p);

// check if handles are ok, and they are not pid 0
for (i = 0; i < num_handles; i++) {
ps_handle_t *handle = R_ExternalPtrAddr(VECTOR_ELT(p, i));
if (!handle) Rf_error("Process pointer clean up already");
if (handle->pid == 0) {
Rf_error(
"preventing sending KILL signal to process with PID 0 as it "
"would affect every process in the process group of the "
"calling process (Sys.getpid()) instead of PID 0"
);
}
}

// OK, we can give it a go then
SEXP res = PROTECT(Rf_allocVector(VECSXP, num_handles));
SEXP ridx = PROTECT(Rf_allocVector(INTSXP, num_handles));
int *idx = INTEGER(ridx);
memset(idx, 0, sizeof(int) * num_handles);

// Send out TERM signals
int signals_sent = 0;
for (i = 0; i < num_handles; i++) {
if (!LOGICAL(psll_is_running(VECTOR_ELT(p, i)))[0]) {
SET_VECTOR_ELT(res, i, Rf_mkString("dead"));
continue;
}
ps_handle_t *handle = R_ExternalPtrAddr(VECTOR_ELT(p, i));
int ret = kill(handle->pid, SIGTERM);
if (ret == -1) {
if (errno == ESRCH) {
SET_VECTOR_ELT(res, i, Rf_mkString("dead"));
continue;
} else if (errno == EPERM || errno == EACCES) {
ps__access_denied_pid(handle->pid, "");
} else {
ps__set_error_from_errno();
}
SET_VECTOR_ELT(res, i, Rf_duplicate(ps__last_error));
} else {
idx[signals_sent++] = i;
}
}

// all done, exit early
if (signals_sent == 0) {
UNPROTECT(2);
return res;
}

SEXP p2 = PROTECT(Rf_allocVector(VECSXP, signals_sent));
for (i = 0; i < signals_sent; i++) {
SET_VECTOR_ELT(p2, i, VECTOR_ELT(p, idx[i]));
}
SEXP waitres = PROTECT(psll_wait(p2, grace));
for (i = 0; i < signals_sent; i++) {
if (LOGICAL(waitres)[i]) {
SET_VECTOR_ELT(res, idx[i], Rf_mkString("terminated"));
} else {
ps_handle_t *handle = R_ExternalPtrAddr(VECTOR_ELT(p2, i));
int ret = kill(handle->pid, SIGKILL);
if (ret == -1) {
if (errno == ESRCH) {
SET_VECTOR_ELT(res, idx[i], Rf_mkString("dead"));
continue;
} else if (errno == EPERM || errno == EACCES) {
ps__access_denied_pid(handle->pid, "");
} else {
ps__set_error_from_errno();
}
SET_VECTOR_ELT(res, idx[i], Rf_duplicate(ps__last_error));
} else {
SET_VECTOR_ELT(res, idx[i], Rf_mkString("killed"));
}
}
}

UNPROTECT(4);
return res;
}

Expand Down
2 changes: 1 addition & 1 deletion src/api-windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ SEXP psll_terminate(SEXP p) {
return R_NilValue;
}

SEXP psll_kill(SEXP p) {
SEXP psll_kill(SEXP p, SEXP grace) {
ps_handle_t *handle = R_ExternalPtrAddr(p);
SEXP running, ret;

Expand Down
2 changes: 1 addition & 1 deletion src/dummy.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ SEXP psll_send_signal(SEXP x, SEXP y) { return ps__dummy("ps_send_signal"); }
SEXP psll_suspend(SEXP x) { return ps__dummy("ps_suspend"); }
SEXP psll_resume(SEXP x) { return ps__dummy("ps_resume"); }
SEXP psll_terminate(SEXP x) { return ps__dummy("ps_terminate"); }
SEXP psll_kill(SEXP x) { return ps__dummy("ps_kill"); }
SEXP psll_kill(SEXP x, SEXP grace) { return ps__dummy("ps_kill"); }
SEXP psll_num_fds(SEXP x) { return ps__dummy("ps_num_fds"); }
SEXP psll_open_files(SEXP x) { return ps__dummy("ps_open_files"); }
SEXP psll_interrupt(SEXP x, SEXP y, SEXP z) { return ps__dummy("ps_interrupt"); }
Expand Down
5 changes: 5 additions & 0 deletions src/extra.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ void *ps__access_denied(const char *msg) {
msg && strlen(msg) ? msg : "Permission denied");
}

void *ps__access_denied_pid(long pid, const char *msg) {
return ps__set_error_impl("access_denied", 0, pid,
msg && strlen(msg) ? msg : "Permission denied");
}

void *ps__zombie_process(long pid) {
return ps__set_error_impl("zombie_process", 0, pid,
"Process is a zombie, pid %ld", pid);
Expand Down
2 changes: 1 addition & 1 deletion src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ static const R_CallMethodDef callMethods[] = {
{ "psll_suspend", (DL_FUNC) psll_suspend, 1 },
{ "psll_resume", (DL_FUNC) psll_resume, 1 },
{ "psll_terminate", (DL_FUNC) psll_terminate, 1 },
{ "psll_kill", (DL_FUNC) psll_kill, 1 },
{ "psll_kill", (DL_FUNC) psll_kill, 2 },
{ "psll_num_fds", (DL_FUNC) psll_num_fds, 1 },
{ "psll_open_files", (DL_FUNC) psll_open_files, 1 },
{ "psll_interrupt", (DL_FUNC) psll_interrupt, 3 },
Expand Down
1 change: 1 addition & 0 deletions src/ps-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void *ps__set_error_from_errno(void);
SEXP ps__throw_error(void);

void *ps__access_denied(const char *msg);
void *ps__access_denied_pid(long pid, const char *msg);
void *ps__no_such_process(long pid, const char *name);
void *ps__zombie_process(long pid);
void *ps__no_memory(const char *msg);
Expand Down
2 changes: 1 addition & 1 deletion src/ps.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ SEXP psll_send_signal(SEXP p, SEXP sig);
SEXP psll_suspend(SEXP p);
SEXP psll_resume(SEXP p);
SEXP psll_terminate(SEXP p);
SEXP psll_kill(SEXP p);
SEXP psll_kill(SEXP p, SEXP grace);
SEXP psll_num_fds(SEXP p);
SEXP psll_open_files(SEXP p);
SEXP psll_interrupt(SEXP p, SEXP ctrlc, SEXP interrupt_path);
Expand Down
Loading

0 comments on commit d3104ee

Please sign in to comment.