Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[HttpFoundation] Strange behaviour with empty session, Swift and fastcgi_finish_request function #6417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
acasademont opened this issue Dec 19, 2012 · 55 comments

Comments

@acasademont
Copy link
Contributor

I have been hit with a very strange situation during these last 2 days. I have a custom security listener and provider that sends some emails to the user and the user's "godfather" when registering. Immediately after registering, the user is redirected to his personal area. The problem arises when from time to time, the user is logged out when redirected.

Digging into the code, I can clearly see that the session is totally empty after the redirect, so no information about the user token is present. However, when I reload the page the session is perfectly filled and the user becomes logged in again. So I assumed that the session was still not written to disk (using native php file handler) when the user was redirected!

So here is what I found out. When sending emails with the default SF mailer (Swift) some bg processing must be kept somewhere. I am using nginx + php-fpm so the fastcgi_finish_request function is called and the page is immediately flushed. But as there must be some kind of bg processing, the session is still not written to disk when the next request coming from the redirect hits the server and the session is empty.

Comenting fastcgi_finish_request function call or not sending the emails seems to fix the problem so it has to be some problematic combination of those 2 things...but who knows!

Any ideas?

@acasademont
Copy link
Contributor Author

Another interesting point is that this only happens when session_fixation_strategy is set to "migrate", as it is by default. Setting it to "none" seems to prevent the strange behaviour. Is there any compelling reasong for this session strategy? A part froom that, why the old session is not deleted when migrating to a new session_id?

@stof
Copy link
Member

stof commented Dec 26, 2012

I faced a similar issue. It looks like the session is not saved quickly enough. The browser receives the redirection without waiting the end of the process (sending the email), thanks to the use of fastcgi_finish_request. But then, the next request seems to come before the previous session is saved (as a redirection is really quick to process client side).

@Drak do you have an idea to fix this ?

@acasademont
Copy link
Contributor Author

I have found finally thanks to newrelic that there is a __destruct method in the SwiftMailer lib that causes that delay, as the session close will be the last thing to be done.

Swift_Transport_AbstractSmtpTransport::__destruct

The quick and dirty solution would be to close & write the session manually just after the last piece of the user code (maybe as a terminate event listener?)

@scourgen
Copy link
Contributor

@stof,I've did some test,I believe that it's not related with fastcgi_finish_request.

acturally in my case (#6481), It only happens when I don't use native file session handler.

@jkm9000
Copy link

jkm9000 commented Dec 31, 2012

The issue I opened is running under apache/mod_php so fastcgi_finish_request should have no effect. Like @scourgen if I revert back to file based sessions it works fine. I tried removing the destructor that @acasademont mentioned but the issue persists.

I even tried putting in a couple of calls to sleep just to slow things down and see if perhaps things were happening too quickly, but it made no difference.

    $this->mailer->sendConfirmationEmailMessage($user);
    sleep(3);
    $this->session->set('fos_user_send_confirmation_email/email', $user->getEmail());
    sleep(3);
    $url = $this->router->generate('fos_user_registration_check_email');
    $event->setResponse(new RedirectResponse($url));

FriendsOfSymfony/FOSUserBundle#921

@ghost
Copy link

ghost commented Jan 1, 2013

I'm wondering if the use of fastcgi_finish_request inside the Response::send() method is very wise. There is no real possibility to do any housekeeping work, like for example, flush the session to take care of this bug.

I don't know the reason why this code is in the Response::send() but I'd be tempted to say it's an improper place - or at least it should be controllable. Has anyone tried commenting out that code?

Lastly if it's not a problem with the native files handler, which drivers is it a problem with specifically? For example if it happens with the Memcached custom solution, does it happen with the native ones shown here

@jkm9000
Copy link

jkm9000 commented Jan 1, 2013

The issue happens when using PdoSessionHandler, I've only tried that and native files.

@ghost
Copy link

ghost commented Jan 1, 2013

I don't know if it's related but IMO there is something wrong with the PDO timing collision code which is using whole seconds, which assumes two requests cant be completed within the same second. IMO the code should be using microtime().

@jkm9000
Copy link

jkm9000 commented Jan 1, 2013

Good to know, thanks. Ultimately I was going to use Redis for sessions so I'll do that sooner than later and see if it happens with that too.

@jkm9000
Copy link

jkm9000 commented Jan 1, 2013

Problem still exists using Redis for sessions.

framework:
    session:
        handler_id: snc_redis.session.handler

snc_redis:
    clients:
        default:
            type: predis
            alias: default
            dsn: redis://localhost
    session:
        client: default
        use_as_default: true

@ghost
Copy link

ghost commented Jan 2, 2013

It would be interesting if you tried the native redis handler from this repo - there's no bundle for it yet.

@acasademont
Copy link
Contributor Author

What I think is that we are facing the same problem (empty session) caused by different things. Maybe we should open some other issues?

@jkm9000
Copy link

jkm9000 commented Jan 2, 2013

The issue remains using the native redis handler from drak. Switch back to the default filesystem and it works fine. Any other session handler and I lose data.

@ghost
Copy link

ghost commented Jan 2, 2013

Well it could be a PHP issue. It'a worth reporting over at PHP.net if you ask me... There have been some pretty serious session issues fixed in PHP recently and should be out in the next release.

@ghost
Copy link

ghost commented Jan 4, 2013

@jkm9000 can I ask you to hack one file in Symfony and see if the problem persists (testing both native and non-native session handlers). In https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php#L21

Line 21, just comment out just before the extends keyword. The reason is there is a bug in PHP 5.4 (it's fixed in dev, but not released yet) which could be causing this issue.

@stof
Copy link
Member

stof commented Jan 4, 2013

@Drak When I faced the issue, it was on PHP 5.3 with the PdoSessionHandler

@jkm9000
Copy link

jkm9000 commented Jan 4, 2013

Tried changing that line, but same problem happens. I tried with both your redis handler and the snc_redis one. Initially was running php 5.4.4, have since upgraded to 5.4.10 to see if that helped.

@ghost
Copy link

ghost commented Jan 4, 2013

The it's clearly either a PHP issue, fast cgi, or Response object problem. I would report this ASAP on bugs.php.net and ping Arpad there on the issue.

@jkm9000
Copy link

jkm9000 commented Jan 10, 2013

https://bugs.php.net/bug.php?id=63963

I did not see a way to ping Arpad though.

@ghost
Copy link

ghost commented Jan 11, 2013

I have sent him an email referencing it.

@jkm9000
Copy link

jkm9000 commented Jan 11, 2013

Thank you.

Also, I don't think it is fast cgi because it originally happened to me using mod_php / apache.

@jkm9000
Copy link

jkm9000 commented Jan 17, 2013

Update - I upgraded my project to Symfony 2.2 and my issue no longer exists.

@pourquoi
Copy link

maybe related:

with php-fpm so using fastcgi_finish_request() in response::send:

PAGE 1: sending bunch of mails using memory spool, redirecting to PAGE 2
PAGE 2: Firewall::onKernelRequest is blocking until all mails are sent

adding session_write_close() BEFORE $response->send() in app.php fixed it

is it a bad thing to calling session_write_close() here?

@ghost
Copy link

ghost commented Jan 21, 2013

You should really call $session->save() - if this is a PHP related bug then maybe a cookbook entry is in order

if (function_exists('fastcgi_finish_request') && $request->hasSession()) {
    $request->getSession()->save();
}

@ghost
Copy link

ghost commented Jan 21, 2013

I've updated the PHP ticket with this information and pinged Arpad again.

@kotsis
Copy link

kotsis commented Jan 26, 2013

ok i'm pasting here cause it has some value for anyone trying to reproduce this. Also since this is reproducible why hasn't this issue the label 'Bug'? Anyways:

First you have to enalbe database sessions, i did so by following this guide http://symfony.com/doc/current/cookbook/configuration/pdo_session_storage.html

Then you can create 2 actions like this:

/**
    * @Route("/lol", name="myroute_lol")
    * 
    */
    public function lolAction()
    {
    $this->container->get('session')->set('testsess', '[email protected]');

    //send an email
    $message = \Swift_Message::newInstance()
        ->setSubject('Test subject')
        ->setFrom('[email protected]')
        ->setTo('[email protected]')
        ->setBody($this->renderView('MyBundle:Default:mail.txt.twig', array('body' => 'some body text')))
    ;
    $this->get('mailer')->send($message);

            return $this->redirect($this->generateUrl('myroute_lol2'));
    }

/**
    * @Route("/lol2", name="myroute_lol2")
    */
public function lol2Action()
    {
    $email = $this->container->get('session')->get('testsess');
        return new Response('email:'.$email);
    }

If you clear you browser cookies then each time you go to the /lol url then you get redirected to /lol2 and the session returns an empty string for session variable 'testsess'
If you refresh /lol2 then the second time it works and shows the correct value.

It has something to do with mail. If you comment out the line where the send() happens then all is ok. It has something to do with not writing the session in the database after the completion of the execution of the action.

I have debugged the swift mail bundle a bit and it seems that when send() is called the mail is saved in memory (spool memory, uses class Swift_MemorySpool), so i suppose mails are sent after the completion of the action, after the action returns, somewhat asynchronously i suppose.

It seems that something happens in the code after the action returns a redirect.
If you change the redirect and simply output something then the session is ok.

This bug has 3 conditions, using mysql sessions, sending an email and then redirecting to another action.
That's all.

PS. using symfony 2.1.7
PS2. It has nothing to do with fastcgi, it happens in preforked apache with php as module also.

@pourquoi
Copy link

Issue is quite clear in all cases: the session is still open after a $response->send() and is only closed after $response->terminate(), i.e. on script end.

The redirection is sent to the browser in $response->send() (in your case on flush(), in mine on fastcgi_finish_request()).

The next page is trying to access session data wich has not yet been written because the previous script is still sending the mails in $response->terminate(). (in my case, with memcached, the session is even locked until first script end)

did you try

if (function_exists('fastcgi_finish_request') && $request->hasSession()) {
    $request->getSession()->save();
}

before $response->send() ?

IMO it is a php bug only if flush() or fastcgi_finish_request() is supposed to trigger the write/close session handlers, otherwise there should be a response listener to do it

@samanime
Copy link

It appears this is still occurring in Symfony 2.3. I tried the various suggestions above, but none of them appeared to make a difference for me. My guess would be because I have even more chances for the race condition, due to the fact that I have some JSON files which load information as well (which means multiple simultaneous loads).

I'm using the PdoSessionHandler. The behavior I get is that it loads the first page I go to, and maybe some of the other files, but it usually fails on one of the JSON pages (no Token message), and then when I try to change pages, I'm logged out.

@tiagojsag
Copy link
Contributor

+1 on 2.3.1

@emgiezet
Copy link

+1 on 2.2.1

@rdimatt
Copy link

rdimatt commented Sep 18, 2013

Is there an update to this issue?

I'm using 2.2.2

@advancingu
Copy link

It's been 11 months now since this issue was reported. Has there been no progress?

I am running into this problem with MongoDB session storage under Symfony 2.3.6, nginx and PHP 5.3.10. Neither upgrading to PHP 5.5.5, nor any of the fixes proposed here worked. Is there really no new information or workaround regarding this issue?

@schemar
Copy link

schemar commented Feb 24, 2014

A quick fix is to disable spooling for SwiftMailer. I am still uncertain whether this is the actual solution, but it seems to work.
By disabling spooling, the mailer will block execution while sending the mail.

I commented out the line that enables spoolingin the configuration for SwiftMailer:

spool:     { type: memory }

By default, the mailer will not use spooling but send the mail right away: http://symfony.com/doc/master/cookbook/email/spool.html

@rainercedric23
Copy link

hi try this one this works for me, creating a custompdosessionhandler hope it helps :D http://stackoverflow.com/questions/22033564/pdosessionhandler-fosuserbundle-login

@pawelbaranski
Copy link

I can confirm this happens in my case with memcached or pdo as session storage. Native file storage seems immune to this.

Disabling memory spool for swiftmailer fixes it, but this is rather a dirty trick than fix. It does not satisfies me. I hope this is going to be fixed soon

@thewilkybarkid
Copy link
Contributor

Not that this seems to need confirming, but I've just been caught out by not having flash messages appear (using 2.4 + Apache/mod_php + PDOSessionHandler + SwiftMailer memory spooling).

@matthieuauger
Copy link
Contributor

Faced the same problem with both PDO and redis, PHP 5.4 + nginx / php-fpm. Symptoms are flash messages randomly not appearing, auto-connections not always working, in fact everytime you call $session->set() before a redirect().
If you explicitly call $session->save() before the redirection, it's ok.

We quick-fixed that by adding a listener on the response event and forcing $session->save(), but it doesn't seems to be a long-term solution.

For information, this logic (explicitly save session on response) is already used during the tests process : https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php#L58

@Tobion
Copy link
Contributor

Tobion commented Apr 11, 2014

So my research summary of how to fix this problem is:

  1. We need to call session_write_close before sending the response
    • This ensures the session is written when the client receives the response (flush/fastcgi_finish_request/redirect) and a we receive a new request while the previous request is not terminated (sending mail spool or whatever) which would automatically call session_write_close.
    • We could do this in Response::send() directly or maybe better in a response listener or Kernel::handle because we can use the Session instance there
    • This means the session is closed after sending the response. But since session is automatically started by the Session class when accessing it, code that uses session later like listeners for terminate event would just restart the session.
    • One could argue, devs should close the session themselves when they don't need it anymore. But we can see from the amout of issues that it's a common problem that should be adressed automatically.
  2. We can additionally add locking support for custom session handlers. See [HttpFoundation] Session handlers should use a lock #4976
    • This prevents losing data through concurrent session manipulation. PHP does this for session files.
    • This would of course fix this issue as well but often slow things down unnecessarily. The same sesssion can also be access from one request at a time. Other requests would block for the previous to finish.
    • Locking support could be optionally enabled.

So all in all, we should save the session (via listener) when sending the response. This makes sure, that the next request (redirect) will have the session information available (and does not log you out). Session locking also solves this problem (but it might no be activated, e.g. due to optimistic approach). Also even if locking is activated, the auto-saving of the session via listener would prevent that the session is locked longer than needed due to long running things after sending the response.

@billmccord
Copy link

I'm trying to save the session explicitly after setting, Swift Mailing, and before a redirect to workaround the issue with resetting a password in the FOSUserBundle and it works on my dev environment (PHP 5.5), but not on on staging site (PHP 5.3). I'm getting the following error:
Uncaught PHP Exception RuntimeException: "Failed to start the session: already started by PHP ($_SESSION is set)."
Any suggestions regarding how to workaround this issue in 5.3?

@veego
Copy link

veego commented May 14, 2014

Have php 5.5 and mongodb session storage, if I changed what @Drak say : #6417 (comment) now its everything working as expected, but hacking core is not good idea.

namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;

/**
 * Adds SessionHandler functionality if available.
 *
 * @see http://php.net/sessionhandler
 */


    class NativeSessionHandler {}

@gedimin45
Copy link

Is there any new insight on this?

@EricReiche
Copy link

+1
This is really causing a lot of problems in any load balanced setup.

@ruttencutter
Copy link

Ok, all, what can we do to push forward a clear solution for this? All I can think of offhand is utilizing a secondary mail queue to store messages and then having another process come back in and handle sending them. Any more ideas folks?

@sabliao
Copy link

sabliao commented Aug 1, 2014

Would using the master version of the PDOSessionHandler, which implements locking, do the trick?

Link to file

@Tobion
Copy link
Contributor

Tobion commented Aug 1, 2014

@sabliao yes, but you should also close the session early when you don't need write access

@gregholland
Copy link

I've just hit the same issue trying to implement a session handler for DynamoDB.

@Tobion
Copy link
Contributor

Tobion commented Sep 30, 2014

@gregholland Are you aware of http://docs.aws.amazon.com/aws-sdk-php/guide/latest/feature-dynamodb-session-handler.html ? It also optionally implements locking.

@gregholland
Copy link

@Tobion Yep, that's what I'm using. I've tried setting the locking strategy to pessimistic but I still have the same issue. When I attempt to login I end up redirected back to the login page with an empty session, even though it is being successfully created.

fabpot added a commit that referenced this issue Nov 2, 2014
…Tobion)

This PR was merged into the 2.3 branch.

Discussion
----------

[Kernel] ensure session is saved before sending response

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #6417, #7885
| License       | MIT
| Doc PR        | n/a

Saves the session, in case it is still open, before sending the response.

This ensures several things in case the developer did not save the session explicitly:

- If a session save handler without locking is used, it ensures the data is available
on the next request, e.g. after a redirect. PHPs auto-save at script end via
session_register_shutdown is executed after fastcgi_finish_request. So in this case
the data could be missing the next request because it might not be saved the moment
the new request is processed.

- A locking save handler (e.g. the native 'files') circumvents concurrency problems like
the one above. By saving the session before long-running things in the terminate event,
we ensure the session is not blocked longer than needed.

- When regenerating the session ID no locking is involved in PHPs session design. See
https://bugs.php.net/bug.php?id=61470 for a discussion. So in this case, the session must
be saved anyway before sending the headers with the new session ID. Otherwise session
data could get lost again for concurrent requests with the new ID. One result could be
that you get logged out after just logging in.

This listener should be executed as one of the last listeners, so that previous listeners
can still operate on the open session. This prevents the overhead of restarting it.
Listeners after closing the session can still work with the session as usual because
 Symfonys session implementation starts the session on demand. So writing to it after
it is saved will just restart it.

Commits
-------

b7bfef0 [Kernel] ensure session is saved before sending response
@fabpot fabpot closed this as completed Nov 2, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests