Skip to content
This repository

Enable simultaneous AJAX requests to work with sessions #1283

Closed
wants to merge 13 commits into from

12 participants

Ian Oberst Chris Berthe Sam Li TomKita Chris Gillis Carlos Ryan Lovelett David Parkinson Jordan Arseno 8snf Darren Hill CroNiX
Ian Oberst

This is in response to issue #154.

This update allows sessions to work gracefully with fast/simultaneous requests without needing to simply ignore updating the session. Users are allowed to have more than one valid session id at any given time, but only one is considered to be "active." The active session id is allowed to be updated (e.g. generate a new session id), but the original session id is left intact, marked as "inactive." Inactive sessions exist solely to make sure the requests that are created near the time a session is regenerated behave properly.

To facilitate this change an additional expiration time has been added, resulting in three "timeouts" for sessions:

  • Session Expiration: The point at which any request is destroyed
  • Time to Update: The point at which the active session id is regenerated
  • Multisession Expiration: The point which an inactive session is no longer valid

Here is an example of a session lifetime:

  • User interacts with the site for the first time, creating a new and active session, Session 1
  • After the Time to Update has passed, Session 1 is "updated." This process results in a new id being generated and associated with the new active session, Session 2. Session 1 is marked as inactive.
  • An AJAX request comes in with Session 1. Since it is marked as inactive, no update occurs, but the AJAX session completes because it has a valid session.
  • The multisession expiration passes
  • At this point, one of two final outcomes can happen:
    • Another AJAX request comes in with Session 1. Since the multisession expiration has passed, the session is destroyed and the AJAX request fails due to not having a valid session. (This is very unlikely)
    • The session expiration passes for Session 1. The session will be destroyed during the course of the normal cleanup processes for sessions. Any request coming in for that session id will fail.
Ian Oberst Enable simultaneous AJAX requests to work with sessions
Allows simultaneous requests to work with session without prevent those
sessions from being updated. Users are allowed to have more than one
session, but only one session is ever allowed to "update" its session
id, preventing unbounded growth of valid sessions.
9d39c01
Chris Berthe

Looks good - will have to test this.

Did you also want to add the two instance variables as config items in config/config.php?

CroNiX

Styleguide requires space after if()
if ($this->sess_use_multisessions)

Sam Li
samxli commented June 12, 2012

@Areson are there any DB structural changes needed for this new fix?

Ian Oberst
Areson commented June 12, 2012

Nope!

system/libraries/Session.php
((15 lines not shown))
  470
+				 * session id that can continue to update.
  471
+				 */ 
  472
+				$this->_get_multi_session($new_sessid);
  473
+				$_SESSION['prevent_update'] = 0;
  474
+				
  475
+				//Write the new session id to the database 
  476
+				$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $cookie_data));
  477
+				
  478
+				//Make sure the user data is copied over
  479
+				$this->sess_write();
  480
+				
  481
+				//Release the session lock for the new session
  482
+				session_write_close();
  483
+			}
  484
+			else {
  485
+				$this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid, 'prevent_update' => 1), array('session_id' => $old_sessid)));	
3
Sam Li
samxli added a note June 13, 2012

'prevent_update' column doesn't exist in the current session DB schema. This has caused some problems if using DB-enabled sessions. Please verify.

Ian Oberst
Areson added a note June 13, 2012

@samxli Thanks for pointing that out. I seem to recall that I intended this to be a drop-in fix with no DB changes. I'm willing to bet that I was pushing that extra column into the DB for my own debugging purposes. I'll double check and fix.

Ian Oberst
Areson added a note June 13, 2012

@samxli I was able to confirm that I had that column referenced for debugging. I've updated the code to remove it. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ian Oberst Removing debug DB reference
Removing a reference to a DB column that was added for debug-only
purposes.
da10bbc
Sam Li
samxli commented June 15, 2012

@Areson I used this code in one of our production sites for about a day and problems happened. Many users reported frequent session timeouts. We opened up about 5 dev machines to test with many different test user accounts and was able to get dropped sessions as well. We observed that once one user dropped, all users dropped too. Still not sure how to narrow down the problem yet. But here is our config.php code for sessions:

$config['sess_cookie_name']     = 'msession';
$config['sess_expiration']      = 10800;
$config['sess_expire_on_close'] = TRUE;
$config['sess_encrypt_cookie']  = TRUE;
$config['sess_use_database']    = TRUE;
$config['sess_table_name']      = 'my_sessions';
$config['sess_match_ip']        = FALSE;
$config['sess_match_useragent'] = FALSE;
$config['sess_time_to_update']  = 1200;
$config['sess_use_multisessions'] = TRUE;
$config['sess_multisession_expiration'] = 5;
Ian Oberst Multisession Expiration being bypassed
The comparison to invoke the multisession expiration had the wrong
comparison operator, which was causing older sessions to be kept. They
still were being prevented from updating, but they remained valid until
the session expiration was reached.
134c4fe
Ian Oberst
Areson commented June 15, 2012

@samxli I dug around and did find a bug with the $sess_multisession_expiration value which was preventing inactive sessions that were not longer eligible for being updated from being destroyed. This means that these sessions would have remained valid until the session expiration was reached, which could lead to some odd behavior if they were used as the active session, but this would only happen if the request that was responsible for updating the session information timed out, meaning that an updated cookie was never received. However, I'm not entirely convinced that this explains the behavior that you were seeing.

A couple of thoughts. The $sess_multisession_expiration value needs to be set to a large enough value to accomodate the longest running AJAX request that you can expect. If not, you will got users experiencing dropped sessions. Here's what that would look like.

The active session has been in existence for long enough that it needs to be updated. A long running AJAX request is made, and since it is the first request since the "time to update" threshold has passed it is the request that will return an updated cookie with the updated session information. The multisession expiration is set to X, but the long running request takes Y time to complete, where Y > X. Any additional requests that come in after X but before Y will still be using the older session information, which is old enough to be considered expired when compared to the multisession timeout. These requests will be destroyed, which could cause your users to be dropped.

I'd take a look at how long your requests are taking to complete and see if you are running into the scenario above. If that doesn't seem to be the case and the update I posted fixes the issues on the test servers, then I guess there is some scenario that I haven't thought of. I'd be curious to know what that was. Let me know how things turn out!

Sam Li
samxli commented June 22, 2012

@Areson Just tried out the new changes again and users were being kicked out after about 10 minutes. I have no idea how to recreate the issue in our test environment. In our test environment it seemed to have worked fine after your latest commit. But once in production, user sessions were dropping like flies. One thing I haven't tried but suspect might be a problem is that we actually have 2 sites that use the same session database table. One is our user-facing site, the other is for admin purposes. The admin site uses the stable Session library with no multisessions feature. It uses the same sess_cookie_name as well. Could this be a conflict? We haven't done testing yet to have both the admin and user site running at the same time. We can check next time. But wanted to let you know that our 2nd trial run on production failed again.

Here is our augmented config for the user-facing site:

$config['sess_cookie_name']             = 'msession';
$config['sess_expiration']              = 10800;
$config['sess_expire_on_close'] = TRUE;
$config['sess_encrypt_cookie']  = TRUE;
$config['sess_use_database']    = TRUE;
$config['sess_table_name']              = 'my_sessions';
$config['sess_match_ip']                = FALSE;
$config['sess_match_useragent'] = FALSE;
$config['sess_time_to_update']  = 1200;
$config['sess_use_multisessions'] = FALSE;
$config['sess_multisession_expiration'] = 10;

And here is the session config for the admin site:

$config['sess_cookie_name']             = 'msession';
$config['sess_expiration']              = 14400;
$config['sess_expire_on_close'] = TRUE;
$config['sess_encrypt_cookie']  = TRUE;
$config['sess_use_database']    = TRUE;
$config['sess_table_name']              = 'my_sessions';
$config['sess_match_ip']                = FALSE;
$config['sess_match_useragent'] = TRUE;
$config['sess_time_to_update']  = 300;


Ian Oberst
Areson commented June 23, 2012

@samxli I'll be out of town for the next week or so, but I'll setup some testing when I get back with the dual setup you have listed above and see what happens. Before the last commit I was noticing the dropping behavior you originally mentioned once the multisession expiration was reached, but the fix I put in seemed to correct it. I think it is certainly possible that the two separate configurations using the same database could cause issues.

TomKita

I've been having random log out issues on my website. I can log in sit on a page for a few mins and then change pages and be logged out. No ajax requests were sent. The problem is random and usually happens after a few mins my session expire time is set to 0;

I've found that !isset($_SESSION['prevent_update']) is the reason it happens what would cause this to be not set?

Ian Oberst
Areson commented July 10, 2012

@TomKita The $_SESSION['prevent_update'] value would become unset if the session was destroyed, either explicitly through the session class or through garbage collection. I've been thinking about this some more, and this may also be a factor in the issue that @samxli is experiencing.

The pull request uses the $_SESSION variable as a session-specific mutex. If you have multiple connections trying to access the same session data, only one is allowed to do so at a time, and the rest are blocked. The pull request uses the "prevent_update" flag to signify if a session is allowed to be updated, or can only be used to verify that the session is valid. By storing it in the $_SESSION variable I figured I could avoid race conditions by making sure that I could update the flag for a session before any other connections tried to access it.

Another reason for using the $_SESSION variable is that the "prevent_update" flag needs to be stored on the server exclusively. If it was stored on the client side, then a malicious client could change the flag and hijack a legitimate session by having an old session id.

The data for the session is by default stored in the same location and garbage collection is periodically run to delete old session data. In the pull request the session class modifies the lifetime of session information as to prevent it from being deleted too early, but this only applies when the session class is included. If anything else is being run on the server (such as sites that are sharing the same server) then whatever site/configuration has the shortest garbage collection time frame controls when garbage collection is run.

I had hoped to avoid needing to modify CI's database structure for sessions by putting that flag in the $_SESSION variable, but it looks as if my testing environment wasn't setup to check for some of the cases I mentioned above. In summary, the following conditions can allow the sessions to seemly expire randomly:

  • If the server has a single CI install
    • And portions of the site do not use the session class
    • And default session garbage collection time frame is less than the session expire setup in the CI config
  • If the server has multiple CI installs and there are different configurations for the session class, with both using multi-sessions
  • If the server has multiple CI installs and only one is using multi-sessions
  • If the server is shared with over many sites and the default garbage collection time frame is smaller than the CI session config's expiration time frame

My test setup is a dedicated server with a single site and all of the test pages use the session class, so I would not have seen this issue.

@TomKita - Is your website on a shared server, and what is the gc_maxlifetime value as indicated by php_info?

@samxli - What is the gc_maxlifetime value that you have?

Ian Oberst
Areson commented July 10, 2012

I'm thinking that there might be a way to move the "prevent_update" flag out of the $_SESSION variable without modifying the database structure, but it would require some fiddling with the user data stored in the session, and I'm not sure if that is a good idea. My though is that when the user data is parsed, we could check to see if a particular variable is set, and if it is, strip if out of the custom user data. This would prevent it from being seen from the user, but allow us to store the flag in the database.

That being said, if the user tries to use a variable with the same name then it would magically disappear when they tried to retrieve it. That could be side stepped by using a unique variable name, such as a GUID, but that feels a bit too hackish. It's probably the most direct route to fixing the issue I highlighted above, but I don't think it's the right direction long-term.

TomKita

Ahh yes my server is shared and its set to 1440 and yea I figured it is the garbage collection that is clearing session.

Sam Li
samxli commented July 10, 2012

my session.gc_maxlifetime = 1440

Chris Gillis

I have a "chat" page on my CI website. After running for 30 minutes or so the long poll ajax request always gets 302 redirect to auth/login (because $_SESSION['logged_in'] is no longer true). Is that something this patch is intended to fix?

I've tried the IS_AJAX fix thats so popular but it didn't work.

Ian Oberst
Areson commented July 13, 2012

@chrisgillis This patch was not designed to fix that issue but I suspect that it may work if you setup the session configuration correctly. You'd have to have the multi-session expiration value set to match the session expiration. That, along with a long session update period, would effectively allow you to have multiple session identifiers for a user. You may still run into issues depending on how long they keep the chat open, but it would probably resolve what you are seeing now.

That being said, there are a couple of issues we've run into with the patch that still need to be resolved (see the above posts), so you probably don't want to use it just yet.

Ian Oberst
Areson commented July 17, 2012

@TomKita @samxli I've got an update to the patch in the works that should (hopefully) remedy the issues that you both have pointed out. Unfortunately I did have to modify the database schema in order to make the changes work. I'm going to do a bit more testing on it and plan on having the changes committed in the next day or so.

Ian Oberst Store Multi-Session flag in database
Using native PHP session to store the multi-session "prevent_update"
flag caused problems on sites that shared a server with other sites or
contained other CI sites with different configurations or that did not
use sessions.

Corrected some issues with the native PHP sessions not being closed at
the proper time, which prevented other requests from being processed
until the entire request had completed.

Added updates to the sess_write method to prevent expired
multi-sessions from writing user data and setting and updated cookie
which would overwrite the current session id.
0a5a37f
Ian Oberst

These changes will require the addition of a "prevent_update" column to the session table. You can use the following table definition as a reference:

CREATE TABLE IF NOT EXISTS  `ci_sessions` (
    session_id varchar(40) DEFAULT '0' NOT NULL,
    ip_address varchar(45) DEFAULT '0' NOT NULL,
    user_agent varchar(120) NOT NULL,
    last_activity int(10) unsigned DEFAULT 0 NOT NULL,
    user_data text NOT NULL,
    prevent_update int(10) DEFAULT NULL,
    PRIMARY KEY (session_id),
    KEY `last_activity_idx` (`last_activity`)
);

Other notable changes:

  • Expired multi-session can no longer write to the database or set an updated cookie. This is to prevent the current session id from being overwritten by a cookie with an old session id.
  • A long-running session will no longer prevent other requests from being processed until it finishes
  • The session garbage collection was updated to cleanup old multi-sessions
Ian Oberst
Areson commented July 18, 2012

@samxli The update should resolve the issues being caused by the native PHP sessions. There is still some risk of session randomly expiring due to your configuration having a shared session table. Since you have different values for the session expiration between the two sites, you may find that your admin session get deleted by garbage collection when requests are made to the user site.

The same risk will pop up if you were to enable multi-sessions in two different configuration and use different multi-session expirations. Moral of the story: if you are going to have a shared session table, make sure your expirations values match.

Let me know if this resolves the issues you were having!

Sam Li
samxli commented July 19, 2012

@Areson Would it also help to have different cookie names? Right now the two apps share the same cookie names as well.

Ian Oberst
Areson commented July 19, 2012

@samxli Unfortunately, different cookie names won't help. The cookie name doesn't factor into the garbage collection code, which only looks at the last activity time on all of the sessions in the database. Any that are past the expiration set in the currently running configuration will get deleted, even if they were created using a different configuration. The only way to avoid this is to use different tables or to set the expiration values to be the same.

Carlos

Hi Areson,

I am testing multisessions because I have the same problem with a site with a lot of Ajax an CI, and I've been looking for a way to get rid of anoying missed session.

I was modifying sess_multisession_expiration at config.php file and nothing happened, so I checked Session.php and found a litle problem at line 79. Should replace sess_multiple_sesson_expiration by sess_multisession_expiration.
It doesn't match the name at config.php and Session class field.

While writing this, I tested again with your last version of the library, but got the same results. From three different browsers I started sending messages and viewing pages inside my website, and over two minutes later two sessions got lost and users were logged out. I'm at the starting point again. No luck.

Ian Oberst Corrected session configuration
Corrected configuration option 'sess_multisession_expiration' so that
uses the correct name.

Fixed indentation in several locations.
9eb2c7b
Ian Oberst

@charlieperez74 Can you post your session config? Thanks!

Carlos

Hi Areson,

Here's my session configuration from config.php

$config['sess_cookie_name'] = 'ci_session';
$config['sess_expiration'] = 0;
$config['sess_expire_on_close'] = TRUE;
$config['sess_encrypt_cookie'] = TRUE;
$config['sess_use_database'] = TRUE;
$config['sess_table_name'] = 'ci_sessions';
$config['sess_match_ip'] = TRUE;
$config['sess_match_useragent'] = TRUE;
$config['sess_time_to_update'] = 300;
$config['sess_use_multisessions'] = TRUE;
$config['sess_multisession_expiration'] = 10;

Ian Oberst Preventing updates from sending two cookies.
During session updates the browser was receiving two cookies.
c78769c
Ian Oberst

@charlieperez74 I haven't been able to reproduce the dropping, but I did notice that there was an issue with the browser receiving two cookies at times. It's possible that this may have caused the issue.

Carlos

I'm going to do the changes to avoid the two cookies problem and test again.

Thanks

Carlos

Did some tests and sessions keep dropping.

Ian Oberst Prevent old multisessions from overwriting current session
The initial behavior of this patch was to have expired multisession
generate a new cookie. The thinking was that once an old session came
in that we would want to ignore it and therefore generate a new cookie.
However, if a request had a significant delay, or if many requests were
coming to the server simultaneously, then from time to time an old
session would hit the server alongside the current session, which
caused a new cookie to be sent back to the client. This resulted in the
session being dropped.

To get around this, expired multi-sessions are no longer longer
forcibly deleted when they are encountered, and garbage collection now
deletes them only when they have passed the session expiration. We
cannot count on networks to behave nicely all the time and it is too
easy to cause dropped sessions by assuming that we won't see a
particular multisession again.

We still need to make sure that we don't allow expired multisessions to
be treated as the current session. To deal with this I've added a
function named "multisession_expired" which returns true if we are
dealing with an expired multisession. This allows us to test a cookie
to see if it is still valid. Note that once a multisession has expired,
we are not longer allowed to change any of the data associated with it.
19fb8b6
Ian Oberst

@charlieperez74 I was finally able to reproduce the sessions being dropped. It looks like the way I had things ordered would cause an expired multisession to drop a new cookie from time to time, which would wipe out the valid cookie. I had to tweak the behavior a bit to avoid this, and essentially I ended up not being able to destroy the expired multi-sessions, as this could lead to valid sessions being dropped.

To get around this I added a function named "multisession_expired." You can call this function to determine if the multisession has expired, and then take appropriate action if it has. I also modified the behavior of expired multisessions to prevent them from being updated, so attempts to set user or flash data will not do anything, and the expired multisession cannot create cookies of updates their information in the database.

Let me know if this resolves the issue you were having.

Carlos

Hi:

I tested the new changes and worked fine for about 10 minutes. I have a chat bot running in my site and session dropped at one Ajax poll request and chat disconnected for a user, but was not kicked off the site, only lost chat connection. When this happens with the new changes, user cannot leave site because is using an expired session and session data cannot be modified.

Carlos
Ian Oberst

@charlieperez74 The issue of what to do with an expired Ajax session is a bit of an open question. We don't want to just let an expired session act as a normal, as that opens up the risk of someone hijacking an old session. In theory, if the Ajax poll request was dropped, then a retry should be able to use the updated session id and continue as normal.

You mentioned that you have a 30-second poll interval, but you set the multi-session expiration at 10 seconds. This leaves a 20 second window during which your poll request could get picked up as an expired multi-session. I would try setting your multi-session expiration at something like 35 seconds to allow your polling to work after the session is updated.

Carlos

I will test setting multi-session-expiration to 35 secs, as you say. I did it once, but before my first post and last changes were not done to the library.

Anyway I will have to find the way to allow users to logout since I cannot modify the session data to clear user status.

I'll let you know if I have luck this time.

Thanks

Ian Oberst

As a more genera question for anyone that cares to chime in, should it expired multi-sessions be allowed to update their content? My thought was "no," the reason being that since we can check to see if a mult-session is expired that we can perform an action appropriate to the context. If it is an Ajax request, likely the request came in just before our session was updated, so retrying the request should pass along the valid session. If it is some other request and we need to log out, clear the session, etc, we could manually call "sess_create" to create a new, blank session.

Ian Oberst Clear multisession expiration on creating a new session
Allow an expired multi-session to call "sess_create" to create a new
(and empty) session. Cleared the expired flag for the multi-session so
that the newly created session could be immediately writable.
245bef5
Ryan Lovelett

+1 @Areson we have deployed this patch and it is working in our environment.

Ian Oberst

Sweet!

David Parkinson

+1.

Jordan Arseno

+1 works great.

Ian Oberst Signing off on the changes.
Signed-off-by: Ian Oberst <areson@gmail.com>
734f24d
8snf

+1 works great for now!

I was session kicked every 5min.

Thanks for investigating and solving it.

Darren Hill

Now the only trouble is that system/libraries/Session.php no longer exists on develop. It's a driver now, so I expect you're going to have to rework your changes against system/libraries/Session/Session.php.

Ian Oberst
David Parkinson

This is a great addition as i'm struggling with this in my app.

Ian Oberst

At this point we should probably have someone close this pull request as it is out of sync with the current develop branch. I'm working on porting this to the Session driver model that CodeIgniter now has, and will be opening a new pull request once I'm finished.

Ian Oberst

And...closing.

Ian Oberst Areson closed this October 16, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 13 unique commits by 1 author.

Apr 23, 2012
Ian Oberst Enable simultaneous AJAX requests to work with sessions
Allows simultaneous requests to work with session without prevent those
sessions from being updated. Users are allowed to have more than one
session, but only one session is ever allowed to "update" its session
id, preventing unbounded growth of valid sessions.
9d39c01
Ian Oberst Updating the default config to contain comments and default values fo…
…r multisessions.
da8a5a8
Ian Oberst Updating code to meet styling guidelines 5de3d9d
Jun 13, 2012
Ian Oberst Removing debug DB reference
Removing a reference to a DB column that was added for debug-only
purposes.
da10bbc
Jun 15, 2012
Ian Oberst Multisession Expiration being bypassed
The comparison to invoke the multisession expiration had the wrong
comparison operator, which was causing older sessions to be kept. They
still were being prevented from updating, but they remained valid until
the session expiration was reached.
134c4fe
Jul 18, 2012
Ian Oberst Store Multi-Session flag in database
Using native PHP session to store the multi-session "prevent_update"
flag caused problems on sites that shared a server with other sites or
contained other CI sites with different configurations or that did not
use sessions.

Corrected some issues with the native PHP sessions not being closed at
the proper time, which prevented other requests from being processed
until the entire request had completed.

Added updates to the sess_write method to prevent expired
multi-sessions from writing user data and setting and updated cookie
which would overwrite the current session id.
0a5a37f
Ian Oberst Correcting styling. 2865b62
Aug 03, 2012
Ian Oberst Minor Styling Corrections c478cb8
Aug 08, 2012
Ian Oberst Corrected session configuration
Corrected configuration option 'sess_multisession_expiration' so that
uses the correct name.

Fixed indentation in several locations.
9eb2c7b
Ian Oberst Preventing updates from sending two cookies.
During session updates the browser was receiving two cookies.
c78769c
Aug 09, 2012
Ian Oberst Prevent old multisessions from overwriting current session
The initial behavior of this patch was to have expired multisession
generate a new cookie. The thinking was that once an old session came
in that we would want to ignore it and therefore generate a new cookie.
However, if a request had a significant delay, or if many requests were
coming to the server simultaneously, then from time to time an old
session would hit the server alongside the current session, which
caused a new cookie to be sent back to the client. This resulted in the
session being dropped.

To get around this, expired multi-sessions are no longer longer
forcibly deleted when they are encountered, and garbage collection now
deletes them only when they have passed the session expiration. We
cannot count on networks to behave nicely all the time and it is too
easy to cause dropped sessions by assuming that we won't see a
particular multisession again.

We still need to make sure that we don't allow expired multisessions to
be treated as the current session. To deal with this I've added a
function named "multisession_expired" which returns true if we are
dealing with an expired multisession. This allows us to test a cookie
to see if it is still valid. Note that once a multisession has expired,
we are not longer allowed to change any of the data associated with it.
19fb8b6
Aug 10, 2012
Ian Oberst Clear multisession expiration on creating a new session
Allow an expired multi-session to call "sess_create" to create a new
(and empty) session. Cleared the expired flag for the multi-session so
that the newly created session could be immediately writable.
245bef5
Sep 13, 2012
Ian Oberst Signing off on the changes.
Signed-off-by: Ian Oberst <areson@gmail.com>
734f24d
This page is out of date. Refresh to see the latest.
6  application/config/config.php
@@ -276,6 +276,10 @@
276 276
 | 'sess_match_ip'			= Whether to match the user's IP address when reading the session data
277 277
 | 'sess_match_useragent'	= Whether to match the User Agent when reading the session data
278 278
 | 'sess_time_to_update'		= how many seconds between CI refreshing Session Information
  279
+| 'sess_use_multisessions'  = Enable multisessions for simultaneous requests. Using multisessions
  280
+|   requires 'sess_use_database' to be TRUE
  281
+| 'sess_multisession_expiration'	=  The number of SECONDS you want a multisession to be 
  282
+|   allowed to last for handling simultaneous requests
279 283
 |
280 284
 */
281 285
 $config['sess_cookie_name']		= 'ci_session';
@@ -287,6 +291,8 @@
287 291
 $config['sess_match_ip']		= FALSE;
288 292
 $config['sess_match_useragent']	= TRUE;
289 293
 $config['sess_time_to_update']	= 300;
  294
+$config['sess_use_multisessions'] = FALSE;
  295
+$config['sess_multisession_expiration'] = 10;
290 296
 
291 297
 /*
292 298
 |--------------------------------------------------------------------------
207  system/libraries/Session.php
@@ -44,6 +44,8 @@ class CI_Session {
44 44
 	public $sess_match_ip			= FALSE;
45 45
 	public $sess_match_useragent		= TRUE;
46 46
 	public $sess_cookie_name		= 'ci_session';
  47
+	public $sess_use_multisessions 		= FALSE;
  48
+	public $sess_multisession_expiration	= 10;
47 49
 	public $cookie_prefix			= '';
48 50
 	public $cookie_path			= '';
49 51
 	public $cookie_domain			= '';
@@ -57,6 +59,8 @@ class CI_Session {
57 59
 	public $userdata			= array();
58 60
 	public $CI;
59 61
 	public $now;
  62
+	private $prevent_update			= FALSE;
  63
+    private $multisession_expired   = FALSE;
60 64
 
61 65
 	/**
62 66
 	 * Session Constructor
@@ -73,7 +77,7 @@ public function __construct($params = array())
73 77
 
74 78
 		// Set all the session preferences, which can either be set
75 79
 		// manually via the $params array above or via the config file
76  
-		foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)
  80
+		foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key', 'sess_use_multisessions', 'sess_multisession_expiration') as $key)
77 81
 		{
78 82
 			$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
79 83
 		}
@@ -208,6 +212,13 @@ public function sess_read()
208 212
 		// Is there a corresponding session in the DB?
209 213
 		if ($this->sess_use_database === TRUE)
210 214
 		{
  215
+			// Are we using multi-sessions? If so, grab a lock on the session
  216
+			if ($this->sess_use_multisessions)
  217
+			{
  218
+				// Load the php session based on the current session id.
  219
+				$this->_get_multi_session($session['session_id']);
  220
+			}
  221
+            
211 222
 			$this->CI->db->where('session_id', $session['session_id']);
212 223
 
213 224
 			if ($this->sess_match_ip == TRUE)
@@ -226,6 +237,13 @@ public function sess_read()
226 237
 			if ($query->num_rows() === 0)
227 238
 			{
228 239
 				$this->sess_destroy();
  240
+                
  241
+				// Kill the multi-session we started
  242
+				if ($this->sess_use_multisessions)
  243
+				{
  244
+			    	session_destroy();
  245
+				}
  246
+                
229 247
 				return FALSE;
230 248
 			}
231 249
 
@@ -243,6 +261,24 @@ public function sess_read()
243 261
 					}
244 262
 				}
245 263
 			}
  264
+            
  265
+			// Are we in a multi-session scenario? If so, set whether the current
  266
+			// session id is allowed to be updated.
  267
+			if ($this->sess_use_multisessions)
  268
+			{
  269
+			 	$this->prevent_update = isset($row->prevent_update)?$row->prevent_update:NULL;
  270
+                
  271
+				// Check to see if this session doesn't exist (previously destroyed) 
  272
+				//  If so, kill it.
  273
+				if (is_null($this->prevent_update))
  274
+				{
  275
+					$this->sess_destroy();
  276
+					
  277
+					// Destroy the php session
  278
+					session_destroy();
  279
+					return FALSE;
  280
+				}
  281
+			}
246 282
 		}
247 283
 
248 284
 		// Session is valid!
@@ -261,6 +297,12 @@ public function sess_read()
261 297
 	 */
262 298
 	public function sess_write()
263 299
 	{
  300
+		//Is this an expired multisession? If so, prevent updates
  301
+		if($this->multisession_expired())
  302
+		{
  303
+	    	return;
  304
+		}
  305
+        
264 306
 		// Are we saving custom data to the DB?  If not, all we do is update the cookie
265 307
 		if ($this->sess_use_database === FALSE)
266 308
 		{
@@ -268,6 +310,13 @@ public function sess_write()
268 310
 			return;
269 311
 		}
270 312
 
  313
+		// If we have enabled multi-session and have one that
  314
+		// can no longer be updated, prevent the session write.
  315
+		if($this->sess_use_multisessions && $this->prevent_update)
  316
+		{
  317
+	    	return;
  318
+		}
  319
+
271 320
 		// set the custom userdata, the session data we will set in a second
272 321
 		$custom_userdata = $this->userdata;
273 322
 		$cookie_userdata = array();
@@ -296,7 +345,7 @@ public function sess_write()
296 345
 		// Run the update query
297 346
 		$this->CI->db->where('session_id', $this->userdata['session_id']);
298 347
 		$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
299  
-
  348
+    
300 349
 		// Write the cookie. Notice that we manually pass the cookie data array to the
301 350
 		// _set_cookie() function. Normally that function will store $this->userdata, but
302 351
 		// in this case that array contains custom data, which we do not want in the cookie.
@@ -323,17 +372,33 @@ public function sess_create()
323 372
 		$sessid .= $this->CI->input->ip_address();
324 373
 
325 374
 		$this->userdata = array(
326  
-					'session_id'	=> md5(uniqid($sessid, TRUE)),
327  
-					'ip_address'	=> $this->CI->input->ip_address(),
328  
-					'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),
329  
-					'last_activity'	=> $this->now,
330  
-					'user_data'	=> ''
  375
+					'session_id'		=> md5(uniqid($sessid, TRUE)),
  376
+					'ip_address'		=> $this->CI->input->ip_address(),
  377
+					'user_agent'		=> substr($this->CI->input->user_agent(), 0, 120),
  378
+					'last_activity'		=> $this->now,
  379
+					'prevent_update'	=> 0,
  380
+					'user_data'		=> ''
331 381
 				);
332 382
 
333 383
 		// Save the data to the DB if needed
334 384
 		if ($this->sess_use_database === TRUE)
335 385
 		{
336 386
 			$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
  387
+			
  388
+			//Are we using multiple sessions?
  389
+			if ($this->sess_use_multisessions)
  390
+			{
  391
+				/* Setup the php session to store information on whether
  392
+				 * or not the session can be updated
  393
+				 */
  394
+				$this->_get_multi_session($this->userdata['session_id']);
  395
+				$this->prevent_update = FALSE;
  396
+                $this->multisession_expired = FALSE;
  397
+                
  398
+        		unset($this->userdata['prevent_update']);
  399
+                
  400
+				session_write_close();  
  401
+			}
337 402
 		}
338 403
 
339 404
 		// Write the cookie
@@ -352,6 +417,28 @@ public function sess_update()
352 417
 		// We only update the session every five minutes by default
353 418
 		if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
354 419
 		{
  420
+			if ($this->sess_use_database && $this->sess_use_multisessions)
  421
+			{
  422
+				session_write_close();
  423
+			}
  424
+            
  425
+			return;
  426
+		}
  427
+
  428
+		// Check if this session is no longer allowed to update and has exired.
  429
+		// If so, flag it as expired so we can take action as appropriate.
  430
+		if ($this->prevent_update && ($this->userdata['last_activity'] + $this->sess_multisession_expiration) < $this->now)
  431
+		{
  432
+			$this->multisession_expired = TRUE;            
  433
+			
  434
+			session_write_close();
  435
+			return;
  436
+		}
  437
+
  438
+		// We only allow sessions to update if they are allowed
  439
+		if ($this->sess_use_database && $this->sess_use_multisessions && $this->prevent_update)
  440
+		{
  441
+			session_write_close();
355 442
 			return;
356 443
 		}
357 444
 
@@ -359,10 +446,10 @@ public function sess_update()
359 446
 		// by pushing all userdata to the cookie.
360 447
 		$cookie_data = NULL;
361 448
 
362  
-		/* Changing the session ID during an AJAX call causes problems,
363  
-		 * so we'll only update our last_activity
364  
-		 */
365  
-		if ($this->CI->input->is_ajax_request())
  449
+		// Changing the session ID during an AJAX call causes problems,
  450
+		// so we'll only update our last_activity, but only if we are not
  451
+		// using multiple sessions.
  452
+		if ($this->CI->input->is_ajax_request() && !$this->sess_use_multisessions)
366 453
 		{
367 454
 			$this->userdata['last_activity'] = $this->now;
368 455
 
@@ -411,7 +498,40 @@ public function sess_update()
411 498
 				$cookie_data[$val] = $this->userdata[$val];
412 499
 			}
413 500
 
414  
-			$this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
  501
+			//Are we allowing multiple sessions?
  502
+			if ($this->sess_use_multisessions)
  503
+			{
  504
+				//Set the session as no longer allowing updates
  505
+				$this->prevent_update = TRUE;
  506
+                
  507
+				//Update the current session
  508
+				$this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'prevent_update' => 1), array('session_id' => $old_sessid)));
  509
+                
  510
+				//Release the session lock so other requests can process
  511
+				session_write_close();
  512
+				
  513
+				/* Create a new entry for the updated session id. This will be the only
  514
+				 * session id that can continue to update.
  515
+				 */ 
  516
+				$this->_get_multi_session($new_sessid);
  517
+				$this->prevent_update = FALSE;
  518
+				
  519
+				//Write the new session id to the database 
  520
+				$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $cookie_data + array('prevent_update' => 0)));
  521
+				
  522
+				//Make sure the user data is copied over
  523
+				$this->sess_write();
  524
+				
  525
+				//Release the session lock for the new session
  526
+				session_write_close();
  527
+                
  528
+				//Return immediately, as sess_write wrote the cookie
  529
+				return;
  530
+			}
  531
+			else 
  532
+			{
  533
+				$this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));	
  534
+			}
415 535
 		}
416 536
 
417 537
 		// Write the cookie
@@ -445,6 +565,18 @@ public function sess_destroy()
445 565
 			);
446 566
 	}
447 567
 
  568
+    // --------------------------------------------------------------------
  569
+
  570
+    /**
  571
+     * Indicates if the session is an expired multi-session
  572
+     *
  573
+     * @return  boolean
  574
+     */
  575
+	public function multisession_expired()
  576
+	{
  577
+    	return ($this->sess_use_multisessions && $this->multisession_expired);
  578
+	}
  579
+
448 580
 	// --------------------------------------------------------------------
449 581
 
450 582
 	/**
@@ -504,6 +636,12 @@ public function all_flashdata()
504 636
 	 */
505 637
 	public function set_userdata($newdata = array(), $newval = '')
506 638
 	{
  639
+		//Is this an expired multisession? If so, prevent updates
  640
+		if($this->multisession_expired())
  641
+		{
  642
+			return;
  643
+		}
  644
+        
507 645
 		if (is_string($newdata))
508 646
 		{
509 647
 			$newdata = array($newdata => $newval);
@@ -529,6 +667,12 @@ public function set_userdata($newdata = array(), $newval = '')
529 667
 	 */
530 668
 	public function unset_userdata($newdata = array())
531 669
 	{
  670
+		//Is this an expired multisession? If so, prevent updates
  671
+		if($this->multisession_expired())
  672
+		{
  673
+			return;
  674
+		}
  675
+        
532 676
 		if (is_string($newdata))
533 677
 		{
534 678
 			$newdata = array($newdata => '');
@@ -557,6 +701,12 @@ public function unset_userdata($newdata = array())
557 701
 	 */
558 702
 	public function set_flashdata($newdata = array(), $newval = '')
559 703
 	{
  704
+		//Is this an expired multisession? If so, prevent updates
  705
+		if($this->multisession_expired())
  706
+		{
  707
+			return;
  708
+		}
  709
+        
560 710
 		if (is_string($newdata))
561 711
 		{
562 712
 			$newdata = array($newdata => $newval);
@@ -581,6 +731,12 @@ public function set_flashdata($newdata = array(), $newval = '')
581 731
 	 */
582 732
 	public function keep_flashdata($key)
583 733
 	{
  734
+		//Is this an expired multisession? If so, prevent updates
  735
+		if($this->multisession_expired())
  736
+		{
  737
+		return;
  738
+		}
  739
+    
584 740
 		// 'old' flashdata gets removed. Here we mark all
585 741
 		// flashdata as 'new' to preserve it from _flashdata_sweep()
586 742
 		// Note the function will return FALSE if the $key
@@ -813,6 +969,33 @@ protected function _sess_gc()
813 969
 		}
814 970
 	}
815 971
 
  972
+	/**
  973
+	 * Multi-Sessions Setup
  974
+	 * 
  975
+	 * Sets up a php session to handle a flag which
  976
+	 * indicates if a session id can update itself
  977
+	 * or not.
  978
+	 * 
  979
+	 * @param string
  980
+	 * @return void
  981
+	 */
  982
+	 protected function _get_multi_session($session_id)
  983
+	 {
  984
+		 /* This is a bit of a hack, but we need to pass around info on 
  985
+		 * if the current session can be updated or not. Starting a php
  986
+		 * session will effectively block all subsequent requests for the
  987
+		 * same session id so that we can prevent race conditions that might
  988
+		 * allow erroneous updates to the session id.
  989
+		 */
  990
+		 
  991
+		//Don't allow cookies for the php session
  992
+	 	ini_set('session.use_cookies', '0');
  993
+		ini_set('session.use_only_cookies', '0');
  994
+		
  995
+		//Start a session using our internally generated session id
  996
+		session_id($session_id);
  997
+		session_start();
  998
+	 }
816 999
 }
817 1000
 
818 1001
 /* End of file Session.php */
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.