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

* New `process$get_end_time()` method returns the time when the process
exited as a `POSIXct`, or `NULL` if it is still running (#218).

* `process$new()` and `run()` now support `pty = TRUE` on Windows 10 version
1809 and later, in addition to Unix. The Windows implementation uses the
ConPTY API (`CreatePseudoConsole`). The API is loaded dynamically so
Expand Down
24 changes: 24 additions & 0 deletions R/process.R
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,17 @@ process <- R6::R6Class(

get_start_time = function() process_get_start_time(self, private),

#' @description
#' `$get_end_time()` returns the time when the process finished,
#' or `NULL` if it is still running.
#' On Unix the timestamp is recorded when R first notices the exit
#' (via the `SIGCHLD` handler or a call to `$is_alive()`,
#' `$get_exit_status()`, or `$wait()`), so it may be slightly later
#' than the actual kernel exit time.
#' On Windows the exact kernel exit time is used.

get_end_time = function() process_get_end_time(self, private),

#' @description
#' `$is_supervised()` returns whether the process is being tracked by
#' supervisor process.
Expand Down Expand Up @@ -726,6 +737,7 @@ process <- R6::R6Class(
cleanfiles = NULL, # which temp stdout/stderr file(s) to clean up
wd = NULL, # working directory (or NULL for current)
starttime = NULL, # timestamp of start
endtime = NULL, # timestamp of exit, or 0 if not yet exited
echo_cmd = NULL, # whether to echo the command
windows_verbatim_args = NULL,
windows_hide_window = NULL,
Expand Down Expand Up @@ -848,6 +860,18 @@ process_get_start_time <- function(self, private) {
format_unix_time(private$starttime)
}

process_get_end_time <- function(self, private) {
if (!is.null(private$endtime)) {
return(private$endtime)
}
et <- chain_call(c_processx__proc_end_time, private$status)
if (is.null(et)) {
return(NULL)
}
private$endtime <- format_unix_time(et)
private$endtime
}

process_get_pid <- function(self, private) {
chain_call(c_processx_get_pid, private$status)
}
Expand Down
19 changes: 19 additions & 0 deletions man/process.Rd

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

14 changes: 14 additions & 0 deletions src/create-time.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,17 @@ SEXP processx__proc_start_time(SEXP status) {

return ScalarReal(handle->create_time);
}

SEXP processx__proc_end_time(SEXP status) {
processx_handle_t *handle = R_ExternalPtrAddr(status);

if (!handle) {
R_THROW_ERROR("Internal processx error, handle already removed");
}

if (handle->end_time == 0.0) {
return R_NilValue;
} else {
return ScalarReal(handle->end_time);
}
}
1 change: 1 addition & 0 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ static const R_CallMethodDef callMethods[] = {
{ "processx_create_named_pipe", (DL_FUNC) &processx_create_named_pipe, 2 },
{ "processx_write_named_pipe", (DL_FUNC) &processx_write_named_pipe, 2 },
{ "processx__proc_start_time", (DL_FUNC) &processx__proc_start_time, 1 },
{ "processx__proc_end_time", (DL_FUNC) &processx__proc_end_time, 1 },
{ "processx__set_boot_time", (DL_FUNC) &processx__set_boot_time, 1 },

{ "processx_connection_create", (DL_FUNC) &processx_connection_create, 2 },
Expand Down
1 change: 1 addition & 0 deletions src/processx.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ SEXP processx_poll(SEXP statuses, SEXP conn, SEXP ms);

SEXP processx__process_exists(SEXP pid);
SEXP processx__proc_start_time(SEXP status);
SEXP processx__proc_end_time(SEXP status);
SEXP processx__unload_cleanup(void);

SEXP processx_is_named_pipe_open(SEXP pipe_ext);
Expand Down
1 change: 1 addition & 0 deletions src/unix/processx-unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ typedef struct processx_handle_s {
int waitpipe[2]; /* use it for wait() with timeout */
int cleanup;
double create_time;
double end_time; /* 0.0 until the process exits */
processx_connection_t *pipes[3];
int ptyfd;
int pty_child_fd; /* parent holds child PTY fd open to prevent data loss on macOS */
Expand Down
10 changes: 10 additions & 0 deletions src/unix/processx.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <time.h>

#include "../processx.h"
#include "../cleancall.h"
Expand Down Expand Up @@ -652,6 +653,15 @@ void processx__collect_exit_status(SEXP status, int retval, int wstat) {
handle->exitcode = - WTERMSIG(wstat);
}

{
struct timespec _et;
if (clock_gettime(CLOCK_REALTIME, &_et) == 0) {
handle->end_time = (double)_et.tv_sec + (double)_et.tv_nsec * 1e-9;
} else {
handle->end_time = NA_REAL;
}
}

handle->collected = 1;

/* Now that the child has exited, release our hold on the PTY child fd.
Expand Down
1 change: 1 addition & 0 deletions src/win/processx-win.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ typedef struct processx_handle_s {
processx_connection_t *pipes[3];
int cleanup;
double create_time;
double end_time; /* 0.0 until the process exits */
void *ptycon; /* ConPTY handle (HPCON), NULL if not using PTY */
} processx_handle_t;

Expand Down
12 changes: 12 additions & 0 deletions src/win/processx.c
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,18 @@ void processx__collect_exit_status(SEXP status, DWORD exitcode) {
processx_handle_t *handle = R_ExternalPtrAddr(status);
handle->exitcode = exitcode;
handle->collected = 1;
{
FILETIME ftCreate, ftExit, ftKernel, ftUser;
if (GetProcessTimes(handle->hProcess,
&ftCreate, &ftExit, &ftKernel, &ftUser)) {
long long ll = ((LONGLONG)ftExit.dwHighDateTime) << 32;
ll += ftExit.dwLowDateTime - 116444736000000000LL;
handle->end_time = (double)(ll / 10000000) +
(double)(ll % 10000000) / 10000000.0;
} else {
handle->end_time = 0.0;
}
}
/* Do NOT call ClosePseudoConsole() here. ConPTY processes the child's
writes asynchronously: by the time GetExitCodeProcess() reports that
the child has exited, conhost may not have finished writing the child's
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/_snaps/Darwin/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Error:
! ! Native call to `processx_exec` failed
Caused by error in `chain_call(c_processx_exec, command, c(command, args), pty, pty_options, ...` at initialize.R:<line>:<col>:
! cannot start processx process '<tempdir>/<tempfile>' (system error 2, No such file or directory) @unix/processx.c:628 (processx_exec)
! cannot start processx process '<tempdir>/<tempfile>' (system error 2, No such file or directory) @unix/processx.c:629 (processx_exec)

# working directory does not exist

Expand All @@ -16,5 +16,5 @@
Error:
! ! Native call to `processx_exec` failed
Caused by error in `chain_call(c_processx_exec, command, c(command, args), pty, pty_options, ...` at initialize.R:<line>:<col>:
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:628 (processx_exec)
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:629 (processx_exec)

2 changes: 1 addition & 1 deletion tests/testthat/_snaps/Darwin/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
Error:
! ! Native call to `processx_exec` failed
Caused by error in `chain_call(c_processx_exec, command, c(command, args), pty, pty_options, ...` at initialize.R:<line>:<col>:
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:628 (processx_exec)
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:629 (processx_exec)

4 changes: 2 additions & 2 deletions tests/testthat/_snaps/Linux/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Error:
! ! Native call to `processx_exec` failed
Caused by error in `chain_call(c_processx_exec, command, c(command, args), pty, pty_options, ...` at initialize.R:<line>:<col>:
! cannot start processx process '<tempdir>/<tempfile>' (system error 2, No such file or directory) @unix/processx.c:628 (processx_exec)
! cannot start processx process '<tempdir>/<tempfile>' (system error 2, No such file or directory) @unix/processx.c:629 (processx_exec)

# working directory does not exist

Expand All @@ -16,5 +16,5 @@
Error:
! ! Native call to `processx_exec` failed
Caused by error in `chain_call(c_processx_exec, command, c(command, args), pty, pty_options, ...` at initialize.R:<line>:<col>:
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:628 (processx_exec)
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:629 (processx_exec)

2 changes: 1 addition & 1 deletion tests/testthat/_snaps/Linux/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
Error:
! ! Native call to `processx_exec` failed
Caused by error in `chain_call(c_processx_exec, command, c(command, args), pty, pty_options, ...` at initialize.R:<line>:<col>:
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:628 (processx_exec)
! cannot start processx process '<path>/px' (system error 2, No such file or directory) @unix/processx.c:629 (processx_exec)

21 changes: 21 additions & 0 deletions tests/testthat/test-process.R
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,24 @@ test_that("R process is installed with a SIGTERM cleanup handler", {
# Was not cleaned up
expect_true(dir.exists(p_temp_dir))
})

test_that("get_end_time", {
px <- get_tool("px")

p <- process$new(px, c("sleep", "1"))
on.exit(p$kill(), add = TRUE)

before <- Sys.time()
expect_null(p$get_end_time())

p$wait()
after <- Sys.time()

et <- p$get_end_time()
expect_s3_class(et, "POSIXct")
expect_gte(as.double(et), as.double(before))
expect_gte(as.double(et), as.double(p$get_start_time()))

# cached: second call returns the same value
expect_equal(p$get_end_time(), et)
})
Loading