_drupal_session_write() make a merge query in every request when there is an active session, even though $_SESSION has not changed during the request. This created unnecessary load on the database.

Also, if a user triggers two concurrent requests (e.g. when loading a page that includes an ImageCache image), there is a chance that one request finished first and leaves something in $_SESSION, while the second request does not change $_SESSION but still writes the value that was loaded at the beginning of the request and thus overwrites the session data saved by the first request.

With this patch, the session is only written if $_SESSION has changed during the request. The session timestamp is updated every per 180 seconds based on the same logic as used for updating {users}.access.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

wow - nice work. can't believe we didn't do this earlier. rtbc.

Damien Tournoud’s picture

This looks very nice. In most cases, we were already duplicating the session data memory (ie. once for $GLOBALS['user']->session, the second one for $_SESSION), so this doesn't actually increase memory usage.

catch’s picture

Category: feature » task

Really, really nice.

realityloop’s picture

session-write-1.patch queued for re-testing.

moshe weitzman’s picture

great - bot is still happy.

Dries’s picture

session-write-1.patch queued for re-testing.

aspilicious’s picture

Status: Reviewed & tested by the community » Needs work

Can you quickreroll and put a newline between @param and @return....
Srry that I'm so late with this.

moshe weitzman’s picture

Status: Needs work » Reviewed & tested by the community

If noone else gets to it, the committers can add the line break (or a follow-up). Lets try not to set important patches to needs work over a single doxygen line break (IMO). Bot is happy so lets take advantage.

aspilicious’s picture

FileSize
8.14 KB

Ok attached a new version of patch 1 with a cleanup of the file.
I didn't change anything of the original patch.

Dries or webchick: If you don't like this additional clean up in this patch just use the one in 1.

Dries’s picture

+++ includes/session.inc	26 May 2010 13:39:50 -0000
@@ -61,12 +61,22 @@
-function _drupal_session_read($sid) {
+function _drupal_session_read($sid, $return_last = FALSE) {

- I like this patch a lot, but I dislike the $return_last parameter that was added to _drupal_session_read(). I think it is cleaner to cache the old value elsewhere. For example, I think it would be cleaner if the $user object kept the original session data. That's how you'd do it in most other programming languages -- I don't think any method in the Java API has a 'return_last' parameter.

- Technically, the extra parameter can be omitted because _drupal_session_read() is only called once, as far as I can see.

- We have an existing pattern for flushing static caches in core. _drupal_session_read() introduces a static cache that cannot be flushed by the test bot. It introduces an inconsistency.

In other words, I'd like to see us ponder some more about the method signature and how we handle caching.

c960657’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
13.03 KB

I don't think it is reasonable to compare Drupal code to Java. In Java the variable would probably be saved in static class variable, but we don't have those in Drupal. Instead we ofte rely on function-static variables, e.g. menu_set_active_menu_names() that works like a getter when no argument is passed. But okay, it is an unusual pattern, so here is another approach:

This patch uses a drupal_static() variable as a shared variable between _drupal_session_read() and _drupal_session_write(). This doesn't follow the usual drupal_static() pattern and it is basically a global variable in disguise, but we do this at least one other place in core, i.e. where the drupal_http_headers static variable is shared between drupal_add_http_header() and drupal_get_http_header().

The patch includes the clean-up by aspilicious.

catch’s picture

fwiw drupal_static() sharing is also used in path.inc for system paths caching too.

catch’s picture

Status: Needs review » Reviewed & tested by the community
Dries’s picture

Status: Reviewed & tested by the community » Needs work

Needs a quick reroll. Sorry!

c960657’s picture

Status: Needs work » Needs review
FileSize
13.89 KB

Rerolled.

Damien Tournoud’s picture

+  // Store the user that was loaded for comparison in _drupal_session_write().
+  $last_user = &drupal_static('_drupal_session_read', NULL);
+  $last_user = $user;

I would prefer we store only what's really necessary from the $user object (ie. sid and session), and that we renable the '_drupal_session_read' name to something more meaningful ('drupal_last_loaded_session' ?).

c960657’s picture

FileSize
16.2 KB

Updated patch. This time it is a bit simpler and uses the same code path for the forced write (session has changed) and the timestamp update (180 seconds have passed). This preserves the sid/ssid logic during the timestamp update as well.

I realized that we also need to preserve {sessions}.timestamp for anonymous users, not only {sessions}.session. Currently the latter may be passed to drupal_anonymous_user(), but instead of passing both values I have removed the argument for drupal_anonymous_user() and instead set the two values directly in _drupal_session_read(). If this small API change is not acceptable at this point, the change to boostrap.inc can simply be omitted — but I don't see much use case for the argument outside a session handler.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

I read through the code and can find no issues. I see that Dries and damien's comments have been addressed. This is a huge performance win ... Please wait for green before commit.

klausi’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
10.84 KB
+++ includes/session.inc	14 Jun 2010 20:26:42 -0000
@@ -105,109 +106,133 @@ function _drupal_session_read($sid) {
+      // The "secure pages" setting allows a site to simultaneously use both secure
+      // and insecure session cookies. If enabled and both cookies are presented
+      // then use both keys. If not enabled but on HTTPS then use the PHP session
+      // id as 'ssid'. If on HTTP then use the PHP session id as 'sid'.

does not wrap at 80 characters anymore. Re-rolled patch fixes this, everything else is unchanged.

51 critical left. Go review some!

catch’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC.

grendzy’s picture

Issue tags: -Performance

#19: 744384-session-unchanged.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work
Issue tags: +Performance

The last submitted patch, 744384-session-unchanged.patch, failed testing.

moshe weitzman’s picture

Grr. This one sat for too long. I wonder why the patch went from 15kn to 10kb between 17 and 19. Anyone available to investigate and reroll?

c960657’s picture

Status: Needs work » Needs review
FileSize
15.12 KB

Reroll. The difference in size is just because I include more context in my diffs than klausi does.

moshe weitzman’s picture

Priority: Normal » Major
Status: Needs review » Reviewed & tested by the community

Thanks for the reroll. Back to RTBC.

sun’s picture

+++ includes/session.inc	20 Sep 2010 15:38:01 -0000
@@ -136,80 +150,90 @@ function _drupal_session_read($sid) {
+      if ($is_https) {
...
+        if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) {

It looks like we could move the variable_get() into the $is_https condition.

Powered by Dreditor.

c960657’s picture

I'm not sure what you mean? If we change if ($is_https) { to if ($is_https && variable_get('https', FALSE)) { we wouldn't execute the $key['ssid'] = $sid assignment when the https setting is false, but we need to (though I may not fully understand the sid/ssid thing).

moshe weitzman’s picture

Issue tags: -Performance

#24: session-write-13.patch queued for re-testing.

moshe weitzman’s picture

Issue tags: +Performance

#24: session-write-13.patch queued for re-testing.

webchick’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: +API change

Moshe pinged me about this again tonight.

It looks like Dries was poised to commit this back in June and got blocked by needing a re-roll, so I took a look tonight.

This is a nice performance improvement for sites using a caching server. There is an API change in this patch (it removes the ability to load an anon user by session ID in drupal_anonymous_user()) which we talked about at length on IRC. Moshe's stance is that while this will impact some contrib modules (e-commerce-related stuff, certain voting modules, etc.) there's a simple workaround (just look in $_SESSION the same as you would in a normal PHP script). I'm a little on the fence myself. But since this patch was originally intended to be committed 3-4 months ago by Dries, I decided to let it slide. Obviously if this ends up causing a lot of problems in contrib, we'll need to revisit this decision.

In the meantime, Committed to HEAD. Moshe said he'd follow-up with instructions for contrib modules who are affected for Randy.

moshe weitzman’s picture

This is just a scaling optimization. The API changes for sessions went in a long time ago. That change had us no longer automatically start a session for anonymous users. This is vital for reverse proxies like Varnish. The takeway for module devs is to minimize use of $_SESSION for anon users. If you want lazy sessions for D6, use Pressflow.

One change here which could affect a few modules (i can't think of any) is that the timestamps in the sessions table are no longer accurate up to the second. we now write only once every 3 mins by default. This matches how access timestamps in user table work.

rfay’s picture

Thanks very much for the info.

I'm not even sure if I understand a change (in this patch) large enough to announce. Just mention that

* Signature change for drupal_anonymous_user()
* $_SESSION is still there, but is not up-to-the-second

True?

catch’s picture

All the data in session will be up to the second, but the timestamp won't be.

This will slightly change the behaviour of the who's online block - specifically if it's set to less than 3 minutes online. That might be worth a followup, but since the session writing is a variable too, it might also just be worth ignoring.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

grendzy’s picture

Category: task » bug
Priority: Major » Normal
Status: Closed (fixed) » Needs review
Issue tags: +Quick fix
FileSize
883 bytes

I am getting this notice when writing tests for securepages:
Undefined property: stdClass::$timestamp Notice session.inc 176 _drupal_session_write()

It seems that the $user->timestamp is never initialized. From my testing, this notice always occurs - but it's not normally captured. I'm guessing it's evasiveness has something to do with _drupal_session_write() being a "magic" PHP callback, but I'm not sure.

grendzy’s picture

many thanks to catch for helping on IRC tonight. I think $is_changed is FALSE when logging in via HTTPS. If you run user.test on an https:// site, you can see the PHP notices. And this explains why the test bots haven't detected it.

grendzy’s picture

Status: Needs review » Closed (fixed)
Issue tags: -Quick fix

This is closely linked to a much more complex issue, so I'm just rolling a variation of this into #1050746: HTTPS sessions not working in all cases