Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
This patch is a big clean-up of the page cache process and session handling, inspired by #370454: Simplify page caching.
This patch:
- Adds a drupal_page_cacheable() method to determine (and force) page cacheability,
- Starts an output buffer unconditionally in DRUPAL_BOOTSTRAP_PAGE_CACHE, in order to simplify the page cache workflow,
- Streamlines the session management: because we have an output buffer enabled, we don't need the drupal_(get|set)_session() magic wrappers anymore. We simply start or cleanup the session on demand, at the end of the page workflow, in drupal_page_footer(),
- Rename and clean-up a few related functions, to get them a clearer name and a clearer responsibility.
Comment | File | Size | Author |
---|---|---|---|
#46 | 477944-follow-up.patch | 13.77 KB | Damien Tournoud |
#44 | 477944-follow-up.patch | 13.65 KB | Damien Tournoud |
#39 | 477944-follow-up.patch | 8.22 KB | Damien Tournoud |
#38 | 477944-follow-up.patch | 8.22 KB | Damien Tournoud |
#36 | 477944-follow-up.patch | 8.23 KB | Damien Tournoud |
Comments
Comment #1
Damien Tournoud CreditAttribution: Damien Tournoud commentedComment #2
Damien Tournoud CreditAttribution: Damien Tournoud commentedUsing drupal_static() instead of the static in drupal_page_cacheable(), thanks chx.
Comment #3
chx CreditAttribution: chx commentedI think this is very very nice. Biggest positives are the removal of drupal_set_session and the anonymous session destroy. Once this is in, we can get back to the page cache simplify which will be a LOT easier to accomplish after this.
Comment #4
c960657 CreditAttribution: c960657 commentedVery nice work.
uniqid() is slow, uniqid(TRUE) is much faster (see #251792: Implement a locking framework for long operations, comments #79 and #81).
The function name is drupal_session_commit().
AFAICT drupal_session_commit() can be written in a more compact and readable way with identical behaviour like this:
$user is not used in that function.
You might want to skip the whole if-block. It was introduces with this patch in #201122: Drupal should support disabling anonymous sessions. This pattern occurs several places.
Why are $session_count['anonymous'] and $session_count['authenticated'] stored in an array, when $anonymous and $authenticated are not? I suggest eliminating the array, unless there is a special reason for using it that I've missed.
During testing I got the following error, but now I cannot reproduce it, so it may be caused by something else: Fatal error: session_start(): Failed to initialize storage module: user (path: /tmp) in /home/chsc/www/drupal7/includes/session.inc on line 196
#0 drupal_session_start() called at [/home/chsc/www/drupal7/includes/session.inc:172]
#1 drupal_session_initialize() called at [/home/chsc/www/drupal7/includes/bootstrap.inc:1366]
#2 _drupal_bootstrap(4) called at [/home/chsc/www/drupal7/includes/bootstrap.inc:1302]
#3 drupal_bootstrap(9) called at [/home/chsc/www/drupal7/index.php:21]
I wonder whether it would be more efficient to replace
print drupal_render_page($return);
in index.php with$output = drupal_render_page($return);
and then pass that to drupal_page_footer(), and then addob_end_flush(); print $output
to the bottom of drupal_page_footer(). The whole page has already been generated in one string, and this change prevents the string from being put into the output buffer before being sent to the client. I haven't benchmarked it, though. The same approach can be used by drupal_serve_page_from_cache().Comment #5
Damien Tournoud CreditAttribution: Damien Tournoud commentedI saw this error message too (but wasn't able to reproduce it either), and it seems related to: PHP Bug #45363. My gut feeling is that moving the session_set_save_handler() just before session_start() should prevent the issue from happening.
Comment #6
Dries CreditAttribution: Dries commentedComment #7
Damien Tournoud CreditAttribution: Damien Tournoud commentedHere is a new version, taking care of c960657 remarks:
- use uniqid('', TRUE) instead of uniqid(), which is slower on some systems
- fix comments
- refactor drupal_session_commit() for clearness
- removed a useless $user
- revert useless isset() constructs all over the place
- remove the array in session.test
- move session_set_save_handler() just before session_start()
I haven't made the suggested change to how we handle output buffers. I don't think it would make a big difference, and it can wait for another patch!
Comment #9
Damien Tournoud CreditAttribution: Damien Tournoud commentedErm.
Calling session_set_save_handler() apparently resets the session handler state, for some (internal PHP) reasons. We cannot move session_set_save_handler() into drupal_session_start(), because we already called some session_* functions at this time. This patch should pass the test bot.
Comment #11
Damien Tournoud CreditAttribution: Damien Tournoud commentedI accidentally introduced a few E_ALL warnings. Lets fix those.
Comment #12
Dries CreditAttribution: Dries commentedI gave this patch another good look, and it is a very solid clean-up. Committed to CVS HEAD so we can keep making progress. Great work!
Comment #13
c960657 CreditAttribution: c960657 commentedI still get this error when logging out (using PHP 5.2.0):
Fatal error: session_start(): Failed to initialize storage module: user (path: /tmp) in /home/chsc/www/drupal7/includes/session.inc on line 196
Comment #14
JamesAn CreditAttribution: JamesAn commentedI can confirm the fatal error on a clean install of HEAD on a clean db.
For whatever reason, the only page that produces this error for me is the Clean URLs settings page. To produce:
And I get that php error.
Reverting to the June 2, 2009 revision (i.e., -D "2 June 2009") removes the problem.
Test server has been removed.
Since some people had problems reproducing the error, I've set up a test server for this:http://477944.jamesan.ca
User: 477944
Pass: 477944
There's a primary link to the Clean URLs settings page at the top-right corner of the page.
Comment #15
JamesAn CreditAttribution: JamesAn commentedI've pinpointed the problem to be something to do with assigning a value in to the $_SESSION in system_clean_url_settings():
$_SESSION['clean_url'] = TRUE;
.We can replace the use of $_SESSION['clean_url'] with the variable_get() and variable_set() with 'clean_url', which is already used to store whether clean urls are enabled.
The patch makes that change, although I don't think this fix addresses the real problem.
Comment #16
Damien Tournoud CreditAttribution: Damien Tournoud commentedThat sounds like a race condition somewhere. That would explain why it is hard to reproduce.
Comment #17
JamesAn CreditAttribution: JamesAn commentedHere's the PHP stack trace before the fatal error:
Comment #18
quicksketchIt's not hard to reproduce, it just so happens to happen *exactly once* on a fresh install. JamesAn nails it exactly in #14. You MUST start with a fresh install. Immediately after finishing the install, try to logout. Drupal throws the session error but does actually log you out. Refresh the page and see that you're now logged out. After the first time logging out, you can login/logout without any further trouble.
Comment #19
webchickI confirm I'm getting the same thing, somewhat randomly. quicksketch sees it too.
Info from XDebug which looks pretty identical to what JamesAn posted.
Comment #20
webchickOh, slow on the draw. :)
Comment #21
c960657 CreditAttribution: c960657 commentedWhat happens, I think, is this:
The test actually triggers the bug, but it isn't noticed because DrupalWebTestCase::drupalLogout() simply discards the output from user/logout.
This patch makes user_logout() reset $_SESSION. Also it was necessary to reset $this->loggedInUser when changing session cookies in order to prevent drupalLogin() from calling drupalLogout() first, even if the new session cookie did not represent a logged in user. The patch doesn't go further to workaround the PHP bug - I'm not sure whether this is necessary.
Comment #22
JamesAn CreditAttribution: JamesAn commentedThe problem seems fixed when I apply this patch. Hopefully testbot will come back with happy results.
Comment #23
Damien Tournoud CreditAttribution: Damien Tournoud commentedNice catch, c960657. I wasn't able to understand the scenario that made us start a session twice.
@JamesAn: does that solves the issue you were seeing on the clean-url page?
Comment #24
JamesAn CreditAttribution: JamesAn commented@Damien Tournoud: yes, indeed.
Comment #25
webchickAt the very least we should document why session_destroy() is not doing what it says on the tin. This part:
But it feels like we're only masking something deeper.
Comment #26
JamesAn CreditAttribution: JamesAn commentedDoing a bit of digging, apparently session_destroy() does not unset $_SESSION.
From http://ca.php.net/session_destroy
and again: Johan 20-Nov-2004 02:00
So we do need to manually clear $_SESSION like it's done in #21. We can't unset it "as this will disable the registering of session variables through the $_SESSION superglobal." (http://ca2.php.net/manual/en/function.session-unset.php)
Should we document that session_destroy() doesn't clear $_SESSION, since it's not obvious?
Comment #27
webchickOk, well then my question is, "Is this actually desirable?" :) Because that $_SESSION = array() line has not existed for the entire history of Drupal (or at least since 4.6) in that function. Why do we need to add it now?
Comment #28
Damien Tournoud CreditAttribution: Damien Tournoud commentedOk, apparently this is not very clear:
Because we don't want to restart a session in that case, the correct fix is indeed to nuke $_SESSION data.
Comment #29
JamesAn CreditAttribution: JamesAn commentedOops. Forgot that Drupal changes the session storage functions.
The bug seems to only rear its head when code manually sets $_SESSION keys, like the clean url settings page. Perhaps we haven't seen it before as modules have historically not fiddled around in $_SESSION, as $_SESSION values are transient and settings are permanently saved in the db.
Does this need to be backported?
Comment #30
Dries CreditAttribution: Dries commentedThis needs to be better documented in the code, IMO.
Comment #31
c960657 CreditAttribution: c960657 commentedAdded a comment above the resetting of $_SESSION.
Comment #32
JamesAn CreditAttribution: JamesAn commentedThe comment works for me. I think this patch is good to go since it's fixed the problem on my test.
Comment #33
Damien Tournoud CreditAttribution: Damien Tournoud commentedFurther clean-up (and an additional test): we don't want to start a session when drupal_save_session() is FALSE.
I moved cleaning $_SESSION and unsetting the cookie in _sess_destroy_sid(), where it makes more sense.
Comment #34
c960657 CreditAttribution: c960657 commentedThis looks good. I like that more logic is moved from the user module into the session handler.
Isn't this condition always TRUE? If yes, we might as well omit it. If not, a clarifying comment would be nice.
A left-over from debugging?
In drupal_session_commit(), should we - for symmetry - avoid destroying empty anonymous sessions, if drupal_save_session() is FALSE?
The word “restore” gives me the impression that $user was somehow reset and needs to be restored. Perhaps something like this instead:
Reset $_SESSION and $user to prevent a new session from being started in drupal_session_commit().
Now that you are touching this line, please change time() to REQUEST_TIME.
Comment #35
David StraussFor anyone on the infrastructure team looking at this issue, the only commit to HEAD referencing this issue is this:
http://vcs.fourkitchens.com/drupal/7-all-history/revision/10220
Does anyone know if any of the cleanup patches have made it in?
Comment #36
Damien Tournoud CreditAttribution: Damien Tournoud commentedHere is an updated patch.
No. This function can be called for a session ID that is not the one of the current user. Added a comment.
Removed.
Indeed. Fixed.
Ok.
Done.
Comment #37
David Strauss"Always use httponly cookies." is an inaccurate comment. That configuration only changes the HTTP-only status for *session* cookies.
"Destroy the current session, it will also reload the anonymous user." is a comma splice.
"Destroy the current session, it will also reset $user to the anonymous
+ // user." is also a comma splice.
Change both comma splice issues to "Destroy the current session and reset $user to the anonymous user."
Comment #38
Damien Tournoud CreditAttribution: Damien Tournoud commentedDone.
Comment #39
Damien Tournoud CreditAttribution: Damien Tournoud commentedBetter yet.
Comment #40
David StraussLooks good to me.
Comment #41
c960657 CreditAttribution: c960657 commentedWhen is the function called with a session other than the one of the current user? It isn't documented explicitly in the PHP manual for session_set_save_handler() that this wont happen, but _sess_read() and _sess_write() assume this (they use the global $user object). So I think _sess_destroy_sid() should make the same assumption, unless there is a reason not to do so.
(For further consistency, _sess_destroy_sid() should probably be renamed to just _sess_destroy(), and $key should be renamed to $sid a few places in session.inc, but we can do that in another issue)
I think the flow in drupal_session_commit() would be easier to follow if drupal_save_session() is called only once in the benning of the function, i.e.
if (!drupal_save_session()) { return; }
.+ // Reset $_SESSION and $user to prevent a new session from being started
+ // in drupal_session_commit()
Sentence does not end with a period.
Comment #42
TheRec CreditAttribution: TheRec commentedSubscribing. Berdir adviced me to look at this issue since this patch might also solve #500680: Fatal error on logout. I will test is whenever I find the time to do it.
Comment #43
TheRec CreditAttribution: TheRec commentedMarked #500680: Fatal error on logout as duplicate. The current issue will indeed solve this bug if the session data are destroyed when the session is destroyed. For the moment, I only tested Damien Tournoud's patch to see if it would solve the bug I spotted and it does solve it.
Comment #44
Damien Tournoud CreditAttribution: Damien Tournoud commentedHere are further cleanups following #41:
- _sess_*() are now renamed _drupal_session_*(). We don't do abbreviations, and those were hurting my eyes.
- $key is now consistently $sid
- _drupal_session_destroy() now assume that $sid is the one of the user (I agree with c960657 that it makes sense)
- refactored drupal_session_commit() code flow so that we only call drupal_save_session() once, and added a few comments to ease readability
Untested patch, but the test bot is there for us, right? ;)
Comment #46
Damien Tournoud CreditAttribution: Damien Tournoud commentedAnd a non broken one.
Comment #47
Dries CreditAttribution: Dries commentedTests pass and it looks like a fairly straight-forward but important clean-up. Committed.
Comment #49
sunThis is entirely undocumented. Worse, the upgrade guide still states drupal_set_session() in http://drupal.org/node/224333#drupal_set_session, which does not exist anymore.
I had to dig through 4-5 issues + look at each committed patch to find the one that is guilty.
Furthermore, the in-code API documentation is a bit sparse... At least drupal_session_start() should clarify Drupal's default session handling, IMHO. In addition to that, the first function that developers are looking at is drupal_session_initialize(), but that literally has zero documentation about what it does and what it doesn't do, neither any mentioning of drupal_session_start()...
Comment #50
sun.core CreditAttribution: sun.core commentedComment #51
jhodgdonchanging tagging scheme for update guide stuff.
sun: does this need other documentation work besides the update guide?
Comment #52
sunYes - at least I don't think that the new session handling with regard to anonymous users is documented anywhere.
The only documentation I was able to find is @file-level in session.inc:
http://api.drupal.org/api/drupal/includes--session.inc/7
Previously, all of this didn't require many details that one should be aware of. But starting with D7, it highly matters that developers know that putting stuff into $_SESSION has a large impact on performance, and they also need to know, how the emptiness of $_SESSION is evaluated.
Comment #53
jhodgdonOK. Adding back the generic "needs documentation" tag, since this needs regular doc as well as an entry in the update modules 67/ page.
Comment #54
ogi CreditAttribution: ogi commentedsubscribe
Comment #55
jhodgdonAn issue summary on this issue would surely help.... sigh.
So. It looks like patches were committed from #11
http://drupal.org/files/issues/477944-fix-streamline-session-page-cache_...
and #46
http://drupal.org/files/issues/477944-follow-up_4.patch
API changes I am seeing:
a) Function name changes - _sess_write() -> drupal_session_write() etc.
This is already documented on the update guide:
http://drupal.org/node/224333#session_rename
b) Some logic in drupal_session_write() moved into drupal_save_session() (to avoid saving anon sessions). I don't know that this particularly needs to be documented.
c) Session destroying is more careful (some things previously done in user_logout() are moved into session_destroy). Again, I'm not sure why we need to document this. There are a few other cases like (b) and (c) too that I don't think deserve a specific mention.
d) function page_get_cache -> drupal_page_get_cache. That needs to be documented on the 6/7 update guide.
e) function drupal_page_cache_header -> drupal_serve_page_from_cache(). That needs to be documented in the 6/7 update guide.
f) Various modules are using different keys in $_SESSIONS than they used to, but I don't think that's worth documenting in the module update guide probably?
So, the items that I think need mentioning in the update guide are (d) and (e). And also see comment #49 for another thing in the update guide that needs to be fixed.
As far as the other doc issues (issues with the API doc), I think we should file a separate issue rather than following up here. So, although I don't necessary understand much about sessions, I took the above comments and filed:
#1188574: session API function doc is lacking
Therefore, clearing the "needs doc" tag, since that is on its own issue now.
Comment #56
jhodgdonI've added doc of (d) and (e) to the 6/7 module page:
http://drupal.org/update/modules/6/7#page-cache-rename
Also I added comments to the 6/7 functions to link them to each other on api.drupal.org. I think this is sufficiently fixed now.
Comment #59
fgmI'm stumbling on a problem with the mandatory httpOnly cookie setting on D8 (accessing the session cookie from a JS-only app). This settings appears to have been added by Damien without further discussion : what was the reason behind forcing it instead of allowing it to be changed in the application settings , apart from the general observations as on https://www.owasp.org/index.php/HttpOnly ? This has been carried identically on D8.