Title: | Utilities for Scheduling Functions to Execute Later with Event Loops |
---|---|
Description: | Executes arbitrary R or C functions some time after the current time, after the R execution stack has emptied. The functions are scheduled in an event loop. |
Authors: | Winston Chang [aut, cre], Joe Cheng [aut], Charlie Gao [aut] , Posit Software, PBC [cph], Marcus Geelnard [ctb, cph] (TinyCThread library, https://tinycthread.github.io/), Evan Nemerson [ctb, cph] (TinyCThread library, https://tinycthread.github.io/) |
Maintainer: | Winston Chang <[email protected]> |
License: | MIT + file LICENSE |
Version: | 1.4.0 |
Built: | 2024-11-26 11:43:41 UTC |
Source: | CRAN |
Normally, later uses a global event loop for scheduling and running functions. However, in some cases, it is useful to create a private event loop to schedule and execute tasks without disturbing the global event loop. For example, you might have asynchronous code that queries a remote data source, but want to wait for a full back-and-forth communication to complete before continuing in your code – from the caller's perspective, it should behave like synchronous code, and not do anything with the global event loop (which could run code unrelated to your operation). To do this, you would run your asynchronous code using a private event loop.
create_loop(parent = current_loop(), autorun = NULL) destroy_loop(loop) exists_loop(loop) current_loop() with_temp_loop(expr) with_loop(loop, expr) global_loop()
create_loop(parent = current_loop(), autorun = NULL) destroy_loop(loop) exists_loop(loop) current_loop() with_temp_loop(expr) with_loop(loop, expr) global_loop()
parent |
The parent event loop for the one being created. Whenever the
parent loop runs, this loop will also automatically run, without having to
manually call |
autorun |
This exists only for backward compatibility. If set to
|
loop |
A handle to an event loop. |
expr |
An expression to evaluate. |
create_loop
creates and returns a handle to a private event loop,
which is useful when for scheduling tasks when you do not want to interfere
with the global event loop.
destroy_loop
destroys a private event loop.
exists_loop
reports whether an event loop exists – that is, that it
has not been destroyed.
current_loop
returns the currently-active event loop. Any calls to
later()
or run_now()
will use the current loop by
default.
with_loop
evaluates an expression with a given event loop as the
currently-active loop.
with_temp_loop
creates an event loop, makes it the current loop, then
evaluates the given expression. Afterwards, the new event loop is destroyed.
global_loop
returns a handle to the global event loop.
Schedule an R function or formula to run after a specified period of time.
Similar to JavaScript's setTimeout
function. Like JavaScript, R is
single-threaded so there's no guarantee that the operation will run exactly
at the requested time, only that at least that much time will elapse.
later(func, delay = 0, loop = current_loop())
later(func, delay = 0, loop = current_loop())
func |
A function or formula (see |
delay |
Number of seconds in the future to delay execution. There is no guarantee that the function will be executed at the desired time, but it should not execute earlier. |
loop |
A handle to an event loop. Defaults to the currently-active loop. |
The mechanism used by this package is inspired by Simon Urbanek's background package and similar code in Rhttpd.
A function, which, if invoked, will cancel the callback. The
function will return TRUE
if the callback was successfully
cancelled and FALSE
if not (this occurs if the callback has
executed or has been cancelled already).
To avoid bugs due to reentrancy, by default, scheduled operations only run
when there is no other R code present on the execution stack; i.e., when R is
sitting at the top-level prompt. You can force past-due operations to run at
a time of your choosing by calling run_now()
.
Error handling is not particularly well-defined and may change in the future.
options(error=browser) should work and errors in func
should generally not
crash the R process, but not much else can be said about it at this point.
If you must have specific behavior occur in the face of errors, put error
handling logic inside of func
.
# Example of formula style later(~cat("Hello from the past\n"), 3) # Example of function style later(function() { print(summary(cars)) }, 2)
# Example of formula style later(~cat("Hello from the past\n"), 3) # Example of function style later(function() { print(summary(cars)) }, 2)
Schedule an R function or formula to run after an indeterminate amount of time when file descriptors are ready for reading or writing, subject to an optional timeout.
later_fd( func, readfds = integer(), writefds = integer(), exceptfds = integer(), timeout = Inf, loop = current_loop() )
later_fd( func, readfds = integer(), writefds = integer(), exceptfds = integer(), timeout = Inf, loop = current_loop() )
func |
A function that takes a single argument, a logical vector that
indicates which file descriptors are ready (a concatenation of |
readfds |
Integer vector of file descriptors, or Windows SOCKETs, to monitor for being ready to read. |
writefds |
Integer vector of file descriptors, or Windows SOCKETs, to monitor being ready to write. |
exceptfds |
Integer vector of file descriptors, or Windows SOCKETs, to monitor for error conditions pending. |
timeout |
Number of seconds to wait before giving up, and calling |
loop |
A handle to an event loop. Defaults to the currently-active loop. |
On the occasion the system-level poll
(on Windows WSAPoll
) returns an
error, the callback will be made on a vector of all NA
s. This is
indistinguishable from a case where the poll
succeeds but there are error
conditions pending against each file descriptor.
If no file descriptors are supplied, the callback is scheduled for immediate
execution and made on the empty logical vector logical(0)
.
A function, which, if invoked, will cancel the callback. The
function will return TRUE
if the callback was successfully
cancelled and FALSE
if not (this occurs if the callback has
executed or has been cancelled already).
To avoid bugs due to reentrancy, by default, scheduled operations only run
when there is no other R code present on the execution stack; i.e., when R is
sitting at the top-level prompt. You can force past-due operations to run at
a time of your choosing by calling run_now()
.
Error handling is not particularly well-defined and may change in the future.
options(error=browser) should work and errors in func
should generally not
crash the R process, but not much else can be said about it at this point.
If you must have specific behavior occur in the face of errors, put error
handling logic inside of func
.
# create nanonext sockets s1 <- nanonext::socket(listen = "inproc://nano") s2 <- nanonext::socket(dial = "inproc://nano") fd1 <- nanonext::opt(s1, "recv-fd") fd2 <- nanonext::opt(s2, "recv-fd") # 1. timeout: prints FALSE, FALSE later_fd(print, c(fd1, fd2), timeout = 0.1) Sys.sleep(0.2) run_now() # 2. fd1 ready: prints TRUE, FALSE later_fd(print, c(fd1, fd2), timeout = 1) res <- nanonext::send(s2, "msg") Sys.sleep(0.1) run_now() # 3. both ready: prints TRUE, TRUE res <- nanonext::send(s1, "msg") later_fd(print, c(fd1, fd2), timeout = 1) Sys.sleep(0.1) run_now() # 4. fd2 ready: prints FALSE, TRUE res <- nanonext::recv(s1) later_fd(print, c(fd1, fd2), timeout = 1) Sys.sleep(0.1) run_now() # 5. fds invalid: prints NA, NA close(s2) close(s1) later_fd(print, c(fd1, fd2), timeout = 0) Sys.sleep(0.1) run_now()
# create nanonext sockets s1 <- nanonext::socket(listen = "inproc://nano") s2 <- nanonext::socket(dial = "inproc://nano") fd1 <- nanonext::opt(s1, "recv-fd") fd2 <- nanonext::opt(s2, "recv-fd") # 1. timeout: prints FALSE, FALSE later_fd(print, c(fd1, fd2), timeout = 0.1) Sys.sleep(0.2) run_now() # 2. fd1 ready: prints TRUE, FALSE later_fd(print, c(fd1, fd2), timeout = 1) res <- nanonext::send(s2, "msg") Sys.sleep(0.1) run_now() # 3. both ready: prints TRUE, TRUE res <- nanonext::send(s1, "msg") later_fd(print, c(fd1, fd2), timeout = 1) Sys.sleep(0.1) run_now() # 4. fd2 ready: prints FALSE, TRUE res <- nanonext::recv(s1) later_fd(print, c(fd1, fd2), timeout = 1) Sys.sleep(0.1) run_now() # 5. fds invalid: prints NA, NA close(s2) close(s1) later_fd(print, c(fd1, fd2), timeout = 0) Sys.sleep(0.1) run_now()
Returns the duration between now and the earliest operation that is currently
scheduled, in seconds. If the operation is in the past, the value will be
negative. If no operation is currently scheduled, the value will be Inf
.
next_op_secs(loop = current_loop())
next_op_secs(loop = current_loop())
loop |
A handle to an event loop. |
Normally, operations scheduled with later()
will not execute unless/until
no other R code is on the stack (i.e. at the top-level). If you need to run
blocking R code for a long time and want to allow scheduled operations to run
at well-defined points of your own operation, you can call run_now()
at
those points and any operations that are due to run will do so.
run_now(timeoutSecs = 0L, all = TRUE, loop = current_loop())
run_now(timeoutSecs = 0L, all = TRUE, loop = current_loop())
timeoutSecs |
Wait (block) for up to this number of seconds waiting for
an operation to be ready to run. If |
all |
If |
loop |
A handle to an event loop. Defaults to the currently-active loop. |
If one of the callbacks throws an error, the error will not be caught, and
subsequent callbacks will not be executed (until run_now()
is called again,
or control returns to the R prompt). You must use your own
tryCatch if you want to handle errors.
A logical indicating whether any callbacks were actually run.