Skip to content

Commit 882e3b2

Browse files
authored
Add process_exists() (#62)
* add process_exists * fix race in signal testing * update NEWS * do not check the exit code in signal testing in Windows
1 parent 2b5d32a commit 882e3b2

15 files changed

Lines changed: 198 additions & 113 deletions

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export(TIMEOUT_IMMEDIATE)
3232
export(TIMEOUT_INFINITE)
3333
export(is_process_handle)
3434
export(process_close_input)
35+
export(process_exists)
3536
export(process_kill)
3637
export(process_read)
3738
export(process_return_code)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
* replace `select()` with `poll()`
66

7+
* new API: `process_exists()`
8+
79
# subprocess 0.8.2
810

911
* fixes in test cases for `testthat` 2.0

R/subprocess.R

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,51 @@ NULL
33

44

55
#' Start a new child process.
6-
#'
6+
#'
77
#' @description
88
#' In Linux, the usual combination of `fork()` and `exec()`
99
#' is used to spawn a new child process. Standard streams are redirected
1010
#' over regular unnamed `pipe`s.
11-
#'
11+
#'
1212
#' In Windows a new process is spawned with `CreateProcess()` and
1313
#' streams are redirected over unnamed pipes obtained with
1414
#' `CreatePipe()`. However, because non-blocking (*overlapped*
1515
#' in Windows-speak) read/write is not supported for unnamed pipes,
1616
#' two reader threads are created for each new child process. These
1717
#' threads never touch memory allocated by R and thus they will not
1818
#' interfere with R interpreter's memory management (garbage collection).
19-
#'
20-
#'
19+
#'
20+
#'
2121
#' @details
2222
#' `command` is always prepended to `arguments` so that the
2323
#' child process can correcty recognize the name of its executable
2424
#' via its `argv` vector. This is done automatically by
2525
#' `spawn_process`.
26-
#'
26+
#'
2727
#' `environment` can be passed as a `character` vector whose
2828
#' elements take the form `"NAME=VALUE"`, a named `character`
2929
#' vector or a named `list`.
30-
#'
30+
#'
3131
#' `workdir` is the path to the directory where the new process is
3232
#' ought to be started. `NULL` and `""` mean that working
3333
#' directory is inherited from the parent.
34-
#'
34+
#'
3535
#' @section Termination:
36-
#'
36+
#'
3737
#' The `termination_mode` specifies what should happen when
3838
#' `process_terminate()` or `process_kill()` is called on a
3939
#' subprocess. If it is set to `TERMINATION_GROUP`, then the
4040
#' termination signal is sent to the parent and all its descendants
4141
#' (sub-processes). If termination mode is set to
4242
#' `TERMINATION_CHILD_ONLY`, only the child process spawned
4343
#' directly from the R session receives the signal.
44-
#'
44+
#'
4545
#' In Windows this is implemented with the job API, namely
4646
#' `CreateJobObject()`, `AssignProcessToJobObject()` and
4747
#' `TerminateJobObject()`. In Linux, the child calls `setsid()`
4848
#' after `fork()` but before `execve()`, and `kill()` is
4949
#' called with the negate process id.
50-
#'
50+
#'
5151
#' @param command Path to the executable.
5252
#' @param arguments Optional arguments for the program.
5353
#' @param environment Optional environment.
@@ -58,10 +58,10 @@ NULL
5858
#' @return `spawn_process()` returns an object of the
5959
#' *process handle* class.
6060
#' @rdname spawn_process
61-
#'
61+
#'
6262
#' @format `TERMINATION_GROUP` and `TERMINATION_CHILD_ONLY`
6363
#' are single `character` values.
64-
#'
64+
#'
6565
#' @export
6666
spawn_process <- function (command, arguments = character(), environment = character(),
6767
workdir = "", termination_mode = TERMINATION_GROUP)
@@ -76,7 +76,7 @@ spawn_process <- function (command, arguments = character(), environment = chara
7676
}
7777
environment <- paste(names(environment), as.character(environment), sep = '=')
7878
}
79-
79+
8080
if(!(is.null(workdir) || identical(workdir, ""))){
8181
workdir <- normalizePath(workdir, mustWork = TRUE)
8282
}
@@ -92,7 +92,7 @@ spawn_process <- function (command, arguments = character(), environment = chara
9292

9393
#' @param x Object to be printed or tested.
9494
#' @param ... Other parameters passed to the `print` method.
95-
#'
95+
#'
9696
#' @export
9797
#' @rdname spawn_process
9898
print.process_handle <- function (x, ...)
@@ -101,14 +101,14 @@ print.process_handle <- function (x, ...)
101101
cat('command : ', x$command, ' ', paste(x$arguments, collapse = ' '), '\n', sep = '')
102102
cat('system id : ', as.integer(x$c_handle), '\n', sep = '')
103103
cat('state : ', process_state(x), '\n', sep = '')
104-
104+
105105
invisible(x)
106106
}
107107

108108

109109
#' @description `is_process_handle()` verifies that an object is a
110110
#' valid *process handle* as returned by `spawn_process()`.
111-
#'
111+
#'
112112
#' @export
113113
#' @rdname spawn_process
114114
is_process_handle <- function (x)
@@ -118,37 +118,37 @@ is_process_handle <- function (x)
118118

119119

120120
#' Terminating a Child Process.
121-
#'
121+
#'
122122
#' @description
123-
#'
123+
#'
124124
#' These functions give access to the state of the child process and to
125125
#' its exit status (return code).
126-
#'
126+
#'
127127
#' The `timeout` parameter can take one of three values:
128128
#' \itemize{
129129
#' \item `0` which means no timeout
130130
#' \item `-1` which means "wait until there is data to read"
131131
#' \item a positive integer, which is the actual timeout in milliseconds
132132
#' }
133-
#'
133+
#'
134134
#' @details `process_wait()` checks the state of the child process
135135
#' by invoking the system call `waitpid()` or
136136
#' `WaitForSingleObject()`.
137-
#'
137+
#'
138138
#' @param handle Process handle obtained from `spawn_process`.
139139
#' @param timeout Optional timeout in milliseconds.
140-
#'
140+
#'
141141
#' @return `process_wait()` returns an `integer` exit code
142142
#' of the child process or `NA` if the child process has not exited
143143
#' yet. The same value can be accessed by `process_return_code()`.
144-
#'
144+
#'
145145
#' @name terminating
146146
#' @rdname terminating
147147
#' @export
148-
#'
148+
#'
149149
#' @seealso [spawn_process()], [process_read()]
150150
#' [signals()]
151-
#'
151+
#'
152152
process_wait <- function (handle, timeout = TIMEOUT_INFINITE)
153153
{
154154
stopifnot(is_process_handle(handle))
@@ -160,10 +160,10 @@ process_wait <- function (handle, timeout = TIMEOUT_INFINITE)
160160
#' `process_wait()` with no timeout and returns one of these
161161
#' values: `"not-started"`. `"running"`, `"exited"`,
162162
#' `"terminated"`.
163-
#'
163+
#'
164164
#' @rdname terminating
165165
#' @export
166-
#'
166+
#'
167167
process_state <- function (handle)
168168
{
169169
stopifnot(is_process_handle(handle))
@@ -174,17 +174,34 @@ process_state <- function (handle)
174174
#' @details `process_return_code()` gives access to the value
175175
#' returned also by `process_wait()`. It does not invoke
176176
#' `process_wait()` behind the scenes.
177-
#'
177+
#'
178178
#' @rdname terminating
179179
#' @export
180-
#'
180+
#'
181181
process_return_code <- function (handle)
182182
{
183183
stopifnot(is_process_handle(handle))
184184
.Call("C_process_return_code", handle$c_handle)
185185
}
186186

187187

188+
#' Check if process with a given id exists.
189+
#'
190+
#' @param x A process handle returned by [spawn_process] or a OS-level process id.
191+
#' @return `TRUE` if process exists, `FALSE` otherwise.
192+
#'
193+
#' @export
194+
#'
195+
process_exists <- function (x)
196+
{
197+
if (is_process_handle(x)) {
198+
x <- x$c_handle
199+
}
200+
201+
isTRUE(.Call("C_process_exists", as.integer(x)))
202+
}
203+
204+
188205
#' @description `TIMEOUT_INFINITE` denotes an "infinite" timeout
189206
#' (that is, wait until response is available) when waiting for an
190207
#' operation to complete.
@@ -197,7 +214,7 @@ TIMEOUT_INFINITE <- -1L
197214
#' @description `TIMEOUT_IMMEDIATE` denotes an "immediate" timeout
198215
#' (in other words, no timeout) when waiting for an operation to
199216
#' complete.
200-
#'
217+
#'
201218
#' @rdname terminating
202219
#' @export
203220
TIMEOUT_IMMEDIATE <- 0L
@@ -206,7 +223,7 @@ TIMEOUT_IMMEDIATE <- 0L
206223
#' @description `TERMINATION_GROUP`: `process_terminate(handle)`
207224
#' and `process_kill(handle)` deliver the signal to the child
208225
#' process pointed to by `handle` and all of its descendants.
209-
#'
226+
#'
210227
#' @rdname spawn_process
211228
#' @export
212229
TERMINATION_GROUP <- "group"
@@ -216,7 +233,7 @@ TERMINATION_GROUP <- "group"
216233
#' `process_terminate(handle)` and `process_kill(handle)`
217234
#' deliver the signal only to the child process pointed to by
218235
#' `handle` but to none of its descendants.
219-
#'
236+
#'
220237
#' @rdname spawn_process
221238
#' @export
222239
TERMINATION_CHILD_ONLY <- "child_only"

man/process_exists.Rd

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config-os.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#ifdef SUBPROCESS_WINDOWS
1919
#define EXPORT __declspec( dllexport )
2020
#else
21-
#define EXPORT
21+
#define EXPORT
2222
#endif
2323

2424

@@ -55,6 +55,7 @@
5555
#undef length
5656

5757
typedef HANDLE process_handle_type;
58+
typedef DWORD pid_type;
5859
typedef HANDLE pipe_handle_type;
5960

6061
constexpr pipe_handle_type HANDLE_CLOSED = nullptr;
@@ -63,6 +64,7 @@ constexpr pipe_handle_type HANDLE_CLOSED = nullptr;
6364

6465
#include <unistd.h>
6566
typedef pid_t process_handle_type;
67+
typedef pid_t pid_type;
6668
typedef int pipe_handle_type;
6769

6870
constexpr pipe_handle_type HANDLE_CLOSED = -1;

src/rapi.cc

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ SEXP C_process_spawn (SEXP _command, SEXP _arguments, SEXP _environment, SEXP _w
120120

121121
char ** arguments = to_C_array(_arguments);
122122
char ** environment = to_C_array(_environment);
123-
123+
124124
/* if environment if empty, simply ignore it */
125125
if (!environment || !*environment) {
126126
// allocated with Calloc() but Free() is still needed
@@ -222,7 +222,7 @@ SEXP C_process_read (SEXP _handle, SEXP _pipe, SEXP _timeout)
222222
/* determine which pipe */
223223
const char * pipe = translateChar(STRING_ELT(_pipe, 0));
224224
pipe_type which_pipe;
225-
225+
226226
if (!strncmp(pipe, "stdout", 6))
227227
which_pipe = PIPE_STDOUT;
228228
else if (!strncmp(pipe, "stderr", 6))
@@ -232,8 +232,8 @@ SEXP C_process_read (SEXP _handle, SEXP _pipe, SEXP _timeout)
232232
else {
233233
Rf_error("unrecognized `pipe` value");
234234
}
235-
236-
try_run(&process_handle_t::read, handle, which_pipe, timeout);
235+
236+
try_run(&process_handle_t::read, handle, which_pipe, timeout);
237237

238238
/* produce the result - a list of one or two elements */
239239
SEXP ans, nms;
@@ -259,7 +259,7 @@ SEXP C_process_close_input (SEXP _handle)
259259
{
260260
process_handle_t * handle = extract_process_handle(_handle);
261261
try_run(&process_handle_t::close_input, handle);
262-
return allocate_TRUE();
262+
return allocate_TRUE();
263263
}
264264

265265

@@ -272,7 +272,7 @@ SEXP C_process_write (SEXP _handle, SEXP _message)
272272
}
273273

274274
const char * message = translateChar(STRING_ELT(_message, 0));
275-
size_t ret = try_run(&process_handle_t::write, handle, message, strlen(message));
275+
size_t ret = try_run(&process_handle_t::write, handle, message, strlen(message));
276276

277277
return allocate_single_int((int)ret);
278278
}
@@ -370,6 +370,19 @@ SEXP C_process_send_signal (SEXP _handle, SEXP _signal)
370370
}
371371

372372

373+
SEXP C_process_exists (SEXP _pid)
374+
{
375+
if (!is_single_integer(_pid)) {
376+
Rf_error("`pid` must be a single integer value");
377+
}
378+
379+
int pid = INTEGER_DATA(_pid)[0];
380+
bool ret = subprocess::process_exists(static_cast<pid_type>(pid));
381+
382+
return allocate_single_bool(ret);
383+
}
384+
385+
373386
SEXP C_known_signals ()
374387
{
375388
SEXP ans;
@@ -415,7 +428,7 @@ SEXP C_known_signals ()
415428
#endif
416429

417430
setAttrib(ans, R_NamesSymbol, ansnames);
418-
431+
419432
/* ans, ansnames */
420433
UNPROTECT(2);
421434
return ans;
@@ -435,16 +448,16 @@ SEXP C_signal (SEXP _signal, SEXP _handler)
435448
if (!is_nonempty_string(_handler)) {
436449
error("`handler` needs to be a single character value");
437450
}
438-
451+
439452
const char * handler = translateChar(STRING_ELT(_handler, 0));
440453
if (!strncmp(handler, "ignore", 6) && !strncmp(handler, "default", 7)) {
441454
error("`handler` can be either \"ignore\" or \"default\"");
442455
}
443-
456+
444457
int sgn = INTEGER_DATA(_signal)[0];
445458
typedef void (*sighandler_t)(int);
446459
sighandler_t hnd = (strncmp(handler, "ignore", 6) ? SIG_DFL : SIG_IGN);
447-
460+
448461
if (signal(sgn, hnd) == SIG_ERR) {
449462
Rf_error("error while calling signal()");
450463
}

src/rapi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ EXPORT SEXP C_process_kill(SEXP _handle);
3636

3737
EXPORT SEXP C_process_send_signal(SEXP _handle, SEXP _signal);
3838

39+
EXPORT SEXP C_process_exists(SEXP _pid);
40+
3941
EXPORT SEXP C_known_signals();
4042

4143
EXPORT SEXP C_signal (SEXP _signal, SEXP _handler);

0 commit comments

Comments
 (0)