How are expired codeigniter sessions cleaned up? - codeigniter

Experts,
I'm trying to understand how sessions are cleaned up in Codeigniter in case the app is configured to store sessions in a database table.
In my case, I have three expired sessions in that session table and one active one for the same user. The manual states:
"Note: The Session class has built-in garbage collection which clears out expired sessions so you do not need to write your own routine to do it."
Hmm, so when are my 'old' session in the db session table being cleared out or am I missing something?
thanks!

Here's the relevant source code of the Session class:
/**
* Garbage collection
*
* This deletes expired session rows from database
* if the probability percentage is met
*
* #access public
* #return void
*/
function _sess_gc()
{
if ($this->sess_use_database != TRUE)
{
return;
}
srand(time());
if ((rand() % 100) < $this->gc_probability)
{
$expire = $this->now - $this->sess_expiration;
$this->CI->db->where("last_activity < {$expire}");
$this->CI->db->delete($this->sess_table_name);
log_message('debug', 'Session garbage collection performed.');
}
}
This function is called in one place, in the constructor of the Session class (almost the last line), so once per request under normal circumstances.
$this->gc_probability is hardcoded to 5 at the top of the class and it doesn't appear to be possible to change it. I'm not sure, but I believe this means that 5% of the time (randomly) the garbage collection will run, clearing out old entries from the sessions DB table.
Note that these old entries are of no significance or harm, the cleanup is only done so your database table does not get overloaded with old, useless records.

Related

Session Expiry in Vapor 4

I am developing a web application with Vapor 4. It would be useful to persist client-made data on the server side for a few minutes at a time in between requests. I want to use sessions to do this. However, I am a bit confused on how the best way to automatically destroy this data after a set time. Should I make a job and have it check periodically? Or is there an easy way to set an expiry time on session creation?
I have used a bit of Middleware to achieve this for some months and it is very reliable.
It compares the timestamp now to the value from the immediate previous request. If the difference is greater than the allowed session timeout, it forces a logout.
I had to give a bit of thought to initialising the timestamp and "BAD" ensures a nil gets returned from trying to initialise a Double, which then gets the current timestamp to start the session 'timer'. I think this is safe as the user can't log in without having made at least one route call beforehand and I have other Middleware that checks to make sure the user is logged in. Try this:
struct SessionTimeoutMiddleware:Middleware
{
func respond(to request:Request, chainingTo next:Responder) -> EventLoopFuture<Response>
{
let lastRequestTimeStamp = Double(request.session.data["lastRequest"] ?? "BAD") ?? Date().timeIntervalSince1970
request.session.data["lastRequest"] = String(Date().timeIntervalSince1970)
if Date().timeIntervalSince1970 - lastRequestTimeStamp > 300.0 // seconds
{
request.auth.logout(User.self)
return request.eventLoop.makeSucceededFuture(request.redirect(to:"/somewhere/safe"))
}
return next.respond(to:request)
}
}
Then, register in configure.swift using:
let userAuthSessionsMW = User.authenticator()
let sessionTimeoutMW = SessionTimeoutMiddleware()
let timed = app.grouped(C.URI.Users).grouped(userAuthSessionsMW, sessionTimeoutMW)
try SecureRoutes(timed)

How to know or database record was deleted

I'm starting some php workers at the same time and each of them takes a job to do. These jobs are written in database table and when worker takes one - it deletes the record. My code:
$job = Job::first();
if (!empty($job) and $job->delete()==true) {
// so something
}
But the problem is that still some workers take the same $job to perform at the same time! How this can happen?
UPDATE
I'm using Postgres Database
Despite above comments seeking for a better solution, you should be able to solve it this way:
$job = Job::first();
if ($job && Job::where('id', $job->id)->delete()) {
// do something else ...
}
Explanation: Job::where('id', $job->id)->delete() will delete all job records with the given id and return the number of affected records. This may be either 0 or 1, or false and true respectively. So this should actually work, if your database handles the concurrent delete properly.
Very simple --- it is a race condition.
In order to avoid that you will need to implement some sort of database locking. You didn't indicate what database you were using, so I'm going to assume that you're using MySQL.
Your select statement needs to Lock the rows you've selected. In MySQL you do this with
DB::beginTransaction();
// Queries you need to make with eloquent
SELECT * FROM queue LIMIT 1 FOR UPDATE;
// use id if you got a row. If not just commit immediately.
DELETE FROM queue WHERE id = $id;
DB::commit();

Session variable on refresh

I have laravel controller like this:
public function postSessionTopic() {
$article_id = Input::get('article_id', 0);
$comment_id = Input::get('comment_id', 0);
\Session::set('page_topic_id', $article_id);
\Session::set('page_comment_id', $comment_id);
\\comment - I have tried \Session::put too, but that doesn't change anything
}
I use it, when user click on a article. I print_r out my session variable in this controller and everything looks fine. But after that I refresh my page, and there I read value from session, and sometimes it load old value or doesn't load anything. I can't understand why, because in controller i can see, that correct value is saved!
In my page, i get that value like this:
\Session::get('page_topic_id', 0)
Probably you do something wrong. You should make sure that in both cases you uses exactly same domain (with or without www).
In this controller when you don't have any input you set to session variables 0. This can also be an issue if you launch this method when you don't have any input.
You could try with adding this basic route:
Route::get('/session', function() {
$page_topic = Session::get('page_topic_id', 1);
$page_comment = Session::get('page_comment_id', 1);
echo $page_topic.' '.$page_comment.'<br />';
$article_id = $page_topic * 2;
$comment_id = $page_comment * 3;
Session::set('page_topic_id', $article_id);
Session::set('page_comment_id', $comment_id);
});
As you see it's working perfectly (but you need to remove session cookie before trying with this path).
You get
1 1
2 3
4 9
8 27
and so on. Everything as expected
Answer was - two ajax at one time. Don't do that, if you store something in session.
The session in Laravel doesn't consider changes permanent unless you generate a response (and that's the result of using symphony as it's base). So make sure your app->run() ends properly and returns a response before refreshing. Your problem is mostly caused by a die() method somewhere along your code or an unexpected exit of PHP instance/worker.
This is probably not your issue but if you are storing your laravel session in the database their is a limit on how large that value can be. The Laravel session migration has a field called "payload" that is a text type. If you exceed the limit on that field the entire session gets killed off. This was happening to me as I was dynamically adding json model data to my session.
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->unique();
$table->text('payload');
$table->integer('last_activity');
});
How much UTF-8 text fits in a MySQL "Text" field?

How to use Cache.getOrElse(java.lang.String key, java.util.concurrent.Callable<T> block, int expiration)

How to use Cache.getOrElse(java.lang.String key, java.util.concurrent.Callable block, int expiration)
Could someone give me a example?
My point is how to use “expiration",I know it means expire time.
By the way:
I want save some object to cache,and set a expire time.
when the expire time,I can reset the object to the cache.
Thanks.
Let's assume that, you want to set User object on cache, for that you set userId as key and user object as value. If need set expiration time, for sample i set it as 30secs.
cache.set(userId, userObject, 30);
At some point of time, if you want to get user object from cache, which you set earlier using userId as key, you might try the following way to get the user object from cache.
User user = cache.get(userId);
Above will return you the user object, if you access within 30secs, otherwise it will return NULL. This will be perfect for case like validating the session.
In some case, you frequently need to retrieve value from cache, for that following is the best approach.
User user = cache.getOrElse(userId, () -> User.get(userId), 30);
cache will check, whether it has given userId as key, if available then straight away return the user object and update the expiration time to 30secs further.
If given userId not available, then callable block gets invoked and set userId as key, user object fetched from db as value and expiration time as 30secs.
Expiration is the number of seconds that the Object would be hold in the Cache. If you pass 0 as expiration the Cache doesn't expire and you would have to control it by hand.
What getOrElse does is check the Cache, if the Object is not there then call the callable block that you are passing and adds the result to the cache for the number of seconds that you are passing as expiration time.
I based my comment in the Play Framework Cache Javadoc.
I use getOrElse in controllers when I have dynamic and static content to display. Cache the static and then render it together with the dynamic part:
try {
Html staticHtml = Cache.getOrElse("static-content", () -> staticView.render(), 60 * 60);
Html rendered = dynamicPage.render(arg1, arg2, staticHtml);
return ok(rendered);
} catch (Exception e) {
e.printStackTrace();
return internalServerError();
}
staticView.render() returns some html from a view. This view should not call any other pages which are dynamic or you stash something you do not really want to stash.
60*60 means I want to store it for one hour (60 seconds times 60 minutes... ok you can write 3600 if you want)
I should add that getOrElse gets the Object from the cache with the specified key (in this example the key is static-content) but if it cannot find it, then it calls the function which returns an object which is then stored for the specified amount of time in the cache with that key. Pretty neat.
Then you can call some other (dynamic) page and pass the html to it.
The dynamic stuff will stay dynamic :)

CodeIgniter: storing sessions in DB, how to know who session came from?

I'm thinking of storing CI sessions in the database so I can display how many users are currently online, who specifically is online, etc.
Looking at http://codeigniter.com/user_guide/libraries/sessions.html, am I right in my understanding that information would be stored in the user_data column of the ci_session table? Meaning, maybe I just store the user's id in there?
CodeIgniter will store the data in the table you specify in your config file. By default, it's ci_session. The session information, what is accessible through $_SESSION for instance, is serialized and saved in a column named user_data. That field will not be able to tell you whether or not the session has expired (or in other words, how many people are online).
What you could do instead is use the last_activity column, which is a timestamp of the last time that session was active. You could run a SQL query that selects the count of session_id where the last_activity is less than 2 minutes ago.
SELECT COUNT(`session_id`) AS `active_user_count` FROM `ci_session` WHERE `last_activity` >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 MINUTE)
With that said, an existing session doesn't necessarily mean that the user is "signed in". If you need to check that they're signed in, you can use a LIKE operator to add a condition to the WHERE statement that checks if a user is signed in. This will depend on what variable name you're using so have a look at your data and see if you can figure it out.
For example:
SELECT COUNT(`session_id`) AS `active_user_count` FROM `ci_session` WHERE `last_activity` >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 2 MINUTE) AND `user_data` LIKE '%s:9:"logged_in";b:1;%'
This works! use it
$session = $this->db->get('ci_sessions')->result_array();
foreach ($session as $sessions) {
$sessio = $sessions['last_activity'] + 7200;
echo $sessio . "time";
echo now();
echo "||";
$custom_data = $this->session->_unserialize($sessions['user_data']);
if (is_array($custom_data)) {
foreach ($custom_data as $key => $val) {
$user[$key] = $val;
}
}
}
print_r($user);
exit();`

Resources