Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Enable simultaneous AJAX requests to work with sessions #1283

Closed
wants to merge 13 commits into from
@Areson

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.
@Areson Areson 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
@chrisberthe

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)

@samxli

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

@Areson

Nope!

system/libraries/Session.php
((15 lines not shown))
+ * session id that can continue to update.
+ */
+ $this->_get_multi_session($new_sessid);
+ $_SESSION['prevent_update'] = 0;
+
+ //Write the new session id to the database
+ $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $cookie_data));
+
+ //Make sure the user data is copied over
+ $this->sess_write();
+
+ //Release the session lock for the new session
+ session_write_close();
+ }
+ else {
+ $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)));
@samxli
samxli added a note

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

@Areson
Areson added a note

@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.

@Areson
Areson added a note

@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
@Areson Areson Removing debug DB reference
Removing a reference to a DB column that was added for debug-only
purposes.
da10bbc
@samxli

@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;
@Areson Areson 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
@Areson

@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!

@samxli

@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;


@Areson

@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?

@Areson

@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?

@Areson

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.

@samxli

my session.gc_maxlifetime = 1440

@chrisgillis

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.

@Areson

@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.

@Areson

@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.

@Areson Areson 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
@Areson

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
@Areson

@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!

@samxli

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

@Areson

@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.

@charlieperez74

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.

@Areson Areson Corrected session configuration
Corrected configuration option 'sess_multisession_expiration' so that
uses the correct name.

Fixed indentation in several locations.
9eb2c7b
@Areson

@charlieperez74 Can you post your session config? Thanks!

@charlieperez74

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;

@Areson Areson Preventing updates from sending two cookies.
During session updates the browser was receiving two cookies.
c78769c
@Areson

@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.

@charlieperez74

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

Thanks

@charlieperez74

Did some tests and sessions keep dropping.

@Areson Areson 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
@Areson

@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.

@charlieperez74

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.

@charlieperez74
@Areson

@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.

@charlieperez74

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

@Areson

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.

@Areson Areson 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
@RLovelett

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

@Areson

Sweet!

@daparky

+1.

@dchill42 dchill42 referenced this pull request
Closed

Session Cookie Locking #1746

@jordanarseno

+1 works great.

@Areson Areson 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.

@dchill42

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.

@Areson
@daparky

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

@Areson

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.

@Areson

And...closing.

@Areson Areson closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 23, 2012
  1. @Areson

    Enable simultaneous AJAX requests to work with sessions

    Areson authored
    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.
  2. @Areson
  3. @Areson
Commits on Jun 13, 2012
  1. @Areson

    Removing debug DB reference

    Areson authored
    Removing a reference to a DB column that was added for debug-only
    purposes.
Commits on Jun 15, 2012
  1. @Areson

    Multisession Expiration being bypassed

    Areson authored
    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.
Commits on Jul 18, 2012
  1. @Areson

    Store Multi-Session flag in database

    Areson authored
    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.
  2. @Areson

    Correcting styling.

    Areson authored
Commits on Aug 3, 2012
  1. @Areson

    Minor Styling Corrections

    Areson authored
Commits on Aug 8, 2012
  1. @Areson

    Corrected session configuration

    Areson authored
    Corrected configuration option 'sess_multisession_expiration' so that
    uses the correct name.
    
    Fixed indentation in several locations.
  2. @Areson

    Preventing updates from sending two cookies.

    Areson authored
    During session updates the browser was receiving two cookies.
Commits on Aug 9, 2012
  1. @Areson

    Prevent old multisessions from overwriting current session

    Areson authored
    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.
Commits on Aug 10, 2012
  1. @Areson

    Clear multisession expiration on creating a new session

    Areson authored
    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.
Commits on Sep 13, 2012
  1. @Areson

    Signing off on the changes.

    Areson authored
    Signed-off-by: Ian Oberst <areson@gmail.com>
This page is out of date. Refresh to see the latest.
Showing with 201 additions and 12 deletions.
  1. +6 −0 application/config/config.php
  2. +195 −12 system/libraries/Session.php
View
6 application/config/config.php
@@ -276,6 +276,10 @@
| 'sess_match_ip' = Whether to match the user's IP address when reading the session data
| 'sess_match_useragent' = Whether to match the User Agent when reading the session data
| 'sess_time_to_update' = how many seconds between CI refreshing Session Information
+| 'sess_use_multisessions' = Enable multisessions for simultaneous requests. Using multisessions
+| requires 'sess_use_database' to be TRUE
+| 'sess_multisession_expiration' = The number of SECONDS you want a multisession to be
+| allowed to last for handling simultaneous requests
|
*/
$config['sess_cookie_name'] = 'ci_session';
@@ -287,6 +291,8 @@
$config['sess_match_ip'] = FALSE;
$config['sess_match_useragent'] = TRUE;
$config['sess_time_to_update'] = 300;
+$config['sess_use_multisessions'] = FALSE;
+$config['sess_multisession_expiration'] = 10;
/*
|--------------------------------------------------------------------------
View
207 system/libraries/Session.php
@@ -44,6 +44,8 @@ class CI_Session {
public $sess_match_ip = FALSE;
public $sess_match_useragent = TRUE;
public $sess_cookie_name = 'ci_session';
+ public $sess_use_multisessions = FALSE;
+ public $sess_multisession_expiration = 10;
public $cookie_prefix = '';
public $cookie_path = '';
public $cookie_domain = '';
@@ -57,6 +59,8 @@ class CI_Session {
public $userdata = array();
public $CI;
public $now;
+ private $prevent_update = FALSE;
+ private $multisession_expired = FALSE;
/**
* Session Constructor
@@ -73,7 +77,7 @@ public function __construct($params = array())
// Set all the session preferences, which can either be set
// manually via the $params array above or via the config file
- 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)
+ 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)
{
$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
}
@@ -208,6 +212,13 @@ public function sess_read()
// Is there a corresponding session in the DB?
if ($this->sess_use_database === TRUE)
{
+ // Are we using multi-sessions? If so, grab a lock on the session
+ if ($this->sess_use_multisessions)
+ {
+ // Load the php session based on the current session id.
+ $this->_get_multi_session($session['session_id']);
+ }
+
$this->CI->db->where('session_id', $session['session_id']);
if ($this->sess_match_ip == TRUE)
@@ -226,6 +237,13 @@ public function sess_read()
if ($query->num_rows() === 0)
{
$this->sess_destroy();
+
+ // Kill the multi-session we started
+ if ($this->sess_use_multisessions)
+ {
+ session_destroy();
+ }
+
return FALSE;
}
@@ -243,6 +261,24 @@ public function sess_read()
}
}
}
+
+ // Are we in a multi-session scenario? If so, set whether the current
+ // session id is allowed to be updated.
+ if ($this->sess_use_multisessions)
+ {
+ $this->prevent_update = isset($row->prevent_update)?$row->prevent_update:NULL;
+
+ // Check to see if this session doesn't exist (previously destroyed)
+ // If so, kill it.
+ if (is_null($this->prevent_update))
+ {
+ $this->sess_destroy();
+
+ // Destroy the php session
+ session_destroy();
+ return FALSE;
+ }
+ }
}
// Session is valid!
@@ -261,6 +297,12 @@ public function sess_read()
*/
public function sess_write()
{
+ //Is this an expired multisession? If so, prevent updates
+ if($this->multisession_expired())
+ {
+ return;
+ }
+
// Are we saving custom data to the DB? If not, all we do is update the cookie
if ($this->sess_use_database === FALSE)
{
@@ -268,6 +310,13 @@ public function sess_write()
return;
}
+ // If we have enabled multi-session and have one that
+ // can no longer be updated, prevent the session write.
+ if($this->sess_use_multisessions && $this->prevent_update)
+ {
+ return;
+ }
+
// set the custom userdata, the session data we will set in a second
$custom_userdata = $this->userdata;
$cookie_userdata = array();
@@ -296,7 +345,7 @@ public function sess_write()
// Run the update query
$this->CI->db->where('session_id', $this->userdata['session_id']);
$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
-
+
// Write the cookie. Notice that we manually pass the cookie data array to the
// _set_cookie() function. Normally that function will store $this->userdata, but
// in this case that array contains custom data, which we do not want in the cookie.
@@ -323,17 +372,33 @@ public function sess_create()
$sessid .= $this->CI->input->ip_address();
$this->userdata = array(
- 'session_id' => md5(uniqid($sessid, TRUE)),
- 'ip_address' => $this->CI->input->ip_address(),
- 'user_agent' => substr($this->CI->input->user_agent(), 0, 120),
- 'last_activity' => $this->now,
- 'user_data' => ''
+ 'session_id' => md5(uniqid($sessid, TRUE)),
+ 'ip_address' => $this->CI->input->ip_address(),
+ 'user_agent' => substr($this->CI->input->user_agent(), 0, 120),
+ 'last_activity' => $this->now,
+ 'prevent_update' => 0,
+ 'user_data' => ''
);
// Save the data to the DB if needed
if ($this->sess_use_database === TRUE)
{
$this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
+
+ //Are we using multiple sessions?
+ if ($this->sess_use_multisessions)
+ {
+ /* Setup the php session to store information on whether
+ * or not the session can be updated
+ */
+ $this->_get_multi_session($this->userdata['session_id']);
+ $this->prevent_update = FALSE;
+ $this->multisession_expired = FALSE;
+
+ unset($this->userdata['prevent_update']);
+
+ session_write_close();
+ }
}
// Write the cookie
@@ -352,6 +417,28 @@ public function sess_update()
// We only update the session every five minutes by default
if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
{
+ if ($this->sess_use_database && $this->sess_use_multisessions)
+ {
+ session_write_close();
+ }
+
+ return;
+ }
+
+ // Check if this session is no longer allowed to update and has exired.
+ // If so, flag it as expired so we can take action as appropriate.
+ if ($this->prevent_update && ($this->userdata['last_activity'] + $this->sess_multisession_expiration) < $this->now)
+ {
+ $this->multisession_expired = TRUE;
+
+ session_write_close();
+ return;
+ }
+
+ // We only allow sessions to update if they are allowed
+ if ($this->sess_use_database && $this->sess_use_multisessions && $this->prevent_update)
+ {
+ session_write_close();
return;
}
@@ -359,10 +446,10 @@ public function sess_update()
// by pushing all userdata to the cookie.
$cookie_data = NULL;
- /* Changing the session ID during an AJAX call causes problems,
- * so we'll only update our last_activity
- */
- if ($this->CI->input->is_ajax_request())
+ // Changing the session ID during an AJAX call causes problems,
+ // so we'll only update our last_activity, but only if we are not
+ // using multiple sessions.
+ if ($this->CI->input->is_ajax_request() && !$this->sess_use_multisessions)
{
$this->userdata['last_activity'] = $this->now;
@@ -411,7 +498,40 @@ public function sess_update()
$cookie_data[$val] = $this->userdata[$val];
}
- $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)));
+ //Are we allowing multiple sessions?
+ if ($this->sess_use_multisessions)
+ {
+ //Set the session as no longer allowing updates
+ $this->prevent_update = TRUE;
+
+ //Update the current session
+ $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)));
+
+ //Release the session lock so other requests can process
+ session_write_close();
+
+ /* Create a new entry for the updated session id. This will be the only
+ * session id that can continue to update.
+ */
+ $this->_get_multi_session($new_sessid);
+ $this->prevent_update = FALSE;
+
+ //Write the new session id to the database
+ $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $cookie_data + array('prevent_update' => 0)));
+
+ //Make sure the user data is copied over
+ $this->sess_write();
+
+ //Release the session lock for the new session
+ session_write_close();
+
+ //Return immediately, as sess_write wrote the cookie
+ return;
+ }
+ else
+ {
+ $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)));
+ }
}
// Write the cookie
@@ -445,6 +565,18 @@ public function sess_destroy()
);
}
+ // --------------------------------------------------------------------
+
+ /**
+ * Indicates if the session is an expired multi-session
+ *
+ * @return boolean
+ */
+ public function multisession_expired()
+ {
+ return ($this->sess_use_multisessions && $this->multisession_expired);
+ }
+
// --------------------------------------------------------------------
/**
@@ -504,6 +636,12 @@ public function all_flashdata()
*/
public function set_userdata($newdata = array(), $newval = '')
{
+ //Is this an expired multisession? If so, prevent updates
+ if($this->multisession_expired())
+ {
+ return;
+ }
+
if (is_string($newdata))
{
$newdata = array($newdata => $newval);
@@ -529,6 +667,12 @@ public function set_userdata($newdata = array(), $newval = '')
*/
public function unset_userdata($newdata = array())
{
+ //Is this an expired multisession? If so, prevent updates
+ if($this->multisession_expired())
+ {
+ return;
+ }
+
if (is_string($newdata))
{
$newdata = array($newdata => '');
@@ -557,6 +701,12 @@ public function unset_userdata($newdata = array())
*/
public function set_flashdata($newdata = array(), $newval = '')
{
+ //Is this an expired multisession? If so, prevent updates
+ if($this->multisession_expired())
+ {
+ return;
+ }
+
if (is_string($newdata))
{
$newdata = array($newdata => $newval);
@@ -581,6 +731,12 @@ public function set_flashdata($newdata = array(), $newval = '')
*/
public function keep_flashdata($key)
{
+ //Is this an expired multisession? If so, prevent updates
+ if($this->multisession_expired())
+ {
+ return;
+ }
+
// 'old' flashdata gets removed. Here we mark all
// flashdata as 'new' to preserve it from _flashdata_sweep()
// Note the function will return FALSE if the $key
@@ -813,6 +969,33 @@ protected function _sess_gc()
}
}
+ /**
+ * Multi-Sessions Setup
+ *
+ * Sets up a php session to handle a flag which
+ * indicates if a session id can update itself
+ * or not.
+ *
+ * @param string
+ * @return void
+ */
+ protected function _get_multi_session($session_id)
+ {
+ /* This is a bit of a hack, but we need to pass around info on
+ * if the current session can be updated or not. Starting a php
+ * session will effectively block all subsequent requests for the
+ * same session id so that we can prevent race conditions that might
+ * allow erroneous updates to the session id.
+ */
+
+ //Don't allow cookies for the php session
+ ini_set('session.use_cookies', '0');
+ ini_set('session.use_only_cookies', '0');
+
+ //Start a session using our internally generated session id
+ session_id($session_id);
+ session_start();
+ }
}
/* End of file Session.php */
Something went wrong with that request. Please try again.