yolenoyer
yolenoyer

Reputation: 9445

session_write_close() doesn't work with Symfony's built-in server, and with Apache with simultaneous browser pages

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:

    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

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

Answers (1)

maff
maff

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

Related Questions