Open RLesur opened 3 years ago
The reason this happens is because the Chromote object creates its own event loop, which is a "child" of the global event loop.
With the later package, there is a global event loop, and it is possible to create event loops which are children of the global loop. (And similarly, it is possible to create children of children.) When an event loop is run, it also runs its children. However, when a child event loop is run, it does not run the parent. (Also, the global loop runs automatically when the R console is idle.)
The purpose of the child event loops is so that asynchronous code can be run "synchronously", without accidentally affecting other asynchronous code. In Chromote, the synchronize()
function essentially takes a promise, and blocks and runs the event loop until that promise is resolved.
Here's an example to illustrate why child loops are important. Shiny apps work by using the global event loop: somewhere in the code it calls later::run_now()
to run the global loop. When that happens, it (1) handles incoming messages, (2) executes reactives, and (3) sends outgoing messages. Now suppose you have observer which did something like this:
observe({
b <- ChromoteSession$new()
b$Page$navigate(url = input$url)
b$screenshot()
b$close()
})
This observer is triggered by an incoming message that says that input$url
has changed. When it runs, Shiny is in step 2 of the cycle described above. If Chromote used the global event loop, the Chromote commands would cause the global event loop to run -- but this would be re-entrant: it would be calling later::run_now()
in the middle of calling later::run_now()
. This could cause Shiny code to run in an unexpected order.
Chromote avoids this problem by using child event loop. When a child event loop is run, it does not cause the global loop to run, and so the observer above is safe.
The problem you have here is that the httpuv application uses the global loop, but when a Chromote command is issued with wait_=TRUE
, it keeps running the child loop. When the child loop is running, it blocks the global loop from running, and so httpuv never gets a chance to handle incoming messages.
As for working around the problem, I tried starting the httpuv app with the Chromote object's child loop, using the with_loop()
function. I expected this work, but it did not. There is probably some detail that I'm forgetting about how Chromote's synchronize()
function works.
library(chromote)
library(servr)
b <- ChromoteSession$new()
b$view()
# launch a httpuv server
later::with_loop(b$get_child_loop(), {
svr <- httd(file.path(R.home("doc"), "manual"))
})
faq_url <- paste(svr$url, "R-FAQ.html", sep = "/")
browseURL(faq_url)
# Works
b$Page$navigate(url = faq_url, wait_ = FALSE)
# Still hangs with wait_ = TRUE
b$Page$navigate(url = faq_url)
Note that for httpuv's unit tests, we have a similar issue, where use curl to fetch data from the httpuv application. If we used the regular, blocking function curl_fetch_memory()
, then the httpuv app would never get a chance to service the request. Instead, we use curl_fetch_multi()
, which is asynchronous and nonblocking. See:
It is called indirectly using fetch()
in tests like this:
https://github.com/rstudio/httpuv/blob/13465f036761952755a1d81c14e0dae017c1c5c0/tests/testthat/test-http-parse.R#L3-L33
I don't have a good workaround at the moment, but that does not mean that it's impossible to make work.
Thanks for the explanations. I have already noticed that servr only uses the global events loop. Maybe this issue is mostly on the httpuv side. For now, here is my workaround:
library(chromote)
library(servr)
b <- ChromoteSession$new()
b$view()
b$parent$debug_messages(TRUE)
# launch a httpuv server
svr <- httd(file.path(R.home("doc"), "manual"))
faq_url <- paste(svr$url, "R-FAQ.html", sep = "/")
browseURL(faq_url)
{
p <- b$Page$navigate(url = faq_url, wait_ = FALSE)
chromote:::synchronize(p, loop = later::global_loop())
}
Bug has been introduced after the recent update. b <- ChromoteSession$new()
returns this error. I checked on multiple versions of R. Same code works fine with prior version of chromote.
Error in onRejected(reason) : code: -32601 message: 'Inspector.enable' wasn't found
@markwsac Can you file a new issue about the Inspector.enable problem?
Sure, I apologize for spamming here. I thought it's related to the discussion.
My R session hangs when I try to open a local web page served by a httpuv server with
wait_=TRUE
. It seems that the httpuv loop does not run (this may be related to https://github.com/rstudio/httpuv/issues/250, feel free to close this issue).Here is an example: