Skip to content

Session warnings during background page cache regeneration #7177

Description

@robertgarrigos

Description of the bug

With page caching and background fetch both enabled, serving an expired cache entry produces two PHP warnings on every affected request:

Warning: session_set_save_handler(): Session save handler cannot be changed after headers have already been sent
in backdrop_session_initialize() (line 306 of core/includes/session.inc).

Warning: session_id(): Session ID cannot be changed after headers have already been sent
in backdrop_session_initialize() (line 331 of core/includes/session.inc).

_backdrop_bootstrap_page_cache() serves the stale page, calls ob_end_flush() and fastcgi_finish_request(), sets backdrop_is_background(TRUE) and lets the request continue to regenerate the entry. The bootstrap then proceeds to BACKDROP_BOOTSTRAP_SESSION, where backdrop_session_initialize() calls session_set_save_handler() and session_id() — with headers already sent.

Instrumenting backdrop_session_initialize() with headers_sent($file, $line) confirms it, on every occurrence:

[example.com] /some/path background=1 → headers sent from core/includes/bootstrap.inc:3277

That line is the ob_end_flush() in _backdrop_bootstrap_page_cache().

Steps To Reproduce

  1. Enable page caching and background fetch, and set page cache expiry to 1 minute (admin/config/development/performance).
  2. Request a URL anonymously, using a unique query string so no other traffic can consume the trigger:
URL="https://example.com/?cachetest=$RANDOM"
curl -s -o /dev/null -D - "$URL" | grep -i x-backdrop-cache   # MISS
curl -s -o /dev/null -D - "$URL" | grep -i x-backdrop-cache   # HIT
sleep 70
curl -s -o /dev/null -D - "$URL" | grep -i x-backdrop-cache   # HIT — stale, triggers this

The third request is the one that reproduces it. With background fetch disabled, the third request returns MISS and no warnings occur.

Why this is probably under-reported

The SESSION bootstrap phase runs before module_load_all(), so watchdog() is a no-op at that point and the warnings are usually discarded. They only reach dblog on sites that happen to have loaded bootstrap modules earlier — for example when $settings['page_cache_invoke_hooks'] = TRUE is set, which calls module_load_all(TRUE) during the page cache phase.

In other words, the warnings are raised on every affected request, but most sites never see them logged. I only found them because one of my sites has that setting.

Actual behavior

Image

Expected behavior

No warning

Additional information

Environment: PHP-FPM (fpm-fcgi), fastcgi_finish_request() available, output_buffering = 4096, zlib.output_compression off. Reproduced on several sites with different module sets. Backdrop 1.34.3

Proposed fix

During a background regeneration, the response has already been sent, so there is no session to initialize for this request:

function backdrop_session_initialize() {
  global $user, $is_https;

  if (backdrop_is_background()) {
    $user = backdrop_anonymous_user();
    date_default_timezone_set(backdrop_get_user_timezone());
    return;
  }
  // ...
}

A narrower alternative would be to guard only the two offending calls, or to check headers_sent() instead — backdrop_is_background() expresses the intent better, but you are better placed than me to judge which is correct here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions