PluginProbe
404 Solution / 4.1.19
404 Solution v4.1.19
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / DataAccessTrait_ViewBuildForceRestart.php

DataAccessTrait_ViewBuildForceRestart.php in 404 Solution 4.1.19, at includes/DataAccessTrait_ViewBuildForceRestart.php

202 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Runner-owned `forceRestartViewBuild()` primitive (Phase 3a step 2 of the
9 * staged view-build watermark refactor; see
10 * docs/refactor-staged-view-build-watermark.md and queue task c554).
11 *
12 * Purpose. Replaces every external "discard whatever the runner has on disk
13 * and restart the build from scratch" caller (the diagnostic AJAX
14 * `?abj404_force_view_rebuild=1` path, the admin "rebuild now" button if
15 * any, the WP-CLI rebuild commands -- migrated by Phase 3a step 4 and the
16 * Cluster A-D tasks). Before Phase 4, those callers reached
17 * `invalidateViewDone()` directly, which violated the runner-ownership
18 * invariant the refactor exists to restore; Phase 4 (commit 2994e21c)
19 * deleted that symbol and routed every external caller through either
20 * `bumpMutationWatermark()` (source-mutation signal) or this primitive
21 * (explicit restart-from-scratch).
22 *
23 * The contract is exactly seven bullets (and the absence of an eighth):
24 *
25 * 1. Acquire the runner lock.
26 * 2. Drop the runner-owned buffer table (`view_build`).
27 * 3. Clear runner progress options (registry + prefix-at-S1 capture +
28 * probe caches -- the same set `clearAllProgressOptions()` owns).
29 * 4. Clear `active_build_started_watermark` (the in-flight build's
30 * S1-entry stamp). The sibling `last_build_started_watermark`
31 * stays put -- it is diagnostic-only and survives both abort and
32 * force-rebuild so an operator can see the most recent stamp.
33 * 5. PRESERVE `built_watermark` (the prior successful build's published
34 * coverage). The rebuild is in flight, the old view_done snapshot is
35 * still serveable until the new S11 RENAME completes; deleting
36 * built_watermark would orphan the freshness signal until then.
37 * 6. Do NOT bump the mutation watermark (force-rebuild is a runner
38 * command, not a data-change signal). Bumping would propagate as a
39 * phantom mutation to every concurrent reader that bracketed this
40 * moment -- their stage-boundary checks would abort their own builds.
41 * 7. Schedule S0/S1 immediately (via the existing cron primitive
42 * `scheduleViewDoneRebuild()`). The lock is released before the
43 * schedule call so the cron tick can acquire cleanly.
44 *
45 * The primitive does NOT:
46 *
47 * - clear `clearStagedBuildDegradedState()`. A host-failed-degraded
48 * site stays degraded across the force-restart unless the caller
49 * explicitly clears the gate; that decision is a caller policy, not
50 * a runner primitive.
51 * - run S0/S1 inline. Callers in request contexts that want immediate
52 * progress (the AJAX force-rebuild path) call `advanceViewBuildOnce()`
53 * after this primitive returns; callers without a request context
54 * (CLI, admin "rebuild now") rely on the scheduled cron tick.
55 * - write to `built_watermark` for any reason.
56 *
57 * Failure modes.
58 *
59 * - Lock contention. If `acquireViewBuildLock(N)` returns false (a
60 * sibling cron tick or admin worker is mid-stage), the primitive
61 * returns false without touching any runner state. Callers retry
62 * on the next request; the in-flight build either completes
63 * normally (best case) or aborts at its next stage boundary and
64 * the next force-restart attempt will find the lock free.
65 *
66 * - No `scheduleViewDoneRebuild()` on lock contention. Without the
67 * cleanup, scheduling a tick would just race the already-running
68 * tick. The existing holder is already advancing the build.
69 *
70 * Allowlist note. The host file
71 * (`includes/DataAccessTrait_ViewBuildForceRestart.php`) matches the
72 * runner-owned `DataAccessTrait_ViewBuild*` glob convention that the
73 * Phase 4 semantic forbidden-operation lint (Codex #6 resolution; see
74 * StagedBuildOwnershipLintTest) will use to define its allowlist of
75 * runner-owned files. The primitive itself issues no DROP TABLE
76 * (delegated to `dropTransientBuffersIfPresent()` in the
77 * StageCallbacks trait, which IS the allowed owner today) and no direct
78 * progress-option writes (delegated to `clearAllProgressOptions()` in
79 * the Helpers trait, which IS the allowed owner today), so no allowlist
80 * updates are required for the lints that are active on current HEAD.
81 *
82 * Sibling traits. `ABJ_404_Solution_DataAccess_ViewBuildHelpersTrait`,
83 * `ABJ_404_Solution_DataAccess_ViewBuildLockAndCronTrait`,
84 * `ABJ_404_Solution_DataAccess_ViewBuildStageCallbacksTrait`; all three
85 * provide the helpers this primitive composes (lock acquire/release,
86 * buffer drop, progress clear, watermark stamp clear, rebuild
87 * scheduling). All four traits are mixed into
88 * `ABJ_404_Solution_DataAccess`.
89 */
90 trait ABJ_404_Solution_DataAccess_ViewBuildForceRestartTrait {
91
92 /**
93 * Runner-owned force-restart primitive. Per Phase 3a step 2 (c554).
94 *
95 * Returns true when the restart completed cleanly: lock acquired,
96 * buffer dropped, progress cleared, active_build_started_watermark
97 * cleared, last_build_started_watermark preserved (diagnostic),
98 * built_watermark preserved (cross-build pre-image), watermark
99 * counter unchanged, rebuild scheduled. Returns false when the lock
100 * could not be acquired within `$lockTimeoutSeconds`; the caller
101 * may retry on the next request.
102 *
103 * Default lock wait of 10s matches the existing
104 * `?abj404_force_view_rebuild=1` AJAX handler in
105 * `advanceViewBuildOnce()` -- comfortable headroom inside a 30s PHP
106 * request budget so a typical cron build mid-flight has time to
107 * release before we return false to the caller. Inlined as the
108 * parameter default rather than a trait const because const-in-trait
109 * is PHP 8.2 and the plugin targets PHP 7.4.
110 *
111 * @param int $lockTimeoutSeconds GET_LOCK wait-time, default 10s.
112 * Pass 0 for a non-blocking acquire (callers that prefer to
113 * retry than to wait).
114 * @return bool true on success, false if lock contended.
115 */
116 public function forceRestartViewBuild(int $lockTimeoutSeconds = 10): bool {
117 // (1) Acquire runner lock. Returns false if a sibling worker (cron
118 // tick, admin form save, REST PUT) holds it -- caller retries.
119 if (!$this->acquireViewBuildLock(max(0, $lockTimeoutSeconds))) {
120 return false;
121 }
122
123 try {
124 $this->runForceRestartCleanupInsideLock();
125 } finally {
126 // Release the lock BEFORE scheduling the next tick so the
127 // cron callback can acquire cleanly. A leaked lock would
128 // stall every subsequent build attempt until the
129 // session-scoped GET_LOCK times out.
130 $this->releaseViewBuildLock();
131 }
132
133 // (7) Schedule S0/S1 immediately. scheduleViewDoneRebuild() is
134 // idempotent (wp_next_scheduled short-circuit) so callers
135 // can chain or replay safely. Cron tick will drive S0 fresh
136 // cleanup -> S1 prefix capture + started-watermark re-stamp
137 // -> S2..S11.
138 $this->scheduleViewDoneRebuild();
139
140 return true;
141 }
142
143 /**
144 * Inside-lock cleanup phase of force-restart, callable by code paths
145 * that already hold the view-build lock and intend to drive the
146 * subsequent S0/S1 run inline (e.g. advanceViewBuildOnce() with
147 * forceRebuild=true). Public callers should prefer
148 * {@see forceRestartViewBuild()} -- this helper does NOT acquire the
149 * lock and does NOT schedule the next cron tick.
150 *
151 * Performs steps 2-6 of the seven-bullet force-restart contract
152 * documented on the trait docblock above:
153 *
154 * - drop the runner-owned buffer table (and the deleteme leftover)
155 * - clear runner progress options + prefix-at-S1 capture
156 * - clear active_build_started_watermark
157 * - preserve built_watermark (no write, no delete)
158 * - DO NOT bump the mutation watermark
159 *
160 * Also resets the per-request serveability cache so a subsequent
161 * viewDoneIsServeable() inside the same request observes the new
162 * state, not the cached pre-cleanup value.
163 */
164 private function runForceRestartCleanupInsideLock(): void {
165 // (2) Drop the runner-owned buffer table (and the deleteme
166 // leftover from any prior crashed S11 RENAME swap).
167 // Gated by SHOW TABLES so a steady-state force-rebuild
168 // after a clean S11 (no buffer present) does not pile
169 // unconditional DDL on the hot path.
170 $this->dropTransientBuffersIfPresent();
171
172 // (3) Clear runner progress options. The helper owns the
173 // registry + prefix-at-S1 capture + sql_mode + php-env
174 // probe-cache clears as one atomic fresh-start step.
175 $this->clearAllProgressOptions();
176
177 // (4) Clear active_build_started_watermark. Lives outside
178 // the progress registry (so the S11 happy-path
179 // observability contract holds across the boundary), so
180 // it needs its own delete call. The sibling
181 // last_build_started_watermark is left alone --
182 // diagnostic-only, survives abort and force-restart.
183 $this->clearActiveBuildStartedWatermark();
184
185 // (5) PRESERVE built_watermark. No write, no delete. The
186 // prior successful build's published coverage stays as
187 // the cross-build pre-image for freshness checks until
188 // the new build's S11 swap publishes a fresh value.
189
190 // (6) DO NOT bump the mutation watermark. No call to
191 // ABJ_404_Solution_MutationWatermark::bump() exists in
192 // this method. Force-rebuild is a runner command, not a
193 // data-change signal; a bump would propagate to every
194 // concurrent reader as a phantom mutation.
195
196 // Reset per-request serveability cache so a subsequent
197 // viewDoneIsServeable() inside this request reflects the
198 // post-cleanup state rather than a stale-cached value.
199 $this->invalidateViewDoneServeableCache();
200 }
201 }
202