Skip to content

Commit e909a01

Browse files
committed
Bisect canonical wp-loaded callbacks
1 parent ba72370 commit e909a01

3 files changed

Lines changed: 97 additions & 5 deletions

File tree

packages/runtime-cloudflare/src/worker.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ async function runBootProbe(phase: string, bucket: R2Bucket): Promise<Response>
566566
}
567567
}
568568

569-
if (["canonical-current-user", "canonical-init", "canonical-site-status", "canonical-wp-loaded"].includes(phase)) {
569+
if (["canonical-current-user", "canonical-init", "canonical-site-status", "canonical-wp-loaded", "canonical-wp-loaded-callbacks", "canonical-wp-loaded-exclude-rewrite-flush", "canonical-wp-loaded-exclude-core-template-header", "canonical-wp-loaded-exclude-playground", "canonical-wp-loaded-exclude-wp-cron", "canonical-wp-loaded-exclude-all"].includes(phase)) {
570570
const runtime = await bootWordPressRuntime("do-not-attempt-installing", true, true, undefined, await packagedCanonicalMarkdownSeed(), new Uint8Array(markdownPrimaryBootstrapIndex), "https://canonical-probe.invalid", {}, bucket, true)
571571
try {
572572
const evidence = (await runtime.php.run({ code: canonicalLifecycleProbeCode(phase) })).text.trim()
@@ -1004,15 +1004,81 @@ require '/wordpress/wp-load.php';`
10041004
}
10051005

10061006
function canonicalLifecycleProbeCode(phase: string): string {
1007+
const wpLoadedExclusions: Record<string, { identifiers: string[]; retain: boolean }> = {
1008+
"canonical-wp-loaded-exclude-rewrite-flush": { identifiers: ["WP_Rewrite::flush_rules", "playground_maybe_flush_rewrite_rules"], retain: false },
1009+
"canonical-wp-loaded-exclude-core-template-header": { identifiers: ["_add_template_loader_filters", "_custom_header_background_just_in_time", "_custom_logo_header_styles"], retain: false },
1010+
"canonical-wp-loaded-exclude-playground": { identifiers: ["playground_maybe_flush_rewrite_rules", "playground_save_wp_env_info"], retain: false },
1011+
"canonical-wp-loaded-exclude-wp-cron": { identifiers: ["_wp_cron"], retain: false },
1012+
"canonical-wp-loaded-exclude-all": { identifiers: [], retain: true },
1013+
}
1014+
const wpLoadedNeedle = "do_action( 'wp_loaded' );"
10071015
const stops: Record<string, { needle: string; after?: boolean }> = {
10081016
"canonical-current-user": { needle: "// Set up current user." },
10091017
"canonical-init": { needle: "do_action( 'init' );", after: true },
10101018
"canonical-site-status": { needle: "// Check site status." },
1011-
"canonical-wp-loaded": { needle: "do_action( 'wp_loaded' );", after: true },
1019+
"canonical-wp-loaded": { needle: wpLoadedNeedle, after: true },
1020+
"canonical-wp-loaded-callbacks": { needle: wpLoadedNeedle },
1021+
...Object.fromEntries(Object.keys(wpLoadedExclusions).map((name) => [name, { needle: wpLoadedNeedle }])),
10121022
}
10131023
const stop = stops[phase]
10141024
if (!stop) throw new Error(`Unknown canonical lifecycle probe phase: ${phase}`)
10151025

1026+
const wpLoadedProbe = phase === "canonical-wp-loaded-callbacks" || wpLoadedExclusions[phase]
1027+
const exclusion = wpLoadedExclusions[phase]
1028+
const wpLoadedStop = !wpLoadedProbe ? "" : `function wp_codebox_canonical_wp_loaded_callback_identifier($callback) {
1029+
if (is_string($callback)) return $callback;
1030+
if ($callback instanceof Closure) return 'Closure';
1031+
if (is_array($callback) && count($callback) === 2 && is_string($callback[1])) {
1032+
$class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
1033+
return is_string($class) ? $class . '::' . $callback[1] : 'Closure';
1034+
}
1035+
if (is_object($callback) && method_exists($callback, '__invoke')) return get_class($callback) . '::__invoke';
1036+
return 'Closure';
1037+
}
1038+
function wp_codebox_canonical_wp_loaded_inventory() {
1039+
$inventory = array();
1040+
$hook = isset($GLOBALS['wp_filter']['wp_loaded']) ? $GLOBALS['wp_filter']['wp_loaded'] : null;
1041+
if (!$hook || !isset($hook->callbacks) || !is_array($hook->callbacks)) return $inventory;
1042+
$remaining = 100;
1043+
foreach ($hook->callbacks as $priority => $callbacks) {
1044+
if (0 === $remaining) break;
1045+
$identifiers = array();
1046+
foreach ($callbacks as $registered) if (isset($registered['function'])) $identifiers[] = wp_codebox_canonical_wp_loaded_callback_identifier($registered['function']);
1047+
sort($identifiers, SORT_STRING);
1048+
$identifiers = array_slice($identifiers, 0, $remaining);
1049+
if ($identifiers) {
1050+
$inventory[(string) $priority] = $identifiers;
1051+
$remaining -= count($identifiers);
1052+
}
1053+
}
1054+
ksort($inventory, SORT_NUMERIC);
1055+
return $inventory;
1056+
}
1057+
function wp_codebox_canonical_wp_loaded_remove_snapshot($identifiers, $retain) {
1058+
$removed = array();
1059+
$hook = isset($GLOBALS['wp_filter']['wp_loaded']) ? $GLOBALS['wp_filter']['wp_loaded'] : null;
1060+
if (!$hook || !isset($hook->callbacks) || !is_array($hook->callbacks)) return $removed;
1061+
$callbacks_by_priority = $hook->callbacks;
1062+
foreach ($callbacks_by_priority as $priority => $callbacks) {
1063+
foreach ($callbacks as $registered) {
1064+
if (!isset($registered['function'])) continue;
1065+
$identifier = wp_codebox_canonical_wp_loaded_callback_identifier($registered['function']);
1066+
$matches = in_array($identifier, $identifiers, true);
1067+
if (($retain && !$matches) || (!$retain && $matches)) {
1068+
if (remove_action('wp_loaded', $registered['function'], (int) $priority)) $removed[] = $identifier;
1069+
}
1070+
}
1071+
}
1072+
sort($removed, SORT_STRING);
1073+
return $removed;
1074+
}
1075+
$inventory = wp_codebox_canonical_wp_loaded_inventory();
1076+
${phase === "canonical-wp-loaded-callbacks" ? `echo json_encode(array('wordpressVersion' => $wp_version, 'bootstrapPhase' => '${phase}', 'callbacks' => $inventory, 'memoryBytes' => memory_get_usage(true), 'peakMemoryBytes' => memory_get_peak_usage(true)));
1077+
return;` : `$removed = wp_codebox_canonical_wp_loaded_remove_snapshot(${JSON.stringify(exclusion?.identifiers ?? [])}, ${exclusion?.retain ? "true" : "false"});
1078+
$memory_before = memory_get_usage(true);
1079+
do_action('wp_loaded');
1080+
echo json_encode(array('wordpressVersion' => $wp_version, 'bootstrapPhase' => '${phase}', 'completed' => true, 'callbacks' => $inventory, 'removedCallbacks' => $removed, 'memoryBeforeBytes' => $memory_before, 'memoryBytes' => memory_get_usage(true), 'peakMemoryBytes' => memory_get_peak_usage(true)));
1081+
return;`}`
10161082
return `<?php
10171083
$settings_path = '/wordpress/wp-settings.php';
10181084
$settings = file_get_contents($settings_path);
@@ -1021,6 +1087,7 @@ if (substr_count($settings, $needle) !== 1) {
10211087
throw new Exception('WordPress canonical lifecycle probe needle was not uniquely found.');
10221088
}
10231089
$stop = <<<'PHP'
1090+
${wpLoadedStop}
10241091
echo json_encode(array(
10251092
'wordpressVersion' => $wp_version,
10261093
'bootstrapPhase' => '${phase}',

scripts/cloudflare-local-gate.mjs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,14 @@ async function assertHealthResponse() {
171171
async function assertCanonicalProbes() {
172172
const wordpress = await (await request(`${origin}/?phase=canonical-wordpress`)).json()
173173
const setup = await (await request(`${origin}/?phase=canonical-bootstrap-setup`)).json()
174-
const lifecyclePhases = ["canonical-current-user", "canonical-init", "canonical-site-status", "canonical-wp-loaded"]
174+
const lifecyclePhases = ["canonical-current-user", "canonical-init", "canonical-site-status", "canonical-wp-loaded-callbacks", "canonical-wp-loaded-exclude-rewrite-flush", "canonical-wp-loaded-exclude-core-template-header", "canonical-wp-loaded-exclude-playground", "canonical-wp-loaded-exclude-wp-cron", "canonical-wp-loaded-exclude-all"]
175+
const expectedRemovedCallbacks = {
176+
"canonical-wp-loaded-exclude-rewrite-flush": ["WP_Rewrite::flush_rules"],
177+
"canonical-wp-loaded-exclude-core-template-header": ["_add_template_loader_filters", "_custom_header_background_just_in_time"],
178+
"canonical-wp-loaded-exclude-playground": ["playground_save_wp_env_info"],
179+
"canonical-wp-loaded-exclude-wp-cron": [],
180+
"canonical-wp-loaded-exclude-all": ["WP_Rewrite::flush_rules", "_add_template_loader_filters", "_custom_header_background_just_in_time", "playground_save_wp_env_info"],
181+
}
175182
const lifecycle = await Promise.all(lifecyclePhases.map(async (phase) => {
176183
const response = await request(`${origin}/?phase=${phase}`)
177184
const body = await response.text()
@@ -190,9 +197,15 @@ async function assertCanonicalProbes() {
190197
for (const probe of lifecycle) {
191198
const evidence = probe.evidence
192199
const schedulingAttached = probe.phase === "canonical-current-user"
193-
if (probe.completed !== true || !lifecyclePhases.includes(probe.phase) || evidence?.bootstrapPhase !== probe.phase || typeof evidence?.wordpressVersion !== "string" || !Number.isInteger(evidence?.memoryBytes) || !Number.isInteger(evidence?.peakMemoryBytes) || evidence?.wpCronInitAttached !== schedulingAttached || evidence?.updateScheduleInitAttached !== schedulingAttached || evidence?.privacyScheduleInitAttached !== schedulingAttached) {
200+
const isWpLoadedProbe = probe.phase.startsWith("canonical-wp-loaded-")
201+
const lifecycleEvidenceIsValid = !isWpLoadedProbe && evidence?.wpCronInitAttached === schedulingAttached && evidence?.updateScheduleInitAttached === schedulingAttached && evidence?.privacyScheduleInitAttached === schedulingAttached
202+
const wpLoadedEvidenceIsValid = isWpLoadedProbe && typeof evidence?.callbacks === "object" && evidence.callbacks !== null && !Array.isArray(evidence.callbacks) && (probe.phase === "canonical-wp-loaded-callbacks" || (evidence?.completed === true && Array.isArray(evidence?.removedCallbacks) && Number.isInteger(evidence?.memoryBeforeBytes)))
203+
if (probe.completed !== true || !lifecyclePhases.includes(probe.phase) || evidence?.bootstrapPhase !== probe.phase || typeof evidence?.wordpressVersion !== "string" || !Number.isInteger(evidence?.memoryBytes) || !Number.isInteger(evidence?.peakMemoryBytes) || (!lifecycleEvidenceIsValid && !wpLoadedEvidenceIsValid)) {
194204
throw new Error(`Canonical lifecycle probe did not stop at its bounded phase: ${JSON.stringify(probe)}`)
195205
}
206+
if (Object.hasOwn(expectedRemovedCallbacks, probe.phase) && JSON.stringify(evidence.removedCallbacks) !== JSON.stringify(expectedRemovedCallbacks[probe.phase])) {
207+
throw new Error(`Canonical wp_loaded exclusion removed an unexpected callback set: ${JSON.stringify(probe)}`)
208+
}
196209
}
197210
console.log(`Canonical probes: WordPress ${wordpress.evidence.wordpressVersion}, disabled-cron scheduling callbacks absent at init, ${lifecyclePhases.join(", ")} completed, ${wordpress.evidence.widgetOptionCount} widget_* options plus sidebars_widgets, ${setup.evidence.changedPathCount} changed ephemeral paths.`)
198211
}

tests/cloudflare-runtime.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,10 @@ test("Cloudflare canonical lifecycle diagnostics compose the disabled-cron patch
318318
const probes = worker.slice(worker.indexOf("async function runBootProbe"), worker.indexOf("if (phase?.startsWith(\"seeded-\"))"))
319319
const lifecycle = worker.slice(worker.indexOf("function canonicalLifecycleProbeCode"), worker.indexOf("\nasync function bootWordPressRuntime"))
320320

321-
for (const phase of ["canonical-current-user", "canonical-init", "canonical-site-status", "canonical-wp-loaded"]) {
321+
for (const phase of ["canonical-current-user", "canonical-init", "canonical-site-status", "canonical-wp-loaded", "canonical-wp-loaded-callbacks", "canonical-wp-loaded-exclude-rewrite-flush", "canonical-wp-loaded-exclude-core-template-header", "canonical-wp-loaded-exclude-playground", "canonical-wp-loaded-exclude-wp-cron", "canonical-wp-loaded-exclude-all"]) {
322322
assert.match(probes, new RegExp(`"${phase}"`))
323323
assert.match(lifecycle, new RegExp(`"${phase}"`))
324+
assert.deepEqual(routeWorkerRequest(new Request(`https://worker.example/?phase=${phase}`)), { kind: "probe", phase })
324325
}
325326
assert.match(probes, /packagedCanonicalMarkdownSeed\(\), new Uint8Array\(markdownPrimaryBootstrapIndex\), "https:\/\/canonical-probe\.invalid", \{\}, bucket, true\)/)
326327
assert.match(lifecycle, /substr_count\(\$settings, \$needle\) !== 1/)
@@ -329,6 +330,17 @@ test("Cloudflare canonical lifecycle diagnostics compose the disabled-cron patch
329330
assert.match(lifecycle, /'wpCronInitAttached' => false !== has_action\('init', 'wp_cron'\)/)
330331
assert.match(lifecycle, /'updateScheduleInitAttached' => false !== has_action\('init', 'wp_schedule_update_checks'\)/)
331332
assert.match(lifecycle, /'privacyScheduleInitAttached' => false !== has_action\('init', 'wp_schedule_delete_old_privacy_export_files'\)/)
333+
assert.match(lifecycle, /function wp_codebox_canonical_wp_loaded_callback_identifier/)
334+
assert.match(lifecycle, /function wp_codebox_canonical_wp_loaded_inventory/)
335+
assert.match(lifecycle, /\$remaining = 100/)
336+
assert.match(lifecycle, /function wp_codebox_canonical_wp_loaded_remove_snapshot/)
337+
assert.match(lifecycle, /remove_action\('wp_loaded', \$registered\['function'\], \(int\) \$priority\)/)
338+
assert.match(lifecycle, /"WP_Rewrite::flush_rules", "playground_maybe_flush_rewrite_rules"/)
339+
assert.match(lifecycle, /"_add_template_loader_filters", "_custom_header_background_just_in_time", "_custom_logo_header_styles"/)
340+
assert.match(lifecycle, /"playground_maybe_flush_rewrite_rules", "playground_save_wp_env_info"/)
341+
assert.match(lifecycle, /"_wp_cron"/)
342+
assert.match(lifecycle, /canonical-wp-loaded-exclude-all": \{ identifiers: \[\], retain: true \}/)
343+
assert.match(lifecycle, /do_action\('wp_loaded'\)/)
332344
assert.doesNotMatch(lifecycle, /get_option|\$wpdb|\$_COOKIE|AUTH_KEY|password|token/i)
333345
assert.equal((lifecycle.match(/do_action\( 'init' \);/g) ?? []).length, 1)
334346
assert.equal((lifecycle.match(/do_action\( 'wp_loaded' \);/g) ?? []).length, 1)

0 commit comments

Comments
 (0)