Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,075 of 9,075 pages
About the help in other languages
All help pages

Concurrency, statics and long-lived processes

How to write Exponential code that is correct when one PHP process serves many requests and many processes serve one site - static caches and singletons, idempotent registrations, exit and cleanExit, class_exists guards…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/help/software-engineers/concurrency-statics-and-long-lived-processes.md

On this page

Concurrency, statics and long-lived processes

This page is for engineers who write or review PHP for Exponential. Two facts make some classic PHP habits unsafe: under Velocity one process serves many requests, and on any server many processes (and sometimes several machines) serve one site at the same time. The rules here come from Kernel architecture and the worker pool specification.

Contents

  1. Two kinds of sharing
  2. Statics and singletons
  3. Registrations must be idempotent
  4. Ending a request
  5. Code that changes under a running worker
  6. Resources the application must manage
  7. Races between processes
  8. Testing for these bugs

Two kinds of sharing

SharingWhereTypical bug
In time: one process, many requestsVelocity persistent workers, the warm-upa value from request A appears in request B
In space: many processes, one siteevery server, several web nodes, cronjobs beside web requeststwo processes write the same file or row

Statics and singletons

The kernel uses static caches and singletons widely (eZINI::instance(), eZDB::instance(), per-class caches). Velocity restores static class properties to their start-up snapshot after each request, so a static cache does not leak between requests in a normal worker. Two cases still go wrong:

  • State set during the warm-up becomes every worker's baseline. A warm-up resets user-class statics to their defaults and keeps only configuration and type registries.
  • State the snapshot cannot see: globals named in keepGlobals, registries filled once with include_once, resources held in statics (connections, file handles).

The rule for kernel code: no per-request data in statics without a reset. Prefer passing values as parameters, and keep request-scoped caches keyed on something that changes per request.

Registrations must be idempotent

A listener, handler or callback attached once per request in a long-lived worker accumulates: the second request runs it twice. Register in a way that is a no-op when the registration exists already, and name listeners as Class::method strings so duplicates are detectable. This applies to ezpEvent listeners, template operator registrations and autoloaders (Events, extension points and plugins).

Ending a request

  • Do not use exit for flow control. Kernel code ends a request through eZExecution::cleanExit().
  • Under Velocity, exit and die are rewritten to end the request, not the worker.
  • Do not wrap a download followed by cleanExit() in a catch ( Exception ) block that also catches the request-ending signal: the page is appended to the download and the output is doubled.

Code that changes under a running worker

Workers inherit the classes the warm-up loaded. After a deploy:

  • a new class named in a shipped INI file is unknown to a worker started before the deploy; guard its use with class_exists() where the kernel loads it, as ezpKernelWeb does;
  • a changed class reaches the workers only after exp:velocity restart or exp:velocity deploy;
  • a changed template or stylesheet needs no restart, only the template caches and Velocity's response cache.

Resources the application must manage

ResourceWhat to do
Database transaction statecommit or roll back before the request ends; never leave a transaction open
Database connection in the warm-upclose it before the fork, so workers do not share a socket
File handles, stream contexts, cURL handlesclose them in the request that opened them
Signal handlersdo not install them in request code

Races between processes

  • Files under var/ may be written by different operating system users (the web server user, the user that runs cron or the console, Velocity's worker user). Create files with the installation's file modes and test with mixed users (File modes).
  • Cache files go through eZClusterFileHandler, which handles concurrent generation on shared storage.
  • Cronjob parts must be safe to run twice and to be interrupted.
  • SQLite serializes writers with a lock file; long write transactions block other writers.

Testing for these bugs

  • Run a test twice in the same process: state left behind by the first run shows up in the second.
  • Under Velocity, request several distinct URLs in a row and check that each returns its own content.
  • A test that passes alone and fails in the whole run shares state with another test; run the two files together to find the pair (CI gates).
  • Watch the Velocity log for workers replaced over the memory ceiling: a leak.

Become an Exponential Partner

Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x