There seems to still be a lot of older CI versions in use and I wanted to add my two cents, even though this thread is old. I just spent a few days solving the problem of AJAX calls in Code Igniter and I have a solution that covers the main issues, although some of the solution isn't 'wonderful'. The CI version that I am ( still ) using is 2.1.3
My application requires that AJAX calls update the last_activity field to maintain a valid session, so it is not good enough for me to simply abandon updating the session on AJAX calls.
The error checking for sess_update and sess_read are inadequate in this CI version ( I have not investigated more recent versions ) and a lot of the problems start there.
Part one: sess_update()
Multiple AJAX calls create race conditions which result in a locked the database for the later calls. If we try to run an update query but the database is locked, we get an error, the query returns false, but the cookie is still updated with new data?... BAD! Also, we don't need a new session_id for every Ajax call. We only need to update last_activity. Try this:
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)
{
return;
}
// Save the old session id so we know which record to
// update in the database if we need it
$old_sessid = $this->userdata['session_id'];
//Assume this is an AJAX call... keep the same session_id
$new_sessid = $old_sessid;
if( !$this->CI->input->is_ajax_request() ){
//Then create a new session id
while (strlen($new_sessid) < 32)
{
$new_sessid .= mt_rand(0, mt_getrandmax());
}
// To make the session ID even more secure we'll combine it with the user's IP
$new_sessid .= $this->CI->input->ip_address();
// Turn it into a hash
$new_sessid = md5(uniqid($new_sessid, TRUE));
}
// _set_cookie() will handle this for us if we aren't using database sessions
// by pushing all userdata to the cookie.
$cookie_data = NULL;
// Update the session ID and last_activity field in the DB if needed
if ($this->sess_use_database === TRUE)
{
//TRY THE QUERY FIRST!
//Multiple simultaneous AJAX calls will not be able to update because the Database will be locked. ( Race Conditions )
//Besides... We don't want to update the cookie if the database didn't update
$query = $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)));
if( $query ){
// Update the session data in the session data array
$this->userdata['session_id'] = $new_sessid;
$this->userdata['last_activity'] = $this->now;
// set cookie explicitly to only have our session data
$cookie_data = array();
foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
{
$cookie_data[$val] = $this->userdata[$val];
}
// Write the cookie
$this->_set_cookie($cookie_data);
}else{
//do nothing... we don't care, we still have an active retreivable session and the update didn't work
//debug: error_log( "ERROR::" . $this->CI->db->_error_message() ); //Shows locked session database
}
}else{
// Update the session data in the session data array
$this->userdata['session_id'] = $new_sessid;
$this->userdata['last_activity'] = $this->now;
// Write the cookie
$this->_set_cookie($cookie_data);
}
}
part 2: sess_read()
Very similar problem here... The database is sometimes locked during a query. Except we can't ignore the errors this time. We are trying to read the session to see if it exists... so if we get a locked database error, we can check for the error and try again ( a couple times if need be ). In my testing I never made it more than 2 tries in ). Also, I don't know about you, but I don't want php to fail on a fatal error by not checking for a false query result. You'll need this at the top of the session.php file if you want to try this code directly:
var $sess_query_attempts = 5;
Also note, this isn't the entire sess_read
function
$query = $this->CI->db->get($this->sess_table_name);
//Multiple AJAX calls checking
//But adding add a loop to check a couple more times has stopped premature session breaking
$counter = 0;
while( !$query && $counter < $this->sess_query_attempts ){
usleep(100000);//wait a tenth of a second
$this->CI->db->where('session_id', $session['session_id']);
if ($this->sess_match_ip == TRUE)
{
$this->CI->db->where('ip_address', $session['ip_address']);
}
if ($this->sess_match_useragent == TRUE)
{
$this->CI->db->where('user_agent', $session['user_agent']);
}
$query = $this->CI->db->get($this->sess_table_name);
$counter++;
}
if ( !$query || $query->num_rows() == 0)
{
$this->CI->db->where('session_id', $session['session_id']);
$query = $this->CI->db->get( $this->sess_table_name );
$this->sess_destroy();
return FALSE;
}
Anyway, imho there isn't a complete answer to this problem out there and I felt like I should share my findings with those who may still be experiencing early session timeouts on sites which use tons of AJAX like mine.