Django bug that should be addressed: Idle timeouts do not clear session information

1,080 views
Skip to first unread message

Huuuze

unread,
Mar 16, 2009, 5:46:11 PM3/16/09
to Django users
I opened the following ticket which was unceremoniously closed by a
committer:

http://code.djangoproject.com/ticket/10518

Here is the text from the ticket:
>> I have set the SESSION_COOKIE_AGE value in my settings.py file to expire sessions after 1 hour. Django successfully logs the user out of the session, however, the backend does not behave as one would expect in this situation. If a user logged out under normal conditions (i.e., clicks a "Logout" link), the session information is cleared from the "django.sessions" table. As such, I would expect an idle timeout (which is just a timed logout) to behave in the same manner. Unfortunately, Django simply creates a new session entry in the "django.sessions" table and the old, expired session remains in the table. The end result is a bloated "django.sessions" table that needs to be maintained through an external script.

The reason for closing the ticket was the following:

>> This is the documented behavior. See http://docs.djangoproject.com/en/dev/topics/http/sessions/#clearing-the-session-table

And my response:

>> I completely disagree with this assessment. Just because it's "documented behavior" doesn't make it correct.

>> Django terminates the session based upon the expiring cookie. As such, the timeout process should call "django.contrib.auth.logout", which clears out records from the django.sessions table.

>> How is the process of idling out any different from the user explicitly clicking a logout link? One is an implicit logout, whereas the other is an explicit logout. At the end of the day, its the same net result -- a user's session has ended. This behavior should be fixed.

Does anyone else agree with my viewpoints on this matter? If so,
please post your comments in the ticket. IMO, this is a bug in Django.

Jacob Kaplan-Moss

unread,
Mar 16, 2009, 6:38:05 PM3/16/09
to django...@googlegroups.com
On Mon, Mar 16, 2009 at 4:46 PM, Huuuze <huu...@ymail.com> wrote:
> Does anyone else agree with my viewpoints on this matter?  If so,
> please post your comments in the ticket.

Actually, the right way to get your viewpoint heard is to take the
matter to the django-developers mailing list, where topics related to
Django's development are discussed. You'll have more luck posting
suggestions and criticism there than here or on the ticket tracker.

However, please keep in mind that we're currently running up to Django
1.1, so it's likely that anything that's not an outright bug might be
left by the wayside while we close bugs for the final release. If you
don't get an immediate response, be patient and wait until a bit after
the release when we all have a bit more time.

Jacob

Jeff FW

unread,
Mar 16, 2009, 8:49:58 PM3/16/09
to Django users
It's not a bug. When a cookie expires, the browser stops sending it
with its requests--therefore, there is *no* way for Django to know
that the cookie (and therefore, the session) has expired. There is no
"timeout" happening on the server side, so the session can't get
cleared out. Hence, why the documented method for clearing out old
sessions.

Maybe you're used to something like PHP's behavior, which cleans old
old sessions automatically. However, it only does this by deciding to
clear out the old sessions (by default) 1 out of every 100 requests--
which is kind of a nasty thing to do that 100th person.

-Jeff

On Mar 16, 6:38 pm, Jacob Kaplan-Moss <jacob.kaplanm...@gmail.com>
wrote:

Huuuze

unread,
Mar 16, 2009, 8:56:45 PM3/16/09
to Django users
Jeff (and Jacob)...

I appreciate your responses and I stand corrected. With that being
said, are either of you (or anyone reading this) aware of a method
that would allow me to track idle session timeouts? I'd like to audit
when a user has been logged out due to a timeout.

Huuuze

Alex Gaynor

unread,
Mar 16, 2009, 9:00:56 PM3/16/09
to django...@googlegroups.com
One possibility would be to use 2 cookies, one the normal session cookie, and a second with a *way* longer expire date.  Then when a user has the long one but not the other one clear out the corresponding session(which you'd keep track of obviously).

Alex

--
"I disapprove of what you say, but I will defend to the death your right to say it." --Voltaire
"The people's good is the highest law."--Cicero

Jeff FW

unread,
Mar 16, 2009, 9:04:34 PM3/16/09
to Django users
When you say "audit"--what do you mean? By that, I mean, what do you
plan to do with the data? Do you need to know the second someone
times out, or can you check later?

If you need to know immediately, I think you may need to do something
terrible with JavaScript. If not, or you can at least wait a little
while--run a cron job (every minute, if you'd like) that finds all of
the sessions that are past their expiration date. You can log them as
you'd like, and then clear them out.

-Jeff

Paulo Köch

unread,
Mar 16, 2009, 9:12:00 PM3/16/09
to django...@googlegroups.com
A background sleeper (as opposed to worker) that would fire a signal
upon timer expiration seems like a nice mechanism.

Cheers,
Paulo Köch

Paulo Köch

unread,
Mar 16, 2009, 9:13:24 PM3/16/09
to django...@googlegroups.com
And it's clearly a feature request. Bug implies defect in
implementation, not formulation.

Cheers.

Huuuze

unread,
Mar 16, 2009, 10:21:34 PM3/16/09
to Django users
Care to elaborate with some sample code?

On Mar 16, 9:12 pm, Paulo Köch <paulo.k...@gmail.com> wrote:
> A background sleeper (as opposed to worker) that would fire a signal
> upon timer expiration seems like a nice mechanism.
>
> Cheers,
> Paulo Köch
>

Paulo Köch

unread,
Mar 17, 2009, 4:23:06 PM3/17/09
to django...@googlegroups.com
Taking the lead from
http://www.artfulcode.net/articles/threading-django/ I would sugest
you use a Timer object
(http://www.python.org/doc/2.5.2/lib/timer-objects.html).

DISCLAIMER: Careful! I haven't tested this! A Timer object uses a
thread internally. Be careful with synchronization and related stuff.

HTH.
Cheers,
Paulo Köch

Huuuze

unread,
Mar 17, 2009, 4:28:51 PM3/17/09
to Django users
Paulo, thank you for the link, but I don't see how that will help. To
help articulate the problem, here is a post I included on
StackOverflow.com:

>> I would like to audit when a user has experienced an idle timeout in my Django application. In other words, if the user's session cookie's expiration date exceeds the SESSION_COOKIE_AGE found in settings.py, the user is redirected to the login page. When that occurs, an audit should also occur.

>> Currently, I have configured some middleware to capture these events. Unfortunately, Django generates a new cookie when the user is redirected to the login page, so I cannot determine if the user was taken to the login page via an idle timeout or some other event.

>> From what I can tell, I would need to work with the "django_session" table. However, the records in this table cannot be associated with that user because the sessionid value in the cookie is reset when the redirect occurs.

>> I'm guessing I'm not the first to encounter this dilemma. Does anyone have insight into how to resolve the problem?


On Mar 16, 6:38 pm, Jacob Kaplan-Moss <jacob.kaplanm...@gmail.com>
wrote:

Paulo Köch

unread,
Mar 17, 2009, 7:26:41 PM3/17/09
to django...@googlegroups.com
My previous idea was to maintain a Timer pool based on session ids.
Every time a request with a session hits your server, you reset the
Timer object associated with it. If a user logs out, you cancel the
respective timer. If a timer triggers, it's a timeout.

But, we can try a simpler approach. Assuming you don't need realtime
audit logs, can you exploit the expired sessions not being
automatically cleaned up?

Cheers,
Paulo Köch

Malcolm Tredinnick

unread,
Mar 17, 2009, 7:37:13 PM3/17/09
to django...@googlegroups.com
On Tue, 2009-03-17 at 23:26 +0000, Paulo Köch wrote:
> My previous idea was to maintain a Timer pool based on session ids.
> Every time a request with a session hits your server, you reset the
> Timer object associated with it. If a user logs out, you cancel the
> respective timer. If a timer triggers, it's a timeout.
>
> But, we can try a simpler approach. Assuming you don't need realtime
> audit logs, can you exploit the expired sessions not being
> automatically cleaned up?

Both of these solutions have the same showstopping flaw: There's no way,
on the server side, to associate a timed-out user with their previous
session. Because the web browser doesn't send the session cookie. This
whole "request for an enhancement" is based on wanting Django to act on
information it cannot possibly know about in the first place and your
solution suffers from the same problem.

The only way to work around something like this is to have "never
expire" sessions and manually manage the timing out on the server side.

Regards,
Malcolm


Paulo Köch

unread,
Mar 17, 2009, 8:55:42 PM3/17/09
to django...@googlegroups.com
Coder speaks louder. HTH.

#Pseudo code. Not tested nor proven.
class TimeoutTimerPool(object):
"""A pool that tracks a set of users and their active session.
Tipically used as a singleton."""
from threading import Timer
def __init__(self):
self.pool = {}
def expiration_handler(self, session_id, user_id):
#Take action with session_id and/or user_id here
pass
def register_user_presence(self, session_id, user_id):
"""Tracks user activity/presence. No magic mojo here, must be
called manually (or integrated in some middleware)."""
# Timers can be reset. So, we just dispose the current and create another.
self.unregister_session(session_id)
self.pool[session_id] = Timer(SESSION_TIMEOUT_OR_SOMETHING,
self.expiration_handler, session_id, user_id)
self.pool[session_id].start()
def untrack_session(self, session_id):
"""Forfeits user tracking. For logouts and such."""
if self.pool.haskey(session_id):
current_timer = self.pool[session_id]
if current_timer.isAlive():
current_time.cancel()
# Don't kow if this is the right way to dispose a Timer
del self.pool[session_id]

Cheers,
Paulo Köch

Paulo Köch

unread,
Mar 17, 2009, 8:59:26 PM3/17/09
to django...@googlegroups.com
Argh! Code in emails sucks. Besides, there was a typo in the previous
email. See http://dpaste.com/15848/

Cheers,
Paulo Köch

Malcolm Tredinnick

unread,
Mar 17, 2009, 9:06:27 PM3/17/09
to django...@googlegroups.com
On Wed, 2009-03-18 at 00:55 +0000, Paulo Köch wrote:
> Coder speaks louder. HTH.
>
> #Pseudo code. Not tested nor proven.
> class TimeoutTimerPool(object):
> """A pool that tracks a set of users and their active session.
> Tipically used as a singleton."""
> from threading import Timer
> def __init__(self):
> self.pool = {}

I'm hesitant to rain on your parade here, as you've put some effort into
writing a bunch of code. But the approach is flawed. As soon as you have
multiple processes involved (which is going to be normal), this
in-memory storage won't work. There's no guarantee that the same session
will go to the same process (not to mention that the processes will stop
and start over time).

If you're going to do something like this, it has to be external from
the web-process lifecycle and a centralised point that each process
talks to. Some kind of server daemon.

At that point it becomes equivalent to just looking in the sessions
table for sessions that have already expired (which is a simple
queryset). (the server daemon in question is the database server, so the
pattern holds).

I also don't think you're solving the original problem here (detecting
when a user who is coming back has an expired session, rather than never
been logged in) and detecting when sessions have expired can already be
done by looking at the database table.

Regards,
Malcolm

Paulo Köch

unread,
Mar 17, 2009, 9:17:42 PM3/17/09
to django...@googlegroups.com
On Wed, Mar 18, 2009 at 01:06, Malcolm Tredinnick
<mal...@pointy-stick.com> wrote:
> There's no guarantee that the same session
> will go to the same process (not to mention that the processes will stop
> and start over time).
>
> If you're going to do something like this, it has to be external from
> the web-process lifecycle and a centralised point that each process
> talks to. Some kind of server daemon.

Doh, missed that! This would only work in a worker model, restricting
deploy strategies. You're 100% correct. Some elaborate solution would
be required.


> At that point it becomes equivalent to just looking in the sessions
> table for sessions that have already expired (which is a simple
> queryset). (the server daemon in question is the database server, so the
> pattern holds).
>
> I also don't think you're solving the original problem here (detecting
> when a user who is coming back has an expired session, rather than never
> been logged in) and detecting when sessions have expired can already be
> done by looking at the database table.

Based on this, I can't see why a simple custom cron job inspecting the
pickled session data (assuming the user_id is in the session) before
purging old session would not suffice. Care to elaborate?

Cheers.

Malcolm Tredinnick

unread,
Mar 17, 2009, 9:23:51 PM3/17/09
to django...@googlegroups.com
On Wed, 2009-03-18 at 01:17 +0000, Paulo Köch wrote:
[...]

> Based on this, I can't see why a simple custom cron job inspecting the
> pickled session data (assuming the user_id is in the session) before
> purging old session would not suffice. Care to elaborate?

I think you've arrived back at the point of what Django already
provides, which is why the original ticket was closed.

You run "django-admin.py cleanup" in a cronjob and all the old sessions
are removed. You don't need to check the user ids or anything like that.
Expired sessions are expired, no matter who they belonged to.

Calling logout(), as the original poster requested doesn't achieve
anything (it does nothing). If it did do something, it would still be a
bad idea to call it, because the user could have already logged in again
and logging them out would be unfortunate.

So, yes, the cronjob is the solution. Which is why it's been in Django
for the last four years. :-)

Regards,
Malcolm

Paulo Köch

unread,
Mar 17, 2009, 9:28:46 PM3/17/09
to django...@googlegroups.com
> Calling logout(), as the original poster requested doesn't achieve
> anything (it does nothing). If it did do something, it would still be a
> bad idea to call it, because the user could have already logged in again
> and logging them out would be unfortunate.

Doesn't this generate a new session_id?

Cheers,
Paulo Köch

Huuuze

unread,
Mar 17, 2009, 9:32:21 PM3/17/09
to Django users
Yes, it does generate new session_id. The user has essentially told
Django to expire the session, which in turn results in a new session
key.

I'm starting to agree with Malcolm. Outside of an elaborate solution,
this one may not be possible. As soon as the session expires, Django
updates the records in the django.sessions table. As such, the only
unique identifier for the user, "session_key", is overwritten.

If there is some straightforward way to intercept the call to update
that table at the time of the automated logout, I'm all ears.

Malcolm Tredinnick

unread,
Mar 17, 2009, 9:35:37 PM3/17/09
to django...@googlegroups.com

More importantly it sets the user's status to be logged out. If they had
logged in again since their previous session expired, you have now just
logged them out again. In the web business we call that "not friendly".

Malcolm


Malcolm Tredinnick

unread,
Mar 17, 2009, 9:38:43 PM3/17/09
to django...@googlegroups.com
On Tue, 2009-03-17 at 18:32 -0700, Huuuze wrote:
[...]

> As soon as the session expires, Django
> updates the records in the django.sessions table. As such, the only
> unique identifier for the user, "session_key", is overwritten.

This isn't what happens when a session expires at all. Django doesn't
know that the session has expired because all it sees is a user arriving
without a session cookie (the cookie has expired, so the browser doesn't
send it). Thus, the user looks like a brand new user at that point.

So far this fact (that the cookie is not sent) has been mentioned three
or four times in this thread. You really have to start believing us.

> If there is some straightforward way to intercept the call to update
> that table at the time of the automated logout, I'm all ears.

There is no automated logout, either. There is only explicit logout
prior to a session being expired.

Regards,
Malcolm

Huuuze

unread,
Mar 17, 2009, 9:55:14 PM3/17/09
to Django users
Malcolm, I believe you and appreciate your advice, but you need to
ease up. You're getting hung up on semantics. In this instance, I'm
simply differentiating between a user clicking a link that says
"Logout" (a.k.a, a manual logout) versus Django detecting the lack of
a session cookie and redirecting the user to a login page (a.k.a., an
automated logout). From the user's perspective, they have been
automatically logged out.

With that out of the way, let's wrap this up. Please correct me if
I'm incorrect in this psuedo-code description of the manual login/
automated logout process:

1. User access Django-based website.
2. Django generates a session cookie with an expiration date based
upon SESSION_COOKIE_AGE. (In this example, it's set to 3600)
3. User logs in
4. User traverses website for one hour (3600 seconds)
5. Browser removes expired cookie
6. User attempts to click new link in Django-based website
7. Django detects the missing cookie
8. Django redirects user to login page
9. Django generates session cookie, inserts a new record into
django.sessions, and leaves old session information in django.sessions
table

The problem I'm trying to solve at this point is to slip in a call to
an audit method between Steps 6 and 8. As soon as Django realizes the
user's session is gone, I'd like to audit the "idle logout" (again,
this is from the user's perspective). By "audit", I mean store a
database record in my person.audit table with the user's user ID and a
message noting their session has expired.

And I agree with you: I don't think this can be done and you (and
others) have provided enough explanation to convince me that there is
no simple solution. I just wanted to make sure we're all on the same
page with respect to the problem I'm trying to resolve.


On Mar 17, 9:35 pm, Malcolm Tredinnick <malc...@pointy-stick.com>
wrote:

Malcolm Tredinnick

unread,
Mar 17, 2009, 10:14:31 PM3/17/09
to django...@googlegroups.com
On Tue, 2009-03-17 at 18:55 -0700, Huuuze wrote:
> Malcolm, I believe you and appreciate your advice, but you need to
> ease up. You're getting hung up on semantics. In this instance, I'm
> simply differentiating between a user clicking a link that says
> "Logout" (a.k.a, a manual logout) versus Django detecting the lack of
> a session cookie and redirecting the user to a login page (a.k.a., an
> automated logout). From the user's perspective, they have been
> automatically logged out.

The semantics are very important. We've been discussing the problem from
the server side where there is no concept of automatic logout. Using the
wrong words confuses things and there was enough confusion when you
started this thread that being precise is necessary. We aren't
discussing what the user sees here and there's never been any
misunderstanding as to what problem you've been trying to solve.

> 1. User access Django-based website.
> 2. Django generates a session cookie with an expiration date based
> upon SESSION_COOKIE_AGE. (In this example, it's set to 3600)
> 3. User logs in
> 4. User traverses website for one hour (3600 seconds)
> 5. Browser removes expired cookie
> 6. User attempts to click new link in Django-based website
> 7. Django detects the missing cookie
> 8. Django redirects user to login page
> 9. Django generates session cookie, inserts a new record into
> django.sessions, and leaves old session information in django.sessions
> table

Yes, this is correct and, yes, it's impossible to do anything special at
step 7 because there's absolutely no difference between the expired
session and the new user or somebody who hasn't visited for the past two
years.

Regards,
Malcolm


Jacob Kaplan-Moss

unread,
Mar 17, 2009, 10:45:15 PM3/17/09
to django...@googlegroups.com
On Tue, Mar 17, 2009 at 8:55 PM, Huuuze <huu...@ymail.com> wrote:
> 7.  Django detects the missing cookie

I think this is where you're getting hung up. Django doesn't "detect"
a "missing" cookie; Django sees a request from a browser that doesn't
include a cookie. Nothing's missing; it's just a new browser without a
cookie.

To Django, there's *no difference* between a user browsing a site
until his cookie expires then coming back, and the same user browsing
until the cookie expires and then a *second* user visiting without a
cookie. There's no "I used to have a cookie but now I don't" header;
there's either a session cookie, or there isn't.

Huuuze, I can appreciate that this is an infuriating aspect of HTTP --
statelessness is a real bitch sometimes. But you need to accept that
you're asking the impossible here and move on.

Jacob

huu...@gmail.com

unread,
Mar 18, 2009, 12:13:47 AM3/18/09
to Django users
On Mar 17, 10:45 pm, Jacob Kaplan-Moss <jacob.kaplanm...@gmail.com>
wrote:
Jacob, as you can see in my previous post (where I stated "And I agree
with you: I don't think this can be done..."), I have moved on. There
is no need for the condescending tone in your previous post and lest
we forget this is a *support* group. I do understand the limitations
and, in the case of my description of Step 7, I simply chose the wrong
words. Regardless, thank you again for assisting and setting me on
the right path.

>
> Jacob

Jeff FW

unread,
Mar 18, 2009, 6:20:15 PM3/18/09
to Django users
My original suggestion would still work--run a cron job every minute
(or so) that finds all of the sessions that have an expiration date
since the last time the script was run--delete those, then log an
entry in your audit table. Seems pretty simple to me, and I can't see
any reason why it wouldn't work.

Also, to back up Malcom, Jacob, and everyone else that's been trying
to help you--you *have* been a bit difficult, and the wording you use
*is* very important. You attract more flies with honey--not that
anyone here is a fly.

-Jeff
Reply all
Reply to author
Forward
0 new messages