Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Session Serialization Lab

One user. Eight requests. One at a time.

An interactive PHP 8.5 simulator for a concurrency failure that aggregate service health can miss: sibling requests sharing one session ID serialize behind the session lock while workers, errors, and fleet-wide utilization still look ordinary.

The lab turns that hidden mutex into request waterfalls, privacy-safe trace correlation, lock acquisition wait, lock residency, occupied-worker time, and a per-session latency tail. It is designed to show what telemetry.sh can reveal when a fleet dashboard says “healthy” but one user journey is frozen.

What it demonstrates

PHP’s default file-based session handler locks a session file once the session opens. Another request for the same session cannot access it until the first request terminates or explicitly closes the session. That consistency boundary can accidentally include database work, API calls, template rendering, and anything else that happens before shutdown.

The simulator replays one same-session burst through four boundaries:

Policy Lock boundary Operational tradeoff
Lock through response Session read, application work, downstream call, and write all remain inside the lock Consistent writes; serialized sibling requests and occupied workers
Early write + close Required mutation is persisted before slow work, then session_write_close() releases ownership Practical when later code never expects another persisted session mutation
Read and close A read-only snapshot uses session_start(['read_and_close' => true]) Minimal lock residency; later session changes are intentionally not written
Stateless claims Identity claims are verified without opening server-side session state Maximum concurrency; expiry, revocation, and claim freshness move into the design

With the default scenario—eight tabs arriving within 35 ms, 24 PHP workers, 260 ms of application work, and a roughly 640 ms downstream call—the deterministic model produces:

Policy p99 response Lock-wait p99 Blocked worker time Average fleet utilization
Lock through response 7,156 ms 6,249 ms 25,070 ms 52.7%
Early write + close 977 ms 70 ms 280 ms 59.2%
Read and close 920 ms 7 ms 28 ms 59.8%
Stateless claims 914 ms 0 ms 0 ms 59.6%

The bad policy has the lowest average fleet utilization because the private queue extends the observation window. That is the point: a normal aggregate can coexist with an awful per-session tail.

These are analytical results, not timings captured from PHP-FPM or a production session backend. Use the lab to form a hypothesis, then validate it with real request and session telemetry.

Run it

No framework, Composer dependency, database, or asset build is required.

make check
make run

Open http://127.0.0.1:8080.

Or run the non-root container:

docker build -t session-serialization-lab .
docker run --rm -p 8080:8080 session-serialization-lab

The container uses PHP 8.5.8 on Alpine, runs as 10001:10001, and starts four CLI server workers. PHP’s built-in server is used only to serve this dependency-free educational lab.

API and CLI

The browser calls:

curl 'http://127.0.0.1:8080/api/simulate?tabs=8&workers=24&downstream_ms=640'

Integer query parameters (the API clamps each value to the model’s safe range):

  • tabs
  • workers
  • work_ms
  • downstream_ms
  • session_read_ms
  • session_write_ms
  • arrival_spread_ms
  • baseline_utilization_percent
  • burst_second
  • seed

Print the default result without starting a server:

php bin/simulate.php --json

Telemetry recipe

Do not record a raw session ID or cookie. Derive a stable, keyed, one-way fingerprint with an operationally managed secret, and use that privacy-safe value only for the correlation window you need.

Useful fields:

session.key_hash
session.lock.wait_ms
session.lock.held_ms
session.lock.release_reason
http.request.session_siblings
worker.blocked_reason
worker.queue_ms
user_journey.p99_ms
session.write.after_close

Suggested investigation:

  1. Group concurrent requests by the privacy-safe session fingerprint.
  2. Split worker admission, session acquisition, session ownership, application work, downstream calls, and response time into separate spans.
  3. Find application or downstream spans nested inside session ownership.
  4. Compare the affected session’s tail with the fleet-wide endpoint percentile.
  5. Verify that an early close does not produce attempted writes after release.

The instrumentation boundary is the lesson. A session_start span without separate wait and held durations cannot tell you whether the handler was slow or whether another request owned it.

Model mechanics

Requests arrive in a deterministic burst and acquire the earliest available worker. A locking policy then waits for exclusive session ownership. That wait continues to occupy the assigned worker. The selected policy determines how long ownership lasts and whether application work runs inside or outside it.

Intentional simplifications:

  • requests acquire the session lock fairly in arrival order;
  • one session is modeled, with configurable background fleet utilization;
  • application and downstream duration vary deterministically by seed;
  • worker admission and session acquisition are modeled separately;
  • storage, network, garbage collection, and scheduler contention are omitted;
  • stateless claim verification is represented by a fixed two-millisecond cost;
  • session-write correctness is described as a policy constraint, not proven by this performance model.

References

Stack

  • PHP 8.5 for the deterministic model, JSON API, router, and tests
  • semantic HTML, modern CSS, vanilla JavaScript, and Canvas 2D
  • no framework, Composer package, database, or remote browser asset
  • security headers, strict input normalization, and deterministic fixtures
  • non-root Alpine container and two-lane GitHub Actions CI

License

MIT

About

Interactive PHP simulator for hidden per-session request serialization, lock queues, blocked workers, early close, and stateless claims.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages