Skip to content

Fix: Request State Isolation in Persistent Workers - #330

Merged
rrr63 merged 3 commits into
doppar:4.xfrom
techmahedy:techmahedy-4.x
Sep 23, 2026
Merged

rrr63 merged 3 commits into
doppar:4.xfrom
techmahedy:techmahedy-4.x

Conversation

@techmahedy

Copy link
Copy Markdown
Member

Fix Request State Isolation in Persistent Workers

Summary

Fixed request-state leakage across persistent workers such as Swoole, FrankenPHP worker mode, and RoadRunner.

The fixes ensure that container state, middleware, dynamically resolved services, and runtime configuration remain isolated between requests.

What Was Fixed

1. Container State Isolation

DI/Container.php had $bindings and $instances defined as private static, causing all Container/Application instances to share the same bindings and resolved instances.

This is generally invisible under PHP-FPM/CLI because the PHP process ends after the request, but becomes problematic in persistent workers and can cause concurrent requests to overwrite request-specific services.

Fix:

  • Converted $bindings and $instances to instance-level properties.
  • Kept Container::$instance static as the active-container pointer.

2. Request-Local Middleware Pipeline

Global and group middleware were previously initialized during application boot and could therefore capture the wrong request in persistent workers.

Route middleware also mutated the shared Gateway middleware chain, causing middleware to accumulate across requests.

Fix:

  • Router::resolve() now builds a fresh middleware chain for every request.
  • Global, group, and route middleware are combined into a request-local pipeline.
  • Prevents middleware from accumulating between requests.
  • Ensures CSRF and trusted-proxy middleware execute for every request.

3. Request-Scoped Dynamic Bindings

Dynamic bindings such as FormRequest classes and #[Bind] / #[Resolver(singleton: true)] could leave their resolved instances in the container after the request completed.

This could cause request-specific objects to be reused by subsequent requests.

Fix:

  • Added Container::snapshotBootBindings().
  • Added Container::forgetRequestScopedInstances().
  • Resolved instances created after boot are now released after each request while their bindings remain available.

4. Runtime Configuration Isolation

Config::set() previously modified the shared configuration state and immediately rewrote the cached config.php file.

This could cause runtime changes such as Application::setLocale() or request-level config() changes to leak into subsequent requests and potentially affect other FPM workers.

Fix:

  • Config::set() now only modifies runtime configuration.
  • Added Config::resetRuntimeOverrides().
  • Runtime configuration is restored to the boot snapshot after each request.
  • Request-level configuration changes no longer modify the cached configuration file.

5. General Request Cleanup

Generalized Application::cleanupRequestScopedServices() so it automatically cleans up request-scoped state instead of relying on a hardcoded list of services.

Result

Request-specific state is now properly isolated across requests, making the application safe for persistent worker environments while preserving the existing PHP-FPM/CLI behavior.

@techmahedy techmahedy added bug Something isn't working enhancement New feature or request help wanted Extra attention is needed labels Sep 22, 2026
@techmahedy

Copy link
Copy Markdown
Member Author

@rrr63 @xentixar
Hi, please review this PR.

The changes cover:

  • Container binding/instance isolation
  • Request-local middleware pipelines
  • Cleanup of dynamically resolved request-scoped services
  • Runtime-only config overrides with per-request reset
  • Generalized request-scoped service cleanup
  • Will not show a blank page if we set a controller as a singleton from a Launcher

I also added regression coverage for multiple dispatches using the same application/router instance.

Validation completed successfully:
3,543 tests, 6,706 assertions, 0 failures, 0 deprecations, and PHPStan is clean on all touched files.

Please pay particular attention to the lifecycle/cleanup logic and whether the behavior remains compatible with the existing lifecycle.

@rrr63 rrr63 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thank you @techmahedy

@rrr63
rrr63 merged commit 78a9aaa into doppar:4.x Sep 23, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants