Fix #2008: Allow eventReactive and observeEvent eventExprs to be async (#2014)

* Fix #2008: Allow eventReactive and observeEvent eventExprs to be async

This makes it possible to monitor e.g. async reactives.

In the process of fixing this, also discovered that observers don't
filter out shiny.silent.error (i.e. req(FALSE)) when they come back
from async operations. For example, this will kill the current
Shiny session instead of being ignored:

  observe({
    promise_resolve(TRUE) %...>%
      {req(FALSE)}
  })

This issue is also fixed in this commit.

* Enable deep stack trace by default, now that it's fast
This commit is contained in:
Joe Cheng
2018-04-16 18:50:28 -07:00
committed by Winston Chang
parent b7b696630f
commit e5d1fa1ea4
2 changed files with 46 additions and 50 deletions

View File

@@ -191,7 +191,7 @@ createStackTracePromiseDomain <- function() {
}
deepStacksEnabled <- function() {
getOption("shiny.deepstacktrace", FALSE)
getOption("shiny.deepstacktrace", TRUE)
}
doCaptureStack <- function(e) {

View File

@@ -781,18 +781,6 @@ Observable <- R6Class(
# If an error occurs, we want to propagate the error, but we also
# want to save a copy of it, so future callers of this reactive will
# get the same error (i.e. the error is cached).
# We stripStackTrace in the next line, just in case someone
# downstream of us (i.e. deeper into the call stack) used
# captureStackTraces; otherwise the entire stack would always be the
# same (i.e. you'd always see the whole stack trace of the *first*
# time the code was run and the condition raised; there'd be no way
# to see the stack trace of the call site that caused the cached
# exception to be re-raised, and you need that information to figure
# out what's triggering the re-raise).
#
# We use try(stop()) as an easy way to generate a try-error object
# out of this condition.
.value <<- cond
.error <<- TRUE
.visible <<- FALSE
@@ -969,19 +957,12 @@ Observer <- R6Class(
if (length(formals(observerFunc)) > 0)
stop("Can't make an observer from a function that takes parameters; ",
"only functions without parameters can be reactive.")
registerDebugHook("observerFunc", environment(), label)
.func <<- function() {
tryCatch(
if (..stacktraceon)
..stacktraceon..(observerFunc())
else
observerFunc(),
# It's OK for shiny.silent.error errors to cause an observer to stop running
shiny.silent.error = function(e) NULL
# validation = function(e) NULL,
# shiny.output.cancel = function(e) NULL
)
if (grepl("\\s", label, perl = TRUE)) {
funcLabel <- "<observer>"
} else {
funcLabel <- paste0("<observer:", label, ">")
}
.func <<- wrapFunctionLabel(observerFunc, funcLabel, ..stacktraceon = ..stacktraceon)
.label <<- label
.domain <<- domain
.priority <<- normalizePriority(priority)
@@ -1046,6 +1027,15 @@ registerDebugHook("observerFunc", environment(), label)
}
},
catch = function(e) {
# It's OK for shiny.silent.error errors to cause an observer to stop running
# shiny.silent.error = function(e) NULL
# validation = function(e) NULL,
# shiny.output.cancel = function(e) NULL
if (inherits(e, "shiny.silent.error")) {
return()
}
printError(e)
if (!is.null(.domain)) {
.domain$unhandledError(e)
@@ -1990,22 +1980,25 @@ observeEvent <- function(eventExpr, handlerExpr,
initialized <- FALSE
o <- observe({
e <- eventFunc()
hybrid_chain(
{eventFunc()},
function(value) {
if (ignoreInit && !initialized) {
initialized <<- TRUE
return()
}
if (ignoreNULL && isNullEvent(value)) {
return()
}
if (ignoreInit && !initialized) {
initialized <<- TRUE
return()
}
if (ignoreNULL && isNullEvent(e)) {
return()
}
if (once) {
on.exit(o$destroy())
}
isolate(handlerFunc())
if (once) {
on.exit(o$destroy())
}
isolate(handlerFunc())
}
)
}, label = label, suspended = suspended, priority = priority, domain = domain,
autoDestroy = TRUE, ..stacktraceon = FALSE)
@@ -2031,16 +2024,19 @@ eventReactive <- function(eventExpr, valueExpr,
initialized <- FALSE
invisible(reactive({
e <- eventFunc()
if (ignoreInit && !initialized) {
initialized <<- TRUE
req(FALSE)
}
req(!ignoreNULL || !isNullEvent(e))
isolate(handlerFunc())
hybrid_chain(
eventFunc(),
function(value) {
if (ignoreInit && !initialized) {
initialized <<- TRUE
req(FALSE)
}
req(!ignoreNULL || !isNullEvent(value))
isolate(handlerFunc())
}
)
}, label = label, domain = domain, ..stacktraceon = FALSE))
}