Skip to content

fix(idle): serve an auto-refreshing waking page for suspended host-proxy sites#673

Merged
geodro merged 3 commits into
lerd-env:mainfrom
prabowosd:fix/idle-suspend-hostproxy-waking-page
Jun 29, 2026
Merged

fix(idle): serve an auto-refreshing waking page for suspended host-proxy sites#673
geodro merged 3 commits into
lerd-env:mainfrom
prabowosd:fix/idle-suspend-hostproxy-waking-page

Conversation

@prabowosd

Copy link
Copy Markdown
Member

Fixes #672.

Problem

When idle-suspend sleeps a host-proxy site, the site returns 502 Bad Gateway and does not recover gracefully on the next request.

A PHP site keeps serving while idle-suspended: the engine stops only its background workers (queue, horizon) and FPM stays up. A host-proxy site has no FPM, the dev server (the app worker) is its only request-serving process. Stopping it leaves the proxy vhost pointing at a dead port, so nginx 502s.

"Resume on next request" can't recover it cleanly either: the request 502s at nginx before the dev server (often a slow Vite/Nuxt warmup) is back, and nginx's 502 page carries no auto-refresh, so the reload loop never lands on the live app.

Fix

On suspend of a host-proxy site, swap its proxy vhost to an auto-refreshing "waking up" landing page instead of leaving the dead proxy:

  • SuspendWorkersForIdle: when the suspended set includes the host-proxy app worker, write waking.html and swap to the waking vhost (GenerateWakingVhost) + reload.
  • ResumeWorkersForIdle: after restarting the dev server, wait (bounded, 60s) for its port to accept connections, then restore the proxy vhost via siteops.RegenerateSiteVhost + reload. The wait keeps the auto-refreshing page serving through the dev server's warmup so the reload lands on the live app rather than flashing nginx's refresh-less 502.

The access-log hit that loads the waking page already drives idle-resume (the existing OnActivity path), so no extra wake wiring is needed. PHP sites are unaffected.

The paused and waking vhosts now share landingVhostConf / writeLandingVhost, so GeneratePausedVhost is output-identical to before.

Test

  • internal/nginx: GenerateWakingVhost serves waking.html (not a proxy_pass), and on a secured site removes the separate -ssl.conf; a regression test pins GeneratePausedVhost still serving paused.html after the refactor.
  • internal/cli: the waking page carries a meta http-equiv="refresh" so a resumed site reloads on its own.
  • go build, go vet ./internal/cli ./internal/nginx, and the nginx + cli + watcher suites pass.

Notes

The port-wait is bounded to 60s and skipped when the proxy has no fixed port (worst case a brief 502 flash on warmup). Scope is the parent host-proxy site; worktree host-proxy dev servers are out of scope here.

…oxy sites

Idle-suspend stops a host-proxy site's dev server (its "app" worker), but a
host-proxy site has no FPM behind the proxy: the dev server is its only
request-serving process. With it stopped the proxy vhost points at a dead port,
so every request returns 502 from nginx. The "resume on next request" design
never recovers gracefully either: the request 502s at nginx before the dev
server (often a slow Vite/Nuxt warmup) is back, and nginx's 502 page carries no
refresh, so the reload loop is broken.

On suspend of a host-proxy site, swap the proxy vhost to an auto-refreshing
"waking up" landing page instead of leaving the dead proxy. The access-log hit
that loads it already drives idle-resume; on resume, wait for the dev server to
accept connections again, then restore the proxy vhost so the page's meta
refresh lands on the live app. PHP sites are unaffected (idle-suspend leaves
their FPM serving; only background workers stop).

The paused and waking vhosts now share landingVhostConf/writeLandingVhost, so
GeneratePausedVhost is output-identical to before.
@geodro

geodro commented Jun 29, 2026

Copy link
Copy Markdown
Member

This reads well and it mirrors the approach from the host-proxy stop fix nicely, reusing RegenerateSiteVhost for the restore and folding the paused and waking vhosts into a shared landing renderer is the right call. I traced the resume path and the 60s port wait is fine since resume already runs in a background goroutine behind the inFlight guard, so it never stalls the watcher loop and concurrent access hits coalesce. Build, vet, gofmt and the nginx, cli and watcher suites all pass here.

Two things before this goes in. There's a stray word in the comment inside restoreHostProxyVhostAfterIdle, the line starts with "ponytail: poll the dev-server port", that looks like an artifact and should just be dropped. And the cli side is a bit light on tests compared to the stop fix, the only cli test is a string check that the waking HTML has a meta refresh, so the host-proxy only gating in swapHostProxyVhostToWaking and restoreHostProxyVhostAfterIdle isn't pinned anywhere. Factoring the decision into a small pure helper the way hostProxyAppLifecycleOp was done in the other PR and table testing it would lock that in.

…stray word

Address review: factor the host-proxy-only gate shared by
swapHostProxyVhostToWaking and restoreHostProxyVhostAfterIdle into a pure
hostProxyVhostSwapApplies(isHostProxy, workers) helper and table-test it, so the
"only a host-proxy site whose suspended/resumed set includes the app worker"
decision is pinned. Also drop the stray "ponytail:" prefix from the restore
comment.
@prabowosd

Copy link
Copy Markdown
Member Author

Both addressed in e688cb4:

  • Dropped the stray ponytail: prefix from the comment in restoreHostProxyVhostAfterIdle (it was an editor artifact, the line is now plain prose).
  • Factored the host-proxy-only gate shared by swapHostProxyVhostToWaking and restoreHostProxyVhostAfterIdle into a pure hostProxyVhostSwapApplies(isHostProxy, workers) helper, mirroring hostProxyAppLifecycleOp from the stop fix, and table-tested it in TestHostProxyVhostSwapApplies (host-proxy with app among others, host-proxy without app, empty set, non-host-proxy with app, etc.). Both call sites now route through it.

go build, go vet, and the cli suite pass.

@geodro geodro merged commit 1849b97 into lerd-env:main Jun 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS/Linux: idle-suspend leaves host-proxy sites returning 502 instead of waking on request

2 participants