Skip to content

Commit 1f7de58

Browse files
authored
Merge pull request #119 from r-lib/autorun
2 parents 0fb877a + 2add006 commit 1f7de58

21 files changed

+1050
-347
lines changed

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: later
22
Type: Package
33
Title: Utilities for Scheduling Functions to Execute Later with Event Loops
4-
Version: 1.0.0.9002
4+
Version: 1.0.0.9004
55
Authors@R: c(
66
person("Joe", "Cheng", role = c("aut", "cre"), email = "[email protected]"),
77
person(family = "RStudio", role = "cph"),
@@ -20,7 +20,7 @@ Imports:
2020
rlang
2121
LinkingTo: Rcpp, BH
2222
Roxygen: list(markdown = TRUE)
23-
RoxygenNote: 7.0.2
23+
RoxygenNote: 7.1.0
2424
Suggests:
2525
knitr,
2626
rmarkdown,

NEWS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
## later 1.0.0.9002
1+
## later 1.0.0.9004
2+
3+
* Private event loops are now automatically run by their parent. That is, whenever an event loop is run, its children event loops are automatically run. The `create_loop()` function has a new parameter `parent`, which defaults to the current loop. The auto-running behavior can be disabled by using `create_loop(parent=NULL)`. ([#119](https://github.com/r-lib/later/pull/119))
24

35
* Fixed [#73](https://github.com/r-lib/later/issues/73), [#109](https://github.com/r-lib/later/issues/109): Previously, later did not build on some platforms, notably ARM, because the `-latomic` linker was needed on those platforms. A configure script now detects when `-latomic` is needed. ([#114](https://github.com/r-lib/later/pull/114))
46

R/RcppExports.R

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,55 @@ log_level <- function(level) {
99
.Call('_later_log_level', PACKAGE = 'later', level)
1010
}
1111

12-
ensureInitialized <- function() {
13-
invisible(.Call('_later_ensureInitialized', PACKAGE = 'later'))
12+
setCurrentRegistryId <- function(id) {
13+
invisible(.Call('_later_setCurrentRegistryId', PACKAGE = 'later', id))
14+
}
15+
16+
getCurrentRegistryId <- function() {
17+
.Call('_later_getCurrentRegistryId', PACKAGE = 'later')
18+
}
19+
20+
deleteCallbackRegistry <- function(loop_id) {
21+
.Call('_later_deleteCallbackRegistry', PACKAGE = 'later', loop_id)
22+
}
23+
24+
notifyRRefDeleted <- function(loop_id) {
25+
.Call('_later_notifyRRefDeleted', PACKAGE = 'later', loop_id)
1426
}
1527

16-
existsCallbackRegistry <- function(loop) {
17-
.Call('_later_existsCallbackRegistry', PACKAGE = 'later', loop)
28+
createCallbackRegistry <- function(id, parent_id) {
29+
invisible(.Call('_later_createCallbackRegistry', PACKAGE = 'later', id, parent_id))
1830
}
1931

20-
createCallbackRegistry <- function(loop) {
21-
.Call('_later_createCallbackRegistry', PACKAGE = 'later', loop)
32+
existsCallbackRegistry <- function(id) {
33+
.Call('_later_existsCallbackRegistry', PACKAGE = 'later', id)
2234
}
2335

24-
deleteCallbackRegistry <- function(loop) {
25-
.Call('_later_deleteCallbackRegistry', PACKAGE = 'later', loop)
36+
list_queue_ <- function(id) {
37+
.Call('_later_list_queue_', PACKAGE = 'later', id)
2638
}
2739

28-
list_queue_ <- function(loop) {
29-
.Call('_later_list_queue_', PACKAGE = 'later', loop)
40+
execCallbacks <- function(timeoutSecs, runAll, loop_id) {
41+
.Call('_later_execCallbacks', PACKAGE = 'later', timeoutSecs, runAll, loop_id)
3042
}
3143

32-
execCallbacks <- function(timeoutSecs, runAll, loop) {
33-
.Call('_later_execCallbacks', PACKAGE = 'later', timeoutSecs, runAll, loop)
44+
idle <- function(loop_id) {
45+
.Call('_later_idle', PACKAGE = 'later', loop_id)
3446
}
3547

36-
idle <- function(loop) {
37-
.Call('_later_idle', PACKAGE = 'later', loop)
48+
ensureInitialized <- function() {
49+
invisible(.Call('_later_ensureInitialized', PACKAGE = 'later'))
3850
}
3951

40-
execLater <- function(callback, delaySecs, loop) {
41-
.Call('_later_execLater', PACKAGE = 'later', callback, delaySecs, loop)
52+
execLater <- function(callback, delaySecs, loop_id) {
53+
.Call('_later_execLater', PACKAGE = 'later', callback, delaySecs, loop_id)
4254
}
4355

44-
cancel <- function(callback_id_s, loop) {
45-
.Call('_later_cancel', PACKAGE = 'later', callback_id_s, loop)
56+
cancel <- function(callback_id_s, loop_id) {
57+
.Call('_later_cancel', PACKAGE = 'later', callback_id_s, loop_id)
4658
}
4759

48-
nextOpSecs <- function(loop) {
49-
.Call('_later_nextOpSecs', PACKAGE = 'later', loop)
60+
nextOpSecs <- function(loop_id) {
61+
.Call('_later_nextOpSecs', PACKAGE = 'later', loop_id)
5062
}
5163

R/later.R

Lines changed: 95 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
.onLoad <- function(...) {
66
ensureInitialized()
77
.globals$next_id <- 0L
8-
.globals$global_loop <- create_loop(autorun = FALSE)
9-
.globals$current_loop <- .globals$global_loop
8+
# Store a ref to the global loop so it doesn't get GC'd.
9+
.globals$global_loop <- create_loop(parent = NULL)
1010
}
1111

1212
.globals <- new.env(parent = emptyenv())
13+
# A registry of weak refs to loop handle objects. Given an ID number, we can
14+
# get the corresponding loop handle. We use weak refs because we don't want
15+
# this registry to keep the loop objects alive.
16+
.loops <- new.env(parent = emptyenv())
1317

1418
#' Private event loops
1519
#'
@@ -47,49 +51,88 @@
4751
#'
4852
#' @param loop A handle to an event loop.
4953
#' @param expr An expression to evaluate.
50-
#' @param autorun Should this event loop automatically be run when its parent
51-
#' loop runs? Currently, only FALSE is allowed, but in the future TRUE will
52-
#' be implemented and the default. Because in the future the default will
53-
#' change, for now any code that calls \code{create_loop} must explicitly
54-
#' pass in \code{autorun=FALSE}.
54+
#' @param autorun This exists only for backward compatibility. If set to
55+
#' \code{FALSE}, it is equivalent to using \code{parent=NULL}.
56+
#' @param parent The parent event loop for the one being created. Whenever the
57+
#' parent loop runs, this loop will also automatically run, without having to
58+
#' manually call \code{\link{run_now}()} on this loop. If \code{NULL}, then
59+
#' this loop will not have a parent event loop that automatically runs it; the
60+
#' only way to run this loop will be by calling \code{\link{run_now}()} on this
61+
#' loop.
5562
#' @rdname create_loop
5663
#'
5764
#' @export
58-
create_loop <- function(autorun = NULL) {
59-
if (!identical(autorun, FALSE)) {
60-
stop("autorun must be set to FALSE (until TRUE is implemented).")
61-
}
62-
65+
create_loop <- function(parent = current_loop(), autorun = NULL) {
6366
id <- .globals$next_id
6467
.globals$next_id <- id + 1L
65-
createCallbackRegistry(id)
68+
69+
if (!is.null(autorun)) {
70+
# This is for backward compatibility, if `create_loop(autorun=FALSE)` is called.
71+
parent <- NULL
72+
}
73+
if (identical(parent, FALSE)) {
74+
# This is for backward compatibility, if `create_loop(FALSE)` is called.
75+
# (Previously the first and only parameter was `autorun`.)
76+
parent <- NULL
77+
warning("create_loop(FALSE) is deprecated. Please use create_loop(parent=NULL) from now on.")
78+
}
79+
if (!is.null(parent) && !inherits(parent, "event_loop")) {
80+
stop("`parent` must be NULL or an event_loop object.")
81+
}
82+
83+
if (is.null(parent)) {
84+
parent_id <- -1L
85+
} else {
86+
parent_id <- parent$id
87+
}
88+
createCallbackRegistry(id, parent_id)
6689

6790
# Create the handle for the loop
6891
loop <- new.env(parent = emptyenv())
6992
class(loop) <- "event_loop"
7093
loop$id <- id
7194
lockBinding("id", loop)
95+
96+
# Add a weak reference to the loop object in our registry.
97+
.loops[[sprintf("%d", id)]] <- rlang::new_weakref(loop)
98+
7299
if (id != 0L) {
73-
# Automatically destroy the loop when the handle is GC'd (unless it's the
74-
# global loop.) The global loop handle never gets GC'd under normal
75-
# circumstances because .globals$global_loop refers to it. However, if the
76-
# package is unloaded it can get GC'd, and we don't want the
77-
# destroy_loop() finalizer to give an error message about not being able
78-
# to destroy the global loop.
79-
reg.finalizer(loop, destroy_loop)
100+
# Inform the C++ layer that there are no more R references when the handle
101+
# is GC'd (unless it's the global loop.) The global loop handle never gets
102+
# GC'd under normal circumstances because .globals$global_loop refers to it.
103+
# However, if the package is unloaded it can get GC'd, and we don't want the
104+
# destroy_loop() finalizer to give an error message about not being able to
105+
# destroy the global loop.
106+
reg.finalizer(loop, notify_r_ref_deleted)
80107
}
81108

82109
loop
83110
}
84111

112+
notify_r_ref_deleted <- function(loop) {
113+
if (identical(loop, global_loop())) {
114+
stop("Can't notify that reference to global loop is deleted.")
115+
}
116+
117+
res <- notifyRRefDeleted(loop$id)
118+
if (res) {
119+
rm(list = sprintf("%d", loop$id), envir = .loops)
120+
}
121+
invisible(res)
122+
}
123+
85124
#' @rdname create_loop
86125
#' @export
87126
destroy_loop <- function(loop) {
88127
if (identical(loop, global_loop())) {
89128
stop("Can't destroy global loop.")
90129
}
91130

92-
deleteCallbackRegistry(loop$id)
131+
res <- deleteCallbackRegistry(loop$id)
132+
if (res) {
133+
rm(list = sprintf("%d", loop$id), envir = .loops)
134+
}
135+
invisible(res)
93136
}
94137

95138
#' @rdname create_loop
@@ -101,13 +144,24 @@ exists_loop <- function(loop) {
101144
#' @rdname create_loop
102145
#' @export
103146
current_loop <- function() {
104-
.globals$current_loop
147+
id <- getCurrentRegistryId()
148+
loop_weakref <- .loops[[sprintf("%d", id)]]
149+
if (is.null(loop_weakref)) {
150+
stop("Current loop with id ", id, " not found.")
151+
}
152+
153+
loop <- rlang::wref_key(loop_weakref)
154+
if (is.null(loop)) {
155+
stop("Current loop with id ", id, " not found.")
156+
}
157+
158+
loop
105159
}
106160

107161
#' @rdname create_loop
108162
#' @export
109163
with_temp_loop <- function(expr) {
110-
loop <- create_loop(autorun = FALSE)
164+
loop <- create_loop(parent = NULL)
111165
on.exit(destroy_loop(loop))
112166

113167
with_loop(loop, expr)
@@ -116,10 +170,13 @@ with_temp_loop <- function(expr) {
116170
#' @rdname create_loop
117171
#' @export
118172
with_loop <- function(loop, expr) {
119-
if (!identical(loop, current_loop())) {
120-
old_loop <- .globals$current_loop
121-
on.exit(.globals$current_loop <- old_loop, add = TRUE)
122-
.globals$current_loop <- loop
173+
if (!exists_loop(loop)) {
174+
stop("loop has been destroyed!")
175+
}
176+
old_loop <- current_loop()
177+
if (!identical(loop, old_loop)) {
178+
on.exit(setCurrentRegistryId(old_loop$id), add = TRUE)
179+
setCurrentRegistryId(loop$id)
123180
}
124181

125182
force(expr)
@@ -134,7 +191,11 @@ global_loop <- function() {
134191

135192
#' @export
136193
format.event_loop <- function(x, ...) {
137-
paste0("<event loop>\n id: ", x$id)
194+
str <- paste0("<event loop> ID: ", x$id)
195+
if (!exists_loop(x)) {
196+
str <- paste(str, "(destroyed)")
197+
}
198+
str
138199
}
139200

140201
#' @export
@@ -191,15 +252,17 @@ later <- function(func, delay = 0, loop = current_loop()) {
191252
f <- rlang::as_function(func)
192253
id <- execLater(f, delay, loop$id)
193254

194-
invisible(create_canceller(id, loop))
255+
invisible(create_canceller(id, loop$id))
195256
}
196257

197258
# Returns a function that will cancel a callback with the given ID. If the
198259
# callback has already been executed or canceled, then the function has no
199260
# effect.
200-
create_canceller <- function(id, loop) {
261+
create_canceller <- function(id, loop_id) {
262+
force(id)
263+
force(loop_id)
201264
function() {
202-
invisible(cancel(id, loop$id))
265+
invisible(cancel(id, loop_id))
203266
}
204267
}
205268

@@ -236,9 +299,7 @@ run_now <- function(timeoutSecs = 0L, all = TRUE, loop = current_loop()) {
236299
if (!is.numeric(timeoutSecs))
237300
stop("timeoutSecs must be numeric")
238301

239-
with_loop(loop,
240-
invisible(execCallbacks(timeoutSecs, all, loop$id))
241-
)
302+
invisible(execCallbacks(timeoutSecs, all, loop$id))
242303
}
243304

244305
#' Check if later loop is empty

inst/include/later.h

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ namespace later {
2929
// int (*dll_api_version)() = (int (*)()) R_GetCCallable("later", "apiVersion");
3030
// if (LATER_H_API_VERSION != (*dll_api_version)()) { ... }
3131
#define LATER_H_API_VERSION 2
32-
3332
#define GLOBAL_LOOP 0
3433

35-
inline void later(void (*func)(void*), void* data, double secs, int loop) {
34+
35+
inline void later(void (*func)(void*), void* data, double secs, int loop_id) {
3636
// This function works by retrieving the later::execLaterNative2 function
3737
// pointer using R_GetCCallable the first time it's called (per compilation
3838
// unit, since it's inline). execLaterNative2 is designed to be safe to call
@@ -69,41 +69,11 @@ inline void later(void (*func)(void*), void* data, double secs, int loop) {
6969
return;
7070
}
7171

72-
eln(func, data, secs, loop);
72+
eln(func, data, secs, loop_id);
7373
}
7474

7575
inline void later(void (*func)(void*), void* data, double secs) {
76-
typedef void (*elnfun)(void (*func)(void*), void*, double);
77-
static elnfun eln = NULL;
78-
if (!eln) {
79-
// Initialize if necessary
80-
if (func) {
81-
// We're not initialized but someone's trying to actually schedule
82-
// some code to be executed!
83-
REprintf(
84-
"Warning: later::execLaterNative called in uninitialized state. "
85-
"If you're using <later.h>, please switch to <later_api.h>.\n"
86-
);
87-
}
88-
eln = (elnfun)R_GetCCallable("later", "execLaterNative");
89-
}
90-
91-
// We didn't want to execute anything, just initialize
92-
if (!func) {
93-
return;
94-
}
95-
96-
eln(func, data, secs);
97-
98-
99-
// Note 2019-09-11: The above code in this function is here just in case a
100-
// package built with this version of later.h is run with an older version
101-
// of the later DLL which does not have the execLaterNative2 function. In
102-
// the next release of later, after we are confident that users have
103-
// installed the newer later DLL which has execLaterNative2, it should be
104-
// safe to replace the code in this function with just this:
105-
//
106-
// later(func, data, secs, GLOBAL_LOOP);
76+
later(func, data, secs, GLOBAL_LOOP);
10777
}
10878

10979

inst/include/later_api.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ namespace {
88
class LaterInitializer {
99
public:
1010
LaterInitializer() {
11-
// See comment in execLaterNative2 to learn why we need to do this in a
12-
// statically initialized object.
13-
later::later(NULL, NULL, 0, GLOBAL_LOOP);
14-
// For execLaterNative
11+
// See comment in execLaterNative to learn why we need to do this
12+
// in a statically initialized object
1513
later::later(NULL, NULL, 0);
1614
}
1715
};
18-
16+
1917
static LaterInitializer init;
2018

2119
} // namespace

0 commit comments

Comments
 (0)