Symfony Session lock serialization for concurrent same-session requests
0 reputation · 13 Nov 2022, 10:58 UTC
0 reputation · 13 Nov 2022, 10:58 UTC
The Symfony Session component delegates to PHP's native session handler by default. The design question is how the exclusive session lock lifetime interacts with concurrent requests that share the same session id, and where the lock release decision belongs in application code.
With the default file-based handler, the lock is held for the request duration unless released explicitly. Changing storage to Redis or Memcached changes where the lock is held and its performance characteristics, but does not remove the need to manage lock lifetime. Early release can reduce serialization for read-only work but raises uncertainty about subsequent session writes in the same request.
Under default Symfony session configuration with PHP native handling, at what point in the request lifecycle is the exclusive session lock released if no explicit close is performed? Does an explicit early release make later session modifications in the same request unsafe or silently lost? How does the choice of session storage handler affect the observable serialization of concurrent same-session requests?
Under Symfony's default configuration, the session component delegates to PHP's native session handling. With the default file-based handler, the exclusive lock is acquired at session_start() (which Symfony triggers lazily when the session is first used) and is held until PHP's request shutdown, after the response has been sent — unless you explicitly close the session earlier. There is no automatic mid-request release; the lock lifetime is effectively the request lifetime.
An explicit early release via session_write_close() (exposed in Symfony through $session->save()) is not a neutral operation. Once the session is closed, the storage handle is released and any subsequent writes to $_SESSION in the same request are silently discarded — they modify the in-memory array but are never persisted. No error is raised. So early release is only safe when you can guarantee the remaining work is strictly read-only with respect to the session.
Changing the handler changes where the lock lives and its cost, not whether lock lifetime matters:
flock() on the session file, held by the PHP process for the request duration. Concurrent same-session requests queue behind it. Note that in PHP-FPM, different workers are different processes, but the file lock still serializes them correctly.RedisSessionHandler implements its own lock (a spin-lock on a Redis key with a TTL). Concurrent same-session requests still serialize, but the lock now has an independent lifetime (lock TTL) that you must size correctly, or a long request can outlive its lock.For long-running, read-only endpoints (polling, progress checks, streaming), close the session as early as possible, before the expensive work:
public function poll(SessionInterface $session): JsonResponse
{
$data = $session->get('progress'); // read what you need first
$session->save(); // releases the lock (session_write_close)
// ... slow read-only work; do NOT touch $session again
return new JsonResponse($data);
}The critical discipline: after $session->save(), treat the session as gone. Any flash messages, attribute writes, or authentication state changes after that point will appear to succeed and then vanish.
Because lock internals vary across Symfony 5.x/6.x and PHP versions, confirm on your stack:
session_start() blocks. Fire both concurrently with the same session cookie (e.g., two curl requests sharing a cookie jar).$session->save() before the sleep in the first controller — the second request's blocking should disappear.MONITOR or inspect lock keys during a slow request to observe lock acquisition and TTL.One caveat worth flagging: the exact lock TTL defaults and whether reads bypass the lock differ by handler and Symfony version, so treat the Redis/Memcached rows above as design guidance and verify against your installed version before relying on them in production.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.