Sessions' annoyances

98 views
Skip to first unread message

Maniac

unread,
Nov 11, 2005, 4:48:36 PM11/11/05
to django-d...@googlegroups.com
Hello!

I recently discovered some annoyances in sessions middleware which I'd
like to fix. But first I prefer to here from you if this is desirable.

1. Session key is generated on save, not on creation.

This means that Django may expose the working and usable session to the
user which nonetheless has no key. I'm developing a site with a
basket-like functionality. I was using sessions to give each user his
basket. And basket was related to sessions by a ForeignKey:

class Basket(meta.Model):
session=meta.ForeignKey(core.Session)

So when new user (without a session cookie) makes a request I'm trying
to create a session and create a new Basket referencing to this new
session. Here's the trap: until the request ends session doesn't have
actual key and the Basket is created with ForeignKey set to None and
user won't see his Basket next time.

I see two solutions:
- generate new session key when processing request immediately if there
is no cookie
- turn creating a session into an explicit procedure in app's view:

if not request.session:
key=request.create_session

2. Session's lifetime is prolonged only when sessions is modified.
Imagine an online shop with sessions lifetime of two hours. User comes
to a site, a session is created. User then adds some goods to the basket
and before making a decision about actually submitting an order decides
to have a closer look at delivery and payment options. When browsing and
reading documents his basket is untouched and not modified. This can
easily take about two hours including interruptions for some irrelevant
incoming calls. And then his session expires. And basket is suddenly empty.

I mean, is there a reason to not update session's expiration time on
each request?

Adrian Holovaty

unread,
Nov 12, 2005, 2:28:55 PM11/12/05
to django-d...@googlegroups.com
On 11/11/05, Maniac <Man...@softwaremaniacs.org> wrote:
> 1. Session key is generated on save, not on creation.
> [...]
> So when new user (without a session cookie) makes a request I'm trying
> to create a session and create a new Basket referencing to this new
> session. Here's the trap: until the request ends session doesn't have
> actual key and the Basket is created with ForeignKey set to None and
> user won't see his Basket next time.

Thanks for bringing this up. Comments are below.

> I see two solutions:
> - generate new session key when processing request immediately if there
> is no cookie

I don't see this as a viable solution, because that would require a
database hit for every request that doesn't have a session -- which
isn't good for performance.

> - turn creating a session into an explicit procedure in app's view:
>
> if not request.session:
> key=request.create_session

I'm iffy on this one as well, but I can give it some more thought.

An easy solution to your problem would be to create the Basket object
for a request if the session is not empty and hasn't yet had a basket
created.

if request.session and not request.session.get('basket_created', False):
# create the basket object

> 2. Session's lifetime is prolonged only when sessions is modified.
> Imagine an online shop with sessions lifetime of two hours. User comes
> to a site, a session is created. User then adds some goods to the basket
> and before making a decision about actually submitting an order decides
> to have a closer look at delivery and payment options. When browsing and
> reading documents his basket is untouched and not modified. This can
> easily take about two hours including interruptions for some irrelevant
> incoming calls. And then his session expires. And basket is suddenly empty.
>
> I mean, is there a reason to not update session's expiration time on
> each request?

Good call; this is a valid concern. The reason to not update session's
expiration time on each request is that we don't want to hit the
database on every request; that's unnecessarily performance-heavy.
Maybe there's a happy medium: Only update the session's expiration
time if the expiration time is less than 10 minutes away? How does
that sound?

Adrian

--
Adrian Holovaty
holovaty.com | djangoproject.com | chicagocrime.org

Maniac

unread,
Nov 13, 2005, 7:49:59 AM11/13/05
to django-d...@googlegroups.com
Adrian Holovaty wrote:

>>I see two solutions:
>>- generate new session key when processing request immediately if there
>>is no cookie
>>
>>
>
>I don't see this as a viable solution, because that would require a
>database hit for every request that doesn't have a session -- which
>isn't good for performance.
>
>
Then it may be deffered to the time of lazy creation
(SessionWrapper._get_session). Because there is a db access anyway. But
this will fix the problem mentioned that a session even after writing
something to it doesn't have an identity.

>An easy solution to your problem would be to create the Basket object
>for a request if the session is not empty and hasn't yet had a basket
>created.
>
>if request.session and not request.session.get('basket_created', False):
> # create the basket object
>
>
I actually just have basket_id (or None) in a session :-). I'm merely
bringing this up not to fix my problem but to eliminate as many traps as
possible for beginners (being one myself).

>Good call; this is a valid concern. The reason to not update session's
>expiration time on each request is that we don't want to hit the
>database on every request; that's unnecessarily performance-heavy.
>
>
I thought that altering a single field found by index lookup wouldn't be
very slow... Also apps of this kind (which show a basket) tend to hit db
almost on each request anyway. I have spent last 2.5 years developing
such a beast and though we were trying to eliminate as many db access as
possible we never managed to drop it completely.

May be then create an option choosing one of the behaviors?
SESSION_RENEW_ON_READ (True/False) or less horrible name...

>Maybe there's a happy medium: Only update the session's expiration
>time if the expiration time is less than 10 minutes away? How does
>that sound?
>
>
While it reduces the probability that the basket will disappear under
user's nose the problem won't go away completely. Since if this time is
relatively small then user may hit server just 1 second before this time
'X' and his session will expire shortly anyway.

Kevin

unread,
Nov 14, 2005, 2:36:01 PM11/14/05
to Django developers
I want to second the idea of a config variable for saving the session
on each request. I got stuck on a bug where my sessions never seemed
to be saved. I'm creating an shopping engine in django, and need to
store a lot of data in a session, and so I wanted to partition the data
a bit for better managebility:

request.session['shopping_cart'] = ShoppingCart()
request.session['billing_info'] = BillingInfo()

and so on... I noticed that none of the later updates to the session
were ever remembered. After digging through the middleware code, I
realized that it sets a "modified" flag when any changes are made on
the session dictionary's keys. Well, those keys are all set when the
session is created, so changes to the sub-members didn't trigger that
modified flag.

I've been trying to think of a way of checking for any changes in the
structure will be saved, but dict's don't support hashing...

PS. As advice for the original question, my work around is to manually
set the request.session.modified when I've made a change to the
session, you could do the same to ensure the session does not expire.

Adrian Holovaty

unread,
Nov 20, 2005, 12:20:00 PM11/20/05
to django-d...@googlegroups.com
On 11/14/05, Kevin <kevin...@gmail.com> wrote:
> I want to second the idea of a config variable for saving the session
> on each request.

As of trunk revision 1303, there's a SESSION_SAVE_EVERY_REQUEST
setting. It's set to False by default. I've updated the docs:

http://www.djangoproject.com/documentation/sessions/#when-sessions-are-saved


> After digging through the middleware code, I
> realized that it sets a "modified" flag when any changes are made on
> the session dictionary's keys. Well, those keys are all set when the
> session is created, so changes to the sub-members didn't trigger that
> modified flag.
>
> I've been trying to think of a way of checking for any changes in the
> structure will be saved, but dict's don't support hashing...

This is an interesting gotcha, which I've documented. Did you ever
find a solution, or are you just going to use
SESSION_SAVE_EVERY_REQUEST?

Kevin

unread,
Nov 21, 2005, 3:02:14 PM11/21/05
to Django developers
I manually set the req.session.modified flag when I updated the value.
I've spent some time trying to think of an automatic way of determining
if the session was modified.

They key is save the state of the session at the beginning of the
request, and then compare it at the end to see if changes were made.

You should be able to use the copy.deepcopy method to save a "pristine"
version of the session, and then just do a comparison at the end. If
different, save to the db.

So, in _get_session(self), on an AttributeError, add
self._original_session = copy.deepcopy(self._session_cache)

And then in process_reponse(), instead of if(modified), do
if(request.session._original_session != request.session._session)


Of course, the downside to doing this is doubling the data and
performing a deepcopy on every request (at least every one that uses
the session). But I figure, ram speed is much faster than db speed, so
it might not be that significant. perhaps a run-time config
SESSION_TRACK_CHANGES or something.

Reply all
Reply to author
Forward
0 new messages