Skip to content

Kill with grace #367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 77 commits into
base: safe-cleanup
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
5ac96f6
Extract `c_processx_wait()`
lionel- Apr 19, 2023
42699d4
Implement `kill(grace = )`
lionel- Apr 19, 2023
dcb71f5
Add `cleanup_grace` argument to `run()`
lionel- Apr 19, 2023
11d37ca
Add `cleanup_grace` argument to process ctor
lionel- Apr 19, 2023
e83a48c
Use `cleanup_grace` in finaliser
lionel- Apr 27, 2023
b586b12
Poll for completion of cleanup process in unit test
lionel- Apr 27, 2023
b693822
Rbuildignore test library
lionel- Apr 27, 2023
da4cf5a
Ignore SIGPIPE while writing to fd
gaborcsardi Jun 17, 2023
a7b9ea9
Avoid strict prototype warnings on Windows
gaborcsardi Jun 30, 2023
b58e1cc
Update NEWS
gaborcsardi Jun 30, 2023
49432ca
Remove win unicode tests
gaborcsardi Jun 30, 2023
25cdd52
Merge branch 'main' of https://github.com/r-lib/processx
gaborcsardi Jun 30, 2023
2d09d47
Increment version number to 3.8.2
gaborcsardi Jun 30, 2023
25812a1
Increment version number to 3.8.2.9000
gaborcsardi Jul 1, 2023
4125a2e
RStudio -> Posit in authors
gaborcsardi Nov 3, 2023
53759eb
usethis::use_tidy_coc()
gaborcsardi Nov 3, 2023
2247366
Use pak::pak("r-lib/processx") in README
gaborcsardi Nov 3, 2023
25520ce
usethis::use_mit_license()
gaborcsardi Nov 3, 2023
5e0e880
usethis::use_tidy_description()
gaborcsardi Nov 3, 2023
d72e110
usethis::use_tidy_github_actions()
gaborcsardi Nov 3, 2023
570d663
Fix printf format string
gaborcsardi Dec 9, 2023
a539803
GHA: test on R-devel Windows
gaborcsardi Dec 10, 2023
e516059
Fix more printf() format strings, on Windows
gaborcsardi Dec 10, 2023
1ea63a1
usethis::use_github_links()
gaborcsardi Dec 10, 2023
f463f83
Redocument
gaborcsardi Dec 10, 2023
b57dc28
Increment version number to 3.8.3
gaborcsardi Dec 10, 2023
bb1fa3d
Increment version number to 3.8.3.9000
gaborcsardi Dec 10, 2023
0362a37
Add a native gcov_flush function
gaborcsardi Dec 18, 2023
861aeff
Add COPYRIGHTS file
gaborcsardi Mar 16, 2024
f0b7713
Update NEWS for release
gaborcsardi Mar 16, 2024
355747c
More copyrights updates
gaborcsardi Mar 16, 2024
956591a
Increment version number to 3.8.4
gaborcsardi Mar 16, 2024
5bb11ea
Increment version number to 3.8.4.9000
gaborcsardi Mar 17, 2024
6d12d99
CI: test on R-nect Windows
gaborcsardi Mar 31, 2024
da1c65d
Skip some failing tests temporarily
gaborcsardi Mar 31, 2024
95ba3aa
Skip the correct tests on Windows
gaborcsardi Mar 31, 2024
c3ab227
Do not use R's RNG to generate tree id
gaborcsardi Apr 2, 2024
c8ed816
Use latest GHA workflows
gaborcsardi Aug 5, 2024
e61bf67
Update test snapshots for new syntax highlighting
gaborcsardi Aug 6, 2024
3f9af6d
Fix test snapshots with both old and new cli
gaborcsardi Aug 6, 2024
f080ee0
Skip some tests in covr
gaborcsardi Aug 6, 2024
3d769d3
Skip a fragile test on GHA
gaborcsardi Aug 6, 2024
bd51c8b
Merge branch 'main' into fix/gha-latest
gaborcsardi Aug 6, 2024
d6a81d9
Merge pull request #387 from r-lib/fix/gha-latest
gaborcsardi Aug 6, 2024
b0a2a3e
Strip shared lib on Linux, if $_R_SHLIB_STRIP_=true
gaborcsardi Aug 22, 2024
3cbce84
Merge pull request #388 from r-lib/fix/strip-linux
gaborcsardi Aug 23, 2024
fb3b4d8
Update NEWS before release
gaborcsardi Jan 8, 2025
f084ec9
Fix curl fd poll tests
gaborcsardi Jan 8, 2025
6ca5da0
GHA: use debug-shell action
gaborcsardi Jan 8, 2025
bab6d3f
Simplify curl fd poll tests
gaborcsardi Jan 8, 2025
3bd3c85
Increment version number to 3.8.5
gaborcsardi Jan 8, 2025
118704a
Increment version number to 3.8.5.9000
gaborcsardi Jan 8, 2025
e190b4a
Creating a pipe does not use the RNG, on Windows
gaborcsardi Jan 28, 2025
a82463e
Make process cloneable, to avoid warnings from R6
gaborcsardi Feb 19, 2025
f542080
Update NEWS
gaborcsardi Feb 19, 2025
511dc4a
Add r-hub-ci workflow
gaborcsardi Feb 19, 2025
d762515
Increment version number to 3.8.6
gaborcsardi Feb 19, 2025
1c9500a
Increment version number to 3.8.6.9000
gaborcsardi Feb 21, 2025
dd80b0f
Make $finalize() private
gaborcsardi Feb 22, 2025
f03ce19
Upkeep 2025
gaborcsardi Apr 25, 2025
0b1e094
usethis::use_testthat(3)
gaborcsardi Apr 26, 2025
8f228ca
usethis:::use_codecov_badge("r-lib/processx")
gaborcsardi Apr 26, 2025
7178687
Add Valgrind suppression file for R-hub
gaborcsardi Apr 26, 2025
91b3949
Add ROR for Posit in DESCRIPTION
gaborcsardi Apr 26, 2025
689ba70
Use air
gaborcsardi Apr 26, 2025
2418198
Extend Valgrind suppression file
gaborcsardi Apr 26, 2025
2bbdf2d
Fix DESCRIPTION typo
gaborcsardi Apr 26, 2025
136e3f8
Switch to expect_snapshot(error = TRUE)
gaborcsardi Apr 26, 2025
d3589db
usethis::use_mit_license()
gaborcsardi Apr 26, 2025
b0c010b
usethis::use_tidy_description()
gaborcsardi Apr 26, 2025
8778aa7
usethis::use_tidy_github_actions()
gaborcsardi Apr 26, 2025
8524951
Fix platform-specific snapshot tests
gaborcsardi Apr 26, 2025
b2bae39
Add Windows test snapshots
gaborcsardi Apr 26, 2025
df05796
Merge pull request #397 from r-lib/upkeep-2025-04
gaborcsardi Apr 26, 2025
6f1ee01
Merge commit 'df0579681e7953e52174614fba8836b9a9c0afbc'
gaborcsardi Apr 26, 2025
86528ec
Code formatting
gaborcsardi Apr 26, 2025
200d27c
Adjust Linux test snapshots
gaborcsardi Apr 26, 2025
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
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ Suggests:
curl,
debugme,
parallel,
pkgload,
rlang (>= 1.0.2),
testthat (>= 3.0.0),
webfakes,
withr
Encoding: UTF-8
RoxygenNote: 7.2.0
RoxygenNote: 7.2.3
Roxygen: list(markdown = TRUE)
Config/testthat/edition: 3
Config/Needs/website: tidyverse/tidytemplate
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# processx (development version)

* 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
before.

# processx 3.8.1

* On Unixes, R processes created by callr now feature a `SIGTERM`
Expand Down
8 changes: 8 additions & 0 deletions R/assertions.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ on_failure(is_flag) <- function(call, env) {
paste0(deparse(call$x), " is not a flag (length 1 logical)")
}

is_numeric_scalar <- function(x) {
is.numeric(x) && length(x) == 1 && !is.na(x)
}

on_failure(is_numeric_scalar) <- function(call, env) {
paste0(deparse(call$x), " is not a length 1 number")
}

is_integerish_scalar <- function(x) {
is.numeric(x) && length(x) == 1 && !is.na(x) && round(x) == x
}
Expand Down
12 changes: 7 additions & 5 deletions R/initialize.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
process_initialize <- function(self, private, command, args,
stdin, stdout, stderr, pty, pty_options,
connections, poll_connection, env, cleanup,
cleanup_tree, wd, echo_cmd, supervise,
windows_verbatim_args, windows_hide_window,
windows_detached_process, encoding,
post_process) {
cleanup_tree, cleanup_grace, wd, echo_cmd,
supervise, windows_verbatim_args,
windows_hide_window, windows_detached_process,
encoding, post_process) {

"!DEBUG process_initialize `command`"

Expand All @@ -45,6 +45,7 @@ process_initialize <- function(self, private, command, args,
is.null(env) || is_env_vector(env),
is_flag(cleanup),
is_flag(cleanup_tree),
is_numeric_scalar(cleanup_grace),
is_string_or_null(wd),
is_flag(echo_cmd),
is_flag(windows_verbatim_args),
Expand Down Expand Up @@ -99,6 +100,7 @@ process_initialize <- function(self, private, command, args,
private$args <- args
private$cleanup <- cleanup
private$cleanup_tree <- cleanup_tree
private$cleanup_grace <- cleanup_grace
private$wd <- wd
private$pstdin <- stdin
private$pstdout <- stdout
Expand Down Expand Up @@ -139,7 +141,7 @@ process_initialize <- function(self, private, command, args,
c_processx_exec,
command, c(command, args), pty, pty_options,
connections, env, windows_verbatim_args, windows_hide_window,
windows_detached_process, private, cleanup, wd, encoding,
windows_detached_process, private, cleanup, cleanup_grace, wd, encoding,
paste0("PROCESSX_", private$tree_id, "=YES")
)

Expand Down
24 changes: 15 additions & 9 deletions R/process.R
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ process <- R6::R6Class(
#' object is garbage collected.
#' @param cleanup_tree Whether to kill the process and its child
#' process tree when the `process` object is garbage collected.
#' @param cleanup_grace Grace period between `SIGTERM` and `SIGKILL`.
#' Only has an effect on Unix platforms. Set to 0 to terminate abruptly
#' with `SIGKILL` only. Currently defaults to 0 until we implement
#' a better approach on session quit.
#' @param wd Working directory of the process. It must exist.
#' If `NULL`, then the current working directory is used.
#' @param echo_cmd Whether to print the command to the screen before
Expand Down Expand Up @@ -212,17 +216,17 @@ process <- R6::R6Class(
initialize = function(command = NULL, args = character(),
stdin = NULL, stdout = NULL, stderr = NULL, pty = FALSE,
pty_options = list(), connections = list(), poll_connection = NULL,
env = NULL, cleanup = TRUE, cleanup_tree = FALSE, wd = NULL,
echo_cmd = FALSE, supervise = FALSE, windows_verbatim_args = FALSE,
windows_hide_window = FALSE, windows_detached_process = !cleanup,
encoding = "", post_process = NULL)
env = NULL, cleanup = TRUE, cleanup_tree = FALSE, cleanup_grace = 0.0,
wd = NULL, echo_cmd = FALSE, supervise = FALSE,
windows_verbatim_args = FALSE, windows_hide_window = FALSE,
windows_detached_process = !cleanup, encoding = "", post_process = NULL)

process_initialize(self, private, command, args, stdin,
stdout, stderr, pty, pty_options, connections,
poll_connection, env, cleanup, cleanup_tree, wd,
echo_cmd, supervise, windows_verbatim_args,
windows_hide_window, windows_detached_process,
encoding, post_process),
poll_connection, env, cleanup, cleanup_tree,
cleanup_grace, wd, echo_cmd, supervise,
windows_verbatim_args, windows_hide_window,
windows_detached_process, encoding, post_process),

#' @description
#' Cleanup method that is called when the `process` object is garbage
Expand Down Expand Up @@ -650,6 +654,7 @@ process <- R6::R6Class(
args = NULL, # Save 'args' argument here
cleanup = NULL, # cleanup argument
cleanup_tree = NULL, # cleanup_tree argument
cleanup_grace = NULL, # cleanup_grace argument
stdin = NULL, # stdin argument or stream
stdout = NULL, # stdout argument or stream
stderr = NULL, # stderr argument or stream
Expand Down Expand Up @@ -737,7 +742,8 @@ process_interrupt <- function(self, private) {

process_kill <- function(self, private, grace, close_connections) {
"!DEBUG process_kill '`private$get_short_name()`', pid `self$get_pid()`"
ret <- chain_call(c_processx_kill, private$status, as.numeric(grace),

ret <- chain_clean_call(c_processx_kill, private$status, as.numeric(grace),
private$get_short_name())
if (close_connections) private$close_connections()
ret
Expand Down
10 changes: 6 additions & 4 deletions R/run.R
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
#' both streams in UTF-8 currently.
#' @param cleanup_tree Whether to clean up the child process tree after
#' the process has finished.
#' @param cleanup_grace Passed to `kill()` or `kill_tree()` on cleanup.
#' @param ... Extra arguments are passed to `process$new()`, see
#' [process]. Note that you cannot pass `stout` or `stderr` here,
#' because they are used internally by `run()`. You can use the
Expand Down Expand Up @@ -162,7 +163,7 @@ run <- function(
stderr_line_callback = NULL, stderr_callback = NULL,
stderr_to_stdout = FALSE, env = NULL,
windows_verbatim_args = FALSE, windows_hide_window = FALSE,
encoding = "", cleanup_tree = FALSE, ...) {
encoding = "", cleanup_tree = FALSE, cleanup_grace = 0.1, ...) {

assert_that(is_flag(error_on_status))
assert_that(is_time_interval(timeout))
Expand All @@ -176,6 +177,7 @@ run <- function(
assert_that(is.null(stdout_callback) || is.function(stdout_callback))
assert_that(is.null(stderr_callback) || is.function(stderr_callback))
assert_that(is_flag(cleanup_tree))
assert_that(is_numeric_scalar(cleanup_grace))
assert_that(is_flag(stderr_to_stdout))
## The rest is checked by process$new()
"!DEBUG run() Checked arguments"
Expand All @@ -195,9 +197,9 @@ run <- function(

## We make sure that the process is eliminated
if (cleanup_tree) {
on.exit(pr$kill_tree(), add = TRUE)
defer(pr$kill_tree(grace = cleanup_grace))
} else {
on.exit(pr$kill(), add = TRUE)
defer(pr$kill(grace = cleanup_grace))
}

## If echo, then we need to create our own callbacks.
Expand Down Expand Up @@ -238,7 +240,7 @@ run <- function(
resenv$errbuf$push(pr$read_error())
resenv$errbuf$read()
}
tryCatch(pr$kill(), error = function(e) NULL)
tryCatch(pr$kill(grace = cleanup_grace), error = function(e) NULL)
signalCondition(new_process_interrupt_cond(
list(
interrupt = TRUE, stderr = err, stdout = out,
Expand Down
21 changes: 21 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,24 @@ defer <- function(expr, frame = parent.frame(), after = FALSE) {
thunk <- as.call(list(function() expr))
do.call(on.exit, list(thunk, add = TRUE, after = after), envir = frame)
}

rimraf <- function(...) {
x <- file.path(...)
if ("~" %in% x) stop("Cowardly refusing to delete `~`")
unlink(x, recursive = TRUE, force = TRUE)
}

get_test_lib <- function(lib) {
if (pkgload::is_dev_package("processx")) {
path <- "src"
} else {
path <- paste0('libs', .Platform$r_arch)
}

system.file(
package = "processx",
path,
"test",
paste0(lib, .Platform$dynlib.ext)
)
}
6 changes: 6 additions & 0 deletions man/process.Rd

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

1 change: 1 addition & 0 deletions man/process_initialize.Rd

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

3 changes: 3 additions & 0 deletions man/run.Rd

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

6 changes: 5 additions & 1 deletion src/Makevars
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ OBJECTS = init.o poll.o errors.o processx-connection.o \

.PHONY: all clean

all: tools/px tools/sock supervisor/supervisor client$(SHLIB_EXT) $(SHLIB)
all: tools/px tools/sock supervisor/supervisor client$(SHLIB_EXT) $(SHLIB) \
test/sigtermignore$(SHLIB_EXT)

tools/px: tools/px.c
$(CC) $(CFLAGS) $(LDFLAGS) -Wall tools/px.c -o tools/px
Expand All @@ -30,6 +31,9 @@ client$(SHLIB_EXT): $(CLIENT_OBJECTS)
patchelf --remove-needed libR.so client$(SHLIB_EXT); \
fi

test/sigtermignore$(SHLIB_EXT): test/sigtermignore.o
$(SHLIB_LINK) -o test/sigtermignore$(SHLIB_EXT) test/sigtermignore.o

clean:
rm -rf $(SHLIB) $(OBJECTS) $(CLIENT_OBJECTS) \
supervisor/supervisor supervisor/supervisor.dSYM \
Expand Down
2 changes: 1 addition & 1 deletion src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ SEXP processx__set_boot_time(SEXP);

static const R_CallMethodDef callMethods[] = {
CLEANCALL_METHOD_RECORD,
{ "processx_exec", (DL_FUNC) &processx_exec, 14 },
{ "processx_exec", (DL_FUNC) &processx_exec, 15 },
{ "processx_wait", (DL_FUNC) &processx_wait, 3 },
{ "processx_is_alive", (DL_FUNC) &processx_is_alive, 2 },
{ "processx_get_exit_status", (DL_FUNC) &processx_get_exit_status, 2 },
Expand Down
5 changes: 5 additions & 0 deletions src/install.libs.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ file.copy(files, dest, overwrite = TRUE)
if (file.exists("symbols.rds")) {
file.copy("symbols.rds", dest, overwrite = TRUE)
}

test_files <- Sys.glob(paste0("test/*", SHLIB_EXT))
test_dest <- file.path(dest, "test")
dir.create(test_dest, recursive = TRUE, showWarnings = FALSE)
file.copy(test_files, test_dest, overwrite = TRUE)
4 changes: 2 additions & 2 deletions src/processx.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ extern "C" {
SEXP processx_exec(SEXP command, SEXP args, SEXP pty, SEXP pty_options,
SEXP connections, SEXP env, SEXP windows_verbatim_args,
SEXP windows_hide_window, SEXP windows_detached_process,
SEXP private_, SEXP cleanup, SEXP wd, SEXP encoding,
SEXP tree_id);
SEXP private_, SEXP cleanup, SEXP cleanup_signal,
SEXP wd, SEXP encoding, SEXP tree_id);
SEXP processx_wait(SEXP status, SEXP timeout, SEXP name);
SEXP processx_is_alive(SEXP status, SEXP name);
SEXP processx_get_exit_status(SEXP status, SEXP name);
Expand Down
11 changes: 11 additions & 0 deletions src/test/sigtermignore.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef _WIN32

#include <Rinternals.h>
#include <R_ext/Rdynload.h>
#include <signal.h>

void R_init_sigtermignore(DllInfo *dll) {
signal(SIGTERM, SIG_IGN);
Copy link
Member Author

Choose a reason for hiding this comment

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

Since I wrote this branch I've added sigterm handlers to px in r-lib/ps#149. We could use this instead of this client lib for our unit tests by depending on the dev version of ps.

}

#endif
7 changes: 7 additions & 0 deletions src/unix/processx-unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ typedef struct processx_handle_s {
int fd2; /* readable */
int waitpipe[2]; /* use it for wait() with timeout */
int cleanup;
int cleanup_signal;
double cleanup_grace;
double create_time;
processx_connection_t *pipes[3];
int ptyfd;
Expand All @@ -36,7 +38,12 @@ void processx__sigchld_callback(int sig, siginfo_t *info, void *ctx);
void processx__setup_sigchld(void);
void processx__remove_sigchld(void);
void processx__block_sigchld(void);
void processx__block_sigchld_save(sigset_t *old);
void processx__unblock_sigchld(void);
void processx__procmask_set(sigset_t *set);

int c_processx_wait(processx_handle_t *handle, int timeout, const char *name);
int c_processx_kill(SEXP status, double grace, SEXP name);

void processx__finalizer(SEXP status);

Expand Down
Loading