PHP Session Locking and cURL timeout error

Although PHP sessions seem to be pretty straightforward on the first sight, underestimating their complexity can lead to some hard to track problems.

One of such things is session locking mechanism. It’s purpose is noble – protect session data from being overwritten by other instances of the same or other scripts running at the same time.

When you call session_start() (or immediately when the script starts if session.auto_start is enabled) the session file is locked from being written into, and by default it only gets unlocked when the script has stopped. This means that any other script trying to access that session file at the same time has to wait until the session file is unlocked.

It’s important to realize that each visitor gets assigned with an unique session_id and gets an individual session storage file. Therefore, session locking only becomes an issue if you run a huge number of AJAX requests. In that case they will not be asynchronous after all, as each request will have to wait until the session file is unlocked by a previous request.

Another situation when session locking becomes an issue is long-running scripts. If you have a script that runs for 30 seconds, it’s important to unlock the session file so other scripts didn’t have to wait 30 seconds before they can begin doing their work.

There’s a number of good articles written on the issue, and the one written by Mattias Geniar I find to be the most comprehensive.

However, there’s a non-obvious problem developers keep running into, and it can be quite hard to debug if you don’t know where to look. It happens when your PHP script sends an HTTP request to the same website using its own cookies.

Here’s the example code (caller.php):

session_start();

$_SESSION['var1'] = 'value1';

// ...

$curl = curl_init('http://samedomain.com/remote.php');
curl_setopt($curl, CURLOPT_COOKIE, http_build_query($_COOKIE));
curl_exec($curl);
curl_close($curl);

At first everything looks fine. Your remote.php needs the cookies, so you send them. It’s a dirty trick and you should never use it, but some people do. The devil is in the detail.

If you look at your cookies closely, you’ll notice that it contains the session ID value ($_COOKIE['PHPSESSID']). Script remote.php assumes the session ID as its own and tries to access the same session file, but it can’t because caller.php is still running, so the session file is locked.

Now we have a Mexican standoff: caller.php waits for response from local.php, and which in turn waits for caller.php to unlock the session file. It could be resolved by cURL timeout, but it’s indefinite by default. So if you didn’t explicitly set it, you have to wait for caller.php to reach max_execution_time. And if the limit is high (or disabled entirely), you have a problem.

First of all, you never should run cURL without a timeout set:

curl_setopt($curl, CURLOPT_TIMEOUT, 5);

It will not solve the problem, but at least you will get a chance to handle the cURL error correctly.

To actually solve the issue you need function session_write_close(). The sole purpose of the function is to unlock the session file, so as soon as you’re done writing into sessions, you need to call that function and let the next script run freely. Hopefully the next one will not keep the file locked for too long either.

Let’s see the final code now:

session_start();

$_SESSION['var1'] = 'value1';

session_write_close();

// ...

$curl = curl_init('http://samedomain.com/remote.php');
curl_setopt($curl, CURLOPT_COOKIE, http_build_query($_COOKIE));
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
$result = curl_exec($curl);

curl_close($curl);

That’s it. Just call the function session_write_close() and relax. Or get to fixing other issues in your projects, which actually seems more likely.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.