Repository source: docs/plans/production_instant_busy_page_simple_plan_v1.md
Production Instant Busy Page Simple Plan
Goal
Make users see the existing Meme Oven Is Busy page immediately when production is already rebuilding or otherwise holding the shared execution lock.
Simplest Remedy
Set the production lock wait timeout to zero:
FORUM_EXECUTION_LOCK_TIMEOUT_SECONDS=0
This uses the lock behavior that already exists. When another request or CLI process owns forum-rewrite.lock, a contending PHP request fails the lock acquisition immediately, the front controller catches the lock-timeout exception, and the existing friendly busy page is returned with HTTP 503.
Why This Is The Smallest Change
It requires no PHP code changes.
It keeps the current
Meme Oven Is Busytemplate and status code.It preserves the current single shared lock contract for web writes, request-driven read-model rebuilds, and
scripts/rebuild_read_model.php.It can be rolled back by restoring the previous timeout value or removing the environment variable.
Implementation Steps
Add
FORUM_EXECUTION_LOCK_TIMEOUT_SECONDS=0to the production Apache/PHP environment.Reload Apache or PHP-FPM, depending on the host setup, so the new environment value is visible to PHP requests.
Confirm the runtime value from production with a temporary diagnostic or by checking the web server environment configuration.
Hold the production execution lock from a shell as the web/deploy user.
Request a dynamic PHP route that needs the lock.
Verify the response is immediate, uses HTTP
503, and containsMeme Oven Is Busy.Release the lock and verify normal pages still load.
Validation Command Shape
Use the actual production paths:
flock /srv/forum-rewrite/state/cache/forum-rewrite.lock sleep 30
In another shell or browser session, request a dynamic route. If the route is served through PHP and needs the shared lock, it should return the busy page immediately.
Important Limits
This remedy makes lock contention fail fast. It does not make every rebuild-related stall impossible.
The request that wins the lock can still spend time rebuilding before it returns.
Anonymous queryless routes served directly from
public/*.htmlby Apache do not enter PHP, so PHP cannot render the busy page for those requests.StaticArtifactBuilder::build()currently rebuilds the read model directly; if production static builds bypassscripts/rebuild_read_model.php, they can still create contention outside the app-level busy-page path.Post-render best-effort artifact generation can still extend a PHP response if it reaches work that does not fail fast.
Follow-Up If This Is Not Enough
If production still shows long stalls after setting the timeout to zero, implement the next-smallest code change:
Add a typed fail-fast lock exception to
ExecutionLock.Replace the front-controller string match with that exception.
Make
Application::ensureReadModel()use the fail-fast lock path when the read model is stale or missing.Skip same-request static artifact generation whenever the shared execution lock is already held.
Put full static artifact builds under the same execution lock, or require operators to run the read-model rebuild separately before static artifact generation.
Those follow-ups are intentionally outside the simplest remedy.