Add startApp() for non-blocking app execution#4349
Add startApp() for non-blocking app execution#4349shikokuchuo wants to merge 40 commits intomainfrom
startApp() for non-blocking app execution#4349Conversation
shikokuchuo
left a comment
There was a problem hiding this comment.
From a conversations with @hadley, we could tweak the UI further so that:
- Starting a new app automatically stops the old one
- Have it automatically run non-blocking if used by an agent (via env vars)
|
I like the model of automatically stopping the previously running app as it matches the existing model pretty closely; it just skips the step of having to Ctrl + C to quit the current app. I would hope that eventually nonblocking mode becomes the default, but defaulting to only during LLM usage would be a good place to start. |
|
Thanks @hadley, starting a new app now automatically stops the old one, and non-blocking is the default for LLMs. |
I thought of this as well. It's convenient to be able to modify the behaviour of the implicit Also in this case, people call it primarily for the side effect of running the App. What |
|
@cpsievert just to update that I tested the Rstudio |
|
Let's move this forward on the following basis (as discussed in today's team meeting):
This has the following advantages:
Noting that @schloerke continues to prefer 2 separate functions. @cpsievert for you to approve this PR. Thanks! |
Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
|
Following discussion at Asilomar, non-blocking behaviour is now separated out into a |
|
@shikokuchuo Could you please update the PR description? |
runApp()startApp() for non-blocking app execution
Ah, of course - all done now! |
cpsievert
left a comment
There was a problem hiding this comment.
A few issues spotted during review — details in inline comments.
Aligns `startApp()` with `runApp()` by setting `options(warn, pool.scheduler)` before `as.shiny.appobj()` and passing `ops` through. Folds the `findVal` precedence block into `.setupShinyApp()`; missingness is checked in the caller's frame via a `caller = parent.frame()` default arg, since `runApp()`/`startApp()` formals carry defaults.
`local_otel_promise_domain()` binds the domain to the caller's frame, which in `startApp()` exits before any request is served. A persistent global install would leak into unrelated user promises between ticks. Wrap the synchronous setup phase and each service iteration in `with_otel_promise_domain()`. Callbacks are wrapped at registration time, so promises created during `onStart`, handlers, and observers stay instrumented when they fire. The domain is dormant between ticks, so it stays out of user promises at the console.
Motivation
Shiny, being an httpuv server, inherently does not need to block — it uses the
laterevent loop, which is designed for cooperative concurrency. Adding a non-blocking mode:Changes
Adds
startApp(), a non-blocking counterpart torunApp():Implementation
ShinyAppHandle(R6 class): Returned bystartApp(). Provides methods for lifecycle management (stop(),status(),url()) and accessing the app's return value (result())..setupShinyApp(): Shared initialization extracted fromrunApp(), used by bothrunApp()andstartApp().serviceNonBlocking(): Runs the httpuv event loop vialatercallbacks instead of a blocking while loop..createCleanup(): Consolidated cleanup logic shared between blocking and non-blocking modes.Safeguards