Lecture
Asynchronous requests allow the browser to call several API endpoints at the same time without blocking the user interface. But on the PHP side, this parallelism can unexpectedly disappear.
The reason is the session locking mechanism. If several requests from the same user use one PHP session, each of them may wait for the session lock to be released, so requests sent simultaneously via AJAX or fetch are actually executed sequentially.
As a result, a simple session_start() can become a hidden performance bottleneck: the longer one request runs, the longer the other requests from the same user are kept waiting.
In this article we will look at why PHP session locking occurs, how it affects asynchronous requests, and what ways there are to solve the problem, from calling session_write_close() early to dropping session-based authentication in the API entirely.
PHP has a very characteristic problem with sessions and parallel AJAX/fetch requests: a session can effectively turn asynchronous requests into sequential ones.
Imagine the browser sends simultaneously:
GET /api/user GET /api/products GET /api/messages GET /api/notifications
You expect:
┌─ /user ───────┐ ├─ /products ───┤ START ─┼─ /messages ───┼─> simultaneously └─ /notifications
But if every PHP script does:
session_start();
then PHP usually locks the session data for that user. The next request with the same PHPSESSID waits until the previous request finishes working with the session.
What you get is roughly:
Request 1: session_start() └────── work 1 ──────┘ session closed Request 2: WAIT ──────────┘ └── work 2 ─── Request 3: WAIT ─────────────────────────┘ └─ work 3
That is, AJAX is technically asynchronous on the browser side, but PHP requests can be serialized because of the session lock. This is especially noticeable if one request runs for a long time.
Suppose each endpoint takes 1 second.
Without locking:
4 requests × 1 second ≈ 1 second total time
With a single PHP session lock:
request 1 → 1 sec request 2 → 1 sec request 3 → 1 sec request 4 → 1 sec ≈ 4 seconds
And this can happen only for one user/one session, so in server load tests the problem is sometimes not noticeable at all.
session_write_close()If the session is needed only at the beginning of the request, for example:
session_start(); $userId = $_SESSION['user_id'];
and then heavy work follows:
// database queries // API // processing // calculations // sleep() // etc.
it is better to do:
session_start(); $userId = $_SESSION['user_id']; session_write_close(); // long-running work follows
session_write_close() saves the session data and releases the lock, after which other parallel requests from this user can continue.
After:
session_write_close();
you must not rely on later changes to $_SESSION being saved.
For example:
session_start(); $_SESSION['user_id'] = 123; session_write_close(); $_SESSION['something'] = 'abc'; // this will no longer be saved properly
So the correct pattern is:
session_start(); $userId = $_SESSION['user_id']; $someValue = $_SESSION['someValue']; session_write_close(); // all the rest of the work
If you mean Laravel specifically, the problem is relevant there too.
For example, suppose your React app makes these requests simultaneously:
/api/services /api/products /api/messages /api/profile /api/notifications
and Laravel uses ordinary session-based authentication.
If a middleware opens the session and the endpoint then performs a long operation, other requests from the same user may wait for the session lock to be released.
That is why a very strange situation sometimes arises:
"I send 5 AJAX requests at the same time, so why does the server process them one after another?"
And the first thing worth checking is PHP session locking.
This is not a problem with async/await, React, Axios, or the browser itself. It is server-side synchronization of session access.
If the first solution is session_write_close(), then the second fundamental solution is not to use PHP sessions for API requests at all and to switch to stateless authentication, for example Bearer tokens.
Instead of:
Browse │ │ PHPSESSID cookie ▼ PHP Session │ └── session lock
use:
Browser │ │ Authorization: Bearer▼ API │ └── token verification
Then no request opens the user's shared PHP session and none of them blocks other parallel requests.
For example:
GET /api/messages Authorization: Bearer abc123
simultaneously:
GET /api/services Authorization: Bearer abc123
and:
GET /api/profile Authorization: Bearer abc123
All three requests can run independently on the server.
For APIs, the following are usually used:
Judging by your previous questions, you already had an option with a Bearer token in localStorage and an Axios interceptor. In that case this is exactly the approach that lets you avoid depending on the PHP session lock.
Do not think:
"A Bearer token automatically makes requests fast."
It only removes the PHP session locking problem. If all 5 requests hit the same slow database at once or perform a heavy operation, they can still be slow.
Summary:
| Approach | Session lock | Parallel API requests |
|---|---|---|
PHP Session + session_start() |
Yes | +- may be blocked |
Session + session_write_close() |
Only briefly | + |
| Bearer Token / stateless API | No | + |
| Laravel Sanctum API | Depends on the authentication method | +for token-based API |
If your API is mostly REST + React, I would prefer the second approach: a stateless API with a Bearer token.
PHP sessions are convenient, but their locking mechanism is important to take into account when developing applications with many parallel requests.
If several AJAX requests use one session, a long request can hold the session lock and force the other requests to wait. Outwardly it looks as if the asynchronous requests are executed sequentially, even though the browser sent them at the same time.
If the session is needed only to read data at the beginning of the request, a simple solution is to fetch the required values and call session_write_close() as early as possible. This releases the lock and lets other requests continue.
For APIs, especially in SPAs and mobile applications, it is even better to consider stateless authentication with Bearer tokens. With this approach the user's state is not stored in a shared PHP session, so requests do not compete for a single session lock.
The main principle is simple: do not hold the PHP session longer than is really necessary. Client-side asynchrony makes sense only when the server is also able to process requests in parallel.
Comments