Skip to content

Block themes lose all CSS after the first request per worker ($wp_styles / style-engine state not reset) #5

Description

@luthermonson

Summary

Block themes render under worker mode — but only the first request each worker
serves
gets any CSS. Every later request on that worker prints zero <style> tags.

This is the adapter-side half of ephpm/ephpm#116.
The engine-side crash reported there no longer reproduces (see the ephpm-side
investigation); what remains is WordPress state this adapter has to reset.

Measured (WordPress 6.7.1, Twenty Twenty-Five, worker_count = 1)

request 1 request 2 on the same worker
/ response size 50704 B 12012 B
inline <style> blocks 21 0

Probing the worker's state right after the first render:

done_count=30                 # $wp_styles->done
global_styles_done=1          # wp_style_is('global-styles', 'done')
queue=30
style_engine_stores=1
  store=block-supports rules=9
unique_id=3                   # wp_unique_id() counter, keeps climbing

Everything lost on request 2: global-styles-inline-css,
core-block-supports-inline-css, wp-block-library-inline-css, and the
per-block wp-block-*-inline-css sheets.

wp_unique_id() climbing is a second-order problem: the markup keeps emitting
fresh wp-container-core-group-is-layout-N / ...-navigation-is-layout-N class
names (observed -16 on one request, -30 on the next) while the CSS defining
them is never printed again — so even a cached stylesheet would not match.

Root cause

Three pieces of WordPress state are per-request in fpm but worker-lifetime here:

  • $wp_styles / $wp_scriptsWP_Dependencies::$done and ::$queue
  • WP_Style_Engine_CSS_Rules_Store — static store registry (block-supports)
  • wp_unique_id()'s static counter

Worker::resetRequestGlobals() resets $wp_query / $wp / $post / the current
user, but not these.

Suggested fix

Reset the dependency + style-engine registries per request, alongside the existing
resetRequestGlobals() work:

  • re-instantiate (or clear done/queue/to_do on) $wp_styles and $wp_scripts
  • WP_Style_Engine_CSS_Rules_Store::remove_all_stores()
  • re-fire the enqueue lifecycle so wp_enqueue_scripts / wp_enqueue_block_assets
    run against the fresh registries

Re-instantiating $wp_styles throws away boot-time wp_register_style()
registrations, so the reset likely has to snapshot the registered set at boot and
restore it per request rather than starting empty.

Repro

e2e/Dockerfile as-is, plus a mu-plugin forcing a block theme:

add_filter('pre_option_template',   fn() => 'twentytwentyfive');
add_filter('pre_option_stylesheet', fn() => 'twentytwentyfive');

Run with EPHPM_PHP__WORKER_COUNT=1 — with the default 3 workers, alternating
requests land on different (still-cold) workers and the loss is invisible.

Acceptance

/ renders byte-comparable CSS on every request of a worker's life, and the e2e
suite can drop its bundled classic theme in favour of a stock block theme.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions