Reputation: 9445
I have two similar issues about Symfony/Php sessions and the lock which happens when a session is already opened, in a controller which takes long time to process. It's related to the session_write_close()
Php function.
Here is a minimalistic example of a controller which takes long time to process:
<?php
// TestController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class TestController extends Controller
{
/**
* @Route("/test", name="test")
*/
public function testAction(Request $request)
{
// Close the session to unblock other parallel requests:
session_write_close();
$seconds_to_wait = (int) $request->query->get('time', 10);
usleep($seconds_to_wait * 1000000);
return new Response("Waited for $seconds_to_wait seconds");
}
}
In order to not block further requests, I use session_write_close()
at the beginning of the controller.
Here is a little HTML/JS frontend to test XHR requests to this controller:
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /></head>
<body>
<button>Send requests</button>
<ul id="log"></ul>
<script type="text/javascript" src="/js/jquery-2.2.4.min.js"></script>
<script type="text/javascript">
function log(message) {
$('#log').append($('<li>').text(new Date().toTimeString() + ' : ' + message));
}
function send_requests() {
var times = [ 8, 2, 6 ]; // Send 3 requests, first one is longer than the next one
for (var i in times) {
log('A request was sent ('+times[i]+'s)');
$.ajax({
url: '/test', data: { time: times[i] },
dataType: 'text',
success: function(response) { log(response) }
});
}
}
$(function() {
$('button').click(function() { send_requests() });
});
</script>
</body>
</html>
Thanks to the session_write_close()
call, it's working fine when I try this page in prod
environment, without using the Symfony built-in server (Apache instead). For example, the above code gives the expected result:
14:19:20 GMT+0200 (CEST) : A request was sent (8s)
14:19:20 GMT+0200 (CEST) : A request was sent (2s)
14:19:20 GMT+0200 (CEST) : A request was sent (6s)
14:19:22 GMT+0200 (CEST) : Waited for 2 seconds <-- Right, it didn't wait for the first request
14:19:26 GMT+0200 (CEST) : Waited for 6 seconds
14:19:28 GMT+0200 (CEST) : Waited for 8 seconds
But here are the two issues:
dev
environment, with the Symfony built-in server, it doesn't work anymore. In this case it acts as if the session_write_close()
had not been called, and further requests have to wait for older ones to finish. Here the output for the same page, but with this different context: 14:10:06 GMT+0200 (CEST) : A request was sent (8s)
14:10:06 GMT+0200 (CEST) : A request was sent (2s)
14:10:06 GMT+0200 (CEST) : A request was sent (6s)
14:10:14 GMT+0200 (CEST) : Waited for 8 seconds
14:10:16 GMT+0200 (CEST) : Waited for 2 seconds <-- Oops, this request had to wait for the 1st one
14:10:22 GMT+0200 (CEST) : Waited for 6 seconds
As I wrote, it's working well when I'm doing some XHR requests, with Apache instead of the built-in server. But there is also an issue using Apache and the prod
environment:
In a browser, when I try to open directly the url to the long-time request (ex: /test?time=15
), but in two different pages at the same time, the same issue happens again: I have to wait ~30 seconds for the 2nd page, instead of 15.
EDIT: Mathias pointed the problem for the first issue: it's due to the limitations of the Php built-in server, which is only "designed to aid application development".
Upvotes: 0
Views: 602
Reputation: 773
There's a simple answer for the dev/built-in server issue:
The web server runs a only one single-threaded process, so PHP applications will stall if a request is blocked. (http://php.net/manual/en/features.commandline.webserver.php)
So the issue in this case is not session related, but the web server does not support handling your simultaneous requests.
A hint regarding session_write_close()
: you can call save()
on the session object to save the pending session data and close the session (depending on your storage, session_write_close
will be called internally). This is what Symfony does at the end of the process before returning the response (see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/EventListener/SaveSessionListener.php). Example:
<?php
namespace AppBundle\Controller;
// use ...
class TestController extends Controller
{
public function testAction(Request $request)
{
$request->getSession()->save();
return new Response('foo');
}
}
Upvotes: 2