cms icon indicating copy to clipboard operation
cms copied to clipboard

[4.x]: Craft doesn't purge stale user sessions from the sessions table in the database with Redis session component

Open dominikkrulak opened this issue 3 years ago • 4 comments

What happened?

Description

I use Redis session component. This issue relates to my answer at StackExchange How does Craft CMS handle user's session with Redis.

Here is excerpt from my question:

How user's session is destroyed When ->userSessionDuration(0), session.gc_maxlifetime = 120 and you quit browser. That is when session.gc_maxlifetime = 120 kicks in because there are no requests incoming to update times in both records and Craft removes a session record from Redis database only. This is unexpected behavior. A record in session table stays even if purgeStaleUserSessionDuration time is reached. I still don't know why "garbage collection" didn't kicked in.

Here's my testing configuration that does that:

config/general.php

// The amount of time before a user will get logged out due to inactivity
// Set to 0 if you want users to stay logged in as long as their browser is open
->userSessionDuration(0)

// The amount of time a user’s elevated session will last, 
// which is required for some sensitive actions. 
// 0 to disable elevated session.
->elevatedSessionDuration(0)

php.ini

; How long session cookie will be stored in browser. 0 until browser is restarted
session.cookie_lifetime = 0

; How long session data will be stored in Redis
session.gc_maxlifetime = 120

Steps to reproduce

I haven't tried it with default session component yet. (I've tried it and the same thing)

  1. Use Redis session component
  2. Configure your config/general.php file and php.ini file to values something like in my testing configuration
  3. Log in to the control panel and quit browser or remove CraftSessionId cookie. Assuming in normal UX a user wouldn't remove CraftSessionId cookie but quit a browser without logging out
  4. Redis key that contained session token is removed after 120 seconds but the session id row that contains invalid session token in session table is still there after purgeStaleUserSessionDuration time is reached which is 60 seconds in my testing environments -> CRAFT_PURGE_STALE_USER_SESSION_DURATION=60

Expected behavior

The row with not valid session token in session table to be removed

Actual behavior

The row with not valid session token in session table is intact

Craft CMS version

4.2.5.1

PHP version

8.1.10

Operating system and version

Debian 11

Database type and version

PostgreSQL 14.5.0 and Redis 7.0.4

Image driver and version

ImageMagick 6.9.11

Installed plugins and versions

  • mailgun 3.0.0

dominikkrulak avatar Oct 03 '22 15:10 dominikkrulak

Can you post how you are overriding the session component? (config/app.web.php)

brandonkelly avatar Oct 10 '22 18:10 brandonkelly

With settings above in my question and these configs I still don't get stale session purged after 60 seconds.

config/app.php file

<?php

use craft\helpers\App;

return [
    'id' => App::env('CRAFT_APP_ID') ?: 'Craft',
    'components' => [
        'redis' => [
            'class' => yii\redis\Connection::class,
            'hostname' => App::env('REDIS_HOSTNAME') ?: 'redis',
            'port' => App::env('REDIS_PORT') ?: 6379,
            'password' => App::env('REDIS_PASSWORD') ?: null,
            'database' => App::env('REDIS_CACHE_DB') ?: 0,
        ],
        'cache' => [
            'class' => yii\redis\Cache::class,
            'defaultDuration' => 86400,
            'keyPrefix' => App::env('CRAFT_APP_ID') ?: 'Craft',
        ],
        'queue' => [
            'proxyQueue' => [
                'class' => yii\queue\redis\Queue::class,
                'redis' => 'redis', // Redis connection component or its config
                'channel' => 'queue', // Queue channel key
            ],
        ],
        'mutex' => [
            'mutex' => yii\redis\Mutex::class,
        ],
    ],
];

config/app.web.php file

<?php

use craft\helpers\App;

return [
    'components' => [
        // See -> https://github.com/yiisoft/yii2-redis/blob/master/docs/guide/topics-session.md
        'session' => function() {
            // Get the default component config
            $config = App::sessionConfig();
            // Override the class to use 'Redis' session class
            $config['class'] = yii\redis\Session::class;
            // Set prefix for each record
            $config['keyPrefix'] = App::env('CRAFT_APP_ID') ?: 'Craft';
            // Instantiate and return it
            return Craft::createObject($config);
        },
    ],
];

Also seems like docs about the application configuration has been updated with different approach which I've followed it too and the issue still persists.

And also I made a video of my whole configuration with latest approach from documentation. Sorry for crappy video quality.

dominikkrulak avatar Oct 11 '22 09:10 dominikkrulak

@dominikkrulak have you found that this issue has been resolved in future updates to Craft 4 or are you still having the same issue?

zizther avatar Aug 10 '23 10:08 zizther

@zizther I haven't checked it yet on my end and I haven't found anything related to this issue in the changelog file.

dominikkrulak avatar Aug 10 '23 14:08 dominikkrulak

but the session id row that contains invalid session token in session table is still there after purgeStaleUserSessionDuration time is reached which is 60 seconds in my testing environments

It's important to remember that by nature, PHP is stateless and nothing is ever going to happen (like garbage collection) without a request triggering it. Once you close your browser, the timer starts on the purgeStaleUserSessionDuration value, but it wont be considered for deletion until then next request.

Furthermore, if garbage collection takes place on the subsequent request is subject to probability, more on that here.

If you run your same test, but after the 60 seconds/purgeStaleUserSessionDuration, try running the CLI command craft gc to force garbage collection. You should see the row in the sessions table removed.

So, from my perspective, this seems to be working as-expected, but please let me know if I'm missing something.


Likely unrelated, but it is highly recommended you use a different redis database for each of your redis components (especially cache). If you don't do this, redis cannot easily flush all values for a given component and has to resort to purging by key prefix.

timkelty avatar Mar 16 '24 02:03 timkelty