There appears to be a bug on linux where a process’s timeout is consistently biased: the process is terminated too early by a sub-second offset that remains constant during a single boot but changes after rebooting.
Using this example, I got rough values for the actual time till a timeout from 5 different reboots of my system:
replicate(10, {
start_time <- Sys.time()
processx::run("sleep", "10", timeout = 3, error_on_status = FALSE)
end_time <- Sys.time()
difftime(end_time, start_time, units = "secs")
})
# Boot 1: 2.672711 2.667453 2.670226 2.670044 2.670001 2.672757 2.668645 2.669582 2.669642 2.669321
# Boot 2: 3.002664 2.976912 2.980564 2.979576 2.979316 2.984085 2.977271 2.979840 2.979048 2.981039
# Boot 3: 2.609917 2.586380 2.590400 2.590042 2.590023 2.591098 2.588827 2.589941 2.589849 2.590309
# Boot 4: 2.138885 2.106352 2.108275 2.106977 2.100902 2.111049 2.109790 2.110651 2.108229 2.110446
# Boot 5: 2.629206 2.606612 2.610635 2.609579 2.610023 2.620473 2.609882 2.610334 2.609502 2.610581
This is what @mb706 and I could figure out:
Internally, the timeout is determined by Sys.time() - start_time > timeout in run_manage():
|
if ( |
|
!is.null(timeout) && |
|
is.finite(timeout) && |
|
Sys.time() - start_time > timeout |
|
) { |
with
start_time ultimately getting defined here:
|
ct = processx__create_time_since_boot(pid); |
|
if (ct == 0) return 0.0; |
|
|
|
bt = processx__boot_time(); |
|
if (bt == 0) return 0.0; |
|
|
|
/* Query if not yet queried */ |
|
if (processx__linux_clock_period == 0) { |
|
clock = sysconf(_SC_CLK_TCK); |
|
if (clock == -1) return 0.0; |
|
processx__linux_clock_period = 1.0 / clock; |
|
} |
|
|
|
return bt + ct * processx__linux_clock_period; |
Here, ct gets read from the field starttime in /proc/<pid>/stat and bt seems to get defined through ps::ps_boot_time() in .onLoad(), which reads the btime field in /proc/stat.
However, the boot time in /proc/stat is only accurate in seconds.
If this is rounded down, that would explain why processx would think the process is older than it actually is.
This might also relate to this issue: #394
> sessioninfo::session_info()
─ Session info ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
setting value
version R version 4.5.1 (2025-06-13)
os Linux Mint 22
system x86_64, linux-gnu
ui X11
language en_US:en
collate de_DE.UTF-8
ctype de_DE.UTF-8
tz Europe/Berlin
date 2026-02-01
pandoc 3.1.3 @ /usr/bin/pandoc
quarto 1.7.29 @ /opt/quarto/bin/quarto
─ Packages ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
cli 3.6.5 2025-04-23 [1] CRAN (R 4.5.0)
processx 3.8.6 2025-02-21 [1] CRAN (R 4.5.0)
ps 1.9.1 2025-04-12 [1] CRAN (R 4.5.0)
R6 2.6.1 2025-02-15 [1] CRAN (R 4.5.0)
sessioninfo 1.2.3 2025-02-05 [1] CRAN (R 4.5.0)
[1] /home/keno/R/x86_64-pc-linux-gnu-library/4.5
[2] /usr/local/lib/R/site-library
[3] /usr/lib/R/site-library
[4] /usr/lib/R/library
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
There appears to be a bug on linux where a process’s timeout is consistently biased: the process is terminated too early by a sub-second offset that remains constant during a single boot but changes after rebooting.
Using this example, I got rough values for the actual time till a timeout from 5 different reboots of my system:
This is what @mb706 and I could figure out:
Internally, the timeout is determined by
Sys.time() - start_time > timeoutinrun_manage():processx/R/run.R
Lines 410 to 414 in 6a16b63
with
start_timeultimately getting defined here:processx/src/create-time.c
Lines 183 to 196 in 6a16b63
Here,
ctgets read from the fieldstarttimein/proc/<pid>/statandbtseems to get defined throughps::ps_boot_time()in.onLoad(), which reads thebtimefield in/proc/stat.However, the boot time in
/proc/statis only accurate in seconds.If this is rounded down, that would explain why
processxwould think the process is older than it actually is.This might also relate to this issue: #394