Skip to content
Merged
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
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# processx (development version)

* The `$signal()` and `$interrupt()` methods now send the signal to the
child's whole process group by default on Unix. This matches the
behavior of `$kill()`. A new `group` argument (default `TRUE`) can be
set to `FALSE` to send the signal to the child process only. The
argument is ignored on Windows.

* The `grace` argument of the `kill()` method is now active on Unix
platforms. processx first tries to kill with `SIGTERM` with a
timeout of `grace` seconds. After the timeout, `SIGKILL` is sent as
Expand Down
32 changes: 25 additions & 7 deletions R/process.R
Original file line number Diff line number Diff line change
Expand Up @@ -355,16 +355,28 @@ process <- R6::R6Class(
#' and the 0 signal as well.
#' @param signal An integer scalar, the id of the signal to send to
#' the process. See [tools::pskill()] for the list of signals.

signal = function(signal) process_signal(self, private, signal),
#' @param group Whether to send the signal to the whole process group of
#' the child. The child is started in its own process group (via
#' `setsid()` on Unix), so by default the signal is delivered to the
#' child and any descendants that have not started a new group of
#' their own. Set to `FALSE` to send the signal to the child process
#' only. Ignored on Windows.

signal = function(signal, group = TRUE) {
process_signal(self, private, signal, group)
},

#' @description
#' Send an interrupt to the process. On Unix this is a
#' `SIGINT` signal, and it is usually equivalent to pressing CTRL+C at
#' the terminal prompt. On Windows, it is a CTRL+BREAK keypress.
#' Applications may catch these events. By default they will quit.
#' @param group Whether to send the interrupt to the whole process group
#' of the child. See `$signal()` for details. Ignored on Windows,
#' where the CTRL+BREAK event is always delivered to all processes
#' attached to the child's console.

interrupt = function() process_interrupt(self, private),
interrupt = function(group = TRUE) process_interrupt(self, private, group),

#' @description
#' Query the process id.
Expand Down Expand Up @@ -846,24 +858,30 @@ process_get_exit_status <- function(self, private) {
)
}

process_signal <- function(self, private, signal) {
process_signal <- function(self, private, signal, group) {
"!DEBUG process_signal `private$get_short_name()` `signal`"
chain_call(
c_processx_signal,
private$status,
as.integer(signal),
private$get_short_name()
private$get_short_name(),
as.logical(group)
)
}

process_interrupt <- function(self, private) {
process_interrupt <- function(self, private, group) {
"!DEBUG process_interrupt `private$get_short_name()`"
if (os_type() == "windows") {
pid <- as.character(self$get_pid())
st <- run(get_tool("interrupt"), c(pid, "c"), error_on_status = FALSE)
if (st$status == 0) TRUE else FALSE
} else {
chain_call(c_processx_interrupt, private$status, private$get_short_name())
chain_call(
c_processx_interrupt,
private$status,
private$get_short_name(),
as.logical(group)
)
}
}

Expand Down
20 changes: 18 additions & 2 deletions man/process.Rd

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

4 changes: 2 additions & 2 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ static const R_CallMethodDef callMethods[] = {
{ "processx_is_alive", (DL_FUNC) &processx_is_alive, 2 },
{ "processx_pty_close", (DL_FUNC) &processx_pty_close, 2 },
{ "processx_get_exit_status", (DL_FUNC) &processx_get_exit_status, 2 },
{ "processx_signal", (DL_FUNC) &processx_signal, 3 },
{ "processx_interrupt", (DL_FUNC) &processx_interrupt, 2 },
{ "processx_signal", (DL_FUNC) &processx_signal, 4 },
{ "processx_interrupt", (DL_FUNC) &processx_interrupt, 3 },
{ "processx_kill", (DL_FUNC) &processx_kill, 3 },
{ "processx_get_pid", (DL_FUNC) &processx_get_pid, 1 },
{ "processx_create_time", (DL_FUNC) &processx_create_time, 1 },
Expand Down
4 changes: 2 additions & 2 deletions src/processx.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ SEXP processx_wait(SEXP status, SEXP timeout, SEXP name);
SEXP processx_is_alive(SEXP status, SEXP name);
SEXP processx_pty_close(SEXP status, SEXP name);
SEXP processx_get_exit_status(SEXP status, SEXP name);
SEXP processx_signal(SEXP status, SEXP signal, SEXP name);
SEXP processx_interrupt(SEXP status, SEXP name);
SEXP processx_signal(SEXP status, SEXP signal, SEXP name, SEXP group);
SEXP processx_interrupt(SEXP status, SEXP name, SEXP group);
SEXP processx_kill(SEXP status, SEXP grace, SEXP name);
SEXP processx_get_pid(SEXP status);
SEXP processx_create_time(SEXP r_pid);
Expand Down
17 changes: 11 additions & 6 deletions src/unix/processx.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,11 +964,12 @@ SEXP processx_get_exit_status(SEXP status, SEXP name) {
* the SIGCHLD handler.
*/

SEXP processx_signal(SEXP status, SEXP signal, SEXP name) {
SEXP processx_signal(SEXP status, SEXP signal, SEXP name, SEXP group) {
processx_handle_t *handle = R_ExternalPtrAddr(status);
const char *cname = isNull(name) ? "???" : CHAR(STRING_ELT(name, 0));
pid_t pid;
int wstat, wp, ret, result;
int cgroup = LOGICAL(group)[0];

processx__block_sigchld();

Expand All @@ -983,13 +984,17 @@ SEXP processx_signal(SEXP status, SEXP signal, SEXP name) {
goto cleanup;
}

/* Otherwise try to send signal */
/* Otherwise try to send signal. The child is started in its own
process group (via setsid()), so its pgid equals its pid; negating
the pid sends the signal to the whole group. ESRCH/EPERM mean the
original target is gone (the PID/PGID may have been reused, possibly
by a process we may not signal). */
pid = handle->pid;
ret = kill(pid, INTEGER(signal)[0]);
ret = kill(cgroup ? -pid : pid, INTEGER(signal)[0]);

if (ret == 0) {
result = 1;
} else if (ret == -1 && errno == ESRCH) {
} else if (ret == -1 && (errno == ESRCH || errno == EPERM)) {
result = 0;
} else {
processx__unblock_sigchld();
Expand Down Expand Up @@ -1018,8 +1023,8 @@ SEXP processx_signal(SEXP status, SEXP signal, SEXP name) {
return ScalarLogical(result);
}

SEXP processx_interrupt(SEXP status, SEXP name) {
return processx_signal(status, ScalarInteger(2), name);
SEXP processx_interrupt(SEXP status, SEXP name, SEXP group) {
return processx_signal(status, ScalarInteger(2), name, group);
}

/* This is a special case of `processx_signal`, and we implement it almost
Expand Down
8 changes: 5 additions & 3 deletions src/win/processx.c
Original file line number Diff line number Diff line change
Expand Up @@ -1540,10 +1540,11 @@ SEXP processx_get_exit_status(SEXP status, SEXP name) {
}
}

SEXP processx_signal(SEXP status, SEXP signal, SEXP name) {
SEXP processx_signal(SEXP status, SEXP signal, SEXP name, SEXP group) {
processx_handle_t *handle = R_ExternalPtrAddr(status);
const char *cname = isNull(name) ? "???" : CHAR(STRING_ELT(name, 0));
DWORD err, exitcode = STILL_ACTIVE;
(void) group;

if (!handle) return ScalarLogical(0);
if (handle->collected) return ScalarLogical(0);
Expand Down Expand Up @@ -1591,13 +1592,14 @@ SEXP processx_signal(SEXP status, SEXP signal, SEXP name) {
}
}

SEXP processx_interrupt(SEXP status, SEXP name) {
SEXP processx_interrupt(SEXP status, SEXP name, SEXP group) {
(void) group;
R_THROW_ERROR("Internal processx error, `processx_interrupt()` should not be called");
return R_NilValue;
}

SEXP processx_kill(SEXP status, SEXP grace, SEXP name) {
return processx_signal(status, ScalarInteger(9), name);
return processx_signal(status, ScalarInteger(9), name, ScalarLogical(1));
}

SEXP processx_get_pid(SEXP status) {
Expand Down
Loading