Skip to content

ext/session: fix session GC fork#22164

Open
jorgsowa wants to merge 1 commit into
php:masterfrom
jorgsowa:fix-session-gc-fork-correlation-21314
Open

ext/session: fix session GC fork#22164
jorgsowa wants to merge 1 commit into
php:masterfrom
jorgsowa:fix-session-gc-fork-correlation-21314

Conversation

@jorgsowa
Copy link
Copy Markdown
Contributor

@jorgsowa jorgsowa commented May 27, 2026

Fixes #21314

If I understand the logic of the bug correctly (retrieved with the help of AI):

  1. In NTS builds, globals_ctor (GINIT) runs at module registration inside php_module_startup.
  2. In PHP-FPM, php_module_startup runs in the master process (fpm_main.c:1757), before fpm_run() forks the workers.
  3. So the PCG is seeded once in the master. Every worker is a fork() of the master → all inherit the same seed at sequence position 0. Nothing reseeds per request: PHP_RINIT_FUNCTION(session) never touches PS(random).
  4. The master never serves requests, so its PCG stays frozen at position 0. When workers recycle (pm.max_requests) and respawn, each new worker forks from the master and restarts at position 0 of the identical sequence.

Changing the logic back to drawing per request gives back the true randomness.

Copy link
Copy Markdown
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a correct fix. Invoking the CSPRNG for every GC attempt is needlessly expensive. It is also fallible - if it fails, sessions will never be collected (e.g. when the getrandom() syscall and /dev/urandom are both unavailable).

@TimWolla
Copy link
Copy Markdown
Member

Sharing the sequence across workers is not great, but also not an issue. We are not interested in unpredictable randomness, but instead use the randomness as a proxy for a probability. Different FPM workers will naturally handle different numbers of requests over time, which means that they will be sitting at different points in the sequence and still avoid "hotspots".

@jorgsowa
Copy link
Copy Markdown
Contributor Author

jorgsowa commented May 27, 2026

We are not interested in unpredictable randomness

What's the point of randomness then and specifying gc.divisor and gc_probability if it's not respected. Whole concept should be redefined then, because it doesn't make much sense anymore.

Invoking the CSPRNG for every GC attempt is needlessly expensive

Would the acceptable trade-off be to lazy-load it at first request instead of in module initalization?

@TimWolla
Copy link
Copy Markdown
Member

if it's not respected.

I'm not sure what you mean by “not respected”. Even if the sequence is not unpredictable, it will run a collection for gc_probability out of every gc_divisor requests on average.

Would the acceptable trade-off be to lazy-load it at first request instead of in module initalization?

Probably, yes.

@jorgsowa
Copy link
Copy Markdown
Contributor Author

Even if the sequence is not unpredictable, it will run a collection for gc_probability out of every gc_divisor requests on average.

That's not always given. There may be cases for short-lived processes when GC is never run based on the set GC session configuration. Of course this is edge-case, but theoretically may happen and it's serious bug then.

I will try to develop better solution tomorrow.

@TimWolla
Copy link
Copy Markdown
Member

As a proper long-term fix we probably need per-extension hooks in php_child_init().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Different session garbage collector behavior between PHP 8.3 and PHP 8.5

2 participants