fix: Wait for app port to bind before navigating (#448) - #449
Conversation
Pull request was closed
Shiny prints its "Listening on ..." line before it binds the listening socket, so on a slow or loaded host AppDriver$new() could navigate to a not-yet-bound port, land on the browser's error page, and race the error-page recovery navigation into an opaque "did not become stable" abort. app_start_shiny() now waits for the port to accept a connection (via pingr::is_up(), the same check app_httr2_get() uses) before returning, so initialization navigates against a bound socket. Co-authored-by: Nicolas Bennett <3158446+nbenn@users.noreply.github.com>
The new port-binding tests use httpuv::randomPort()/startServer() to exercise app_wait_for_serving() against a real socket, so declare httpuv in Suggests (fixing the "'::' import not declared from: 'httpuv'" R CMD check warning) and guard the tests with skip_if_not_installed().
CI status — failures are unrelated to this change; merging via adminThe
To keep
|
81c5bbb to
9f7d2a7
Compare
Fixes #448
Summary
An intermittent
AppDriver$new()failure —Shiny app did not become stable— traces back to a startup race: Shiny prints itsListening on http://…line one statement before it binds the listening socket (rstudio/shiny#4400).app_start_shiny()parsed the URL from that stderr line and let initialization navigate the browser immediately, so on a slow or loaded host the browser could reach a not-yet-bound port, land on Chrome's error page, and when the app finally bound, the error-page recovery navigation raced the readiness/idle checks into the opaque abort (which fires well beforeload_timeout).This adds an
app_wait_for_serving()helper that polls the port withpingr::is_up()— the same reachability checkapp_httr2_get()already uses — until it accepts a connection orload_timeoutelapses, then returns. Initialization therefore navigates against a bound socket. If the port never comes up in time, the helper returns quietly and startup proceeds as before (graceful fallback). This mirrors the approach proposed by @nbenn in the issue.Verification
Using the issue's deterministic reproducer (tracing
startServerto sleep 5s before bind): without the fix it aborts in ~6s withdid not become stable … Caused by error in app_wait_for_idle(); with the fix the app becomes stable in ~8s. New unit tests intests/testthat/test-app-start.Rcover both the unbound-port (returnsFALSE, honors timeout) and bound-port (returnsTRUE) cases.