Angus Lees wrote:
At Tue, 13 Jul 2004 12:30:42 +0200, RobertCZ  wrote:
  
Gerald Richter wrote:
    
 I tried to play with it and it seems that Postgres as a store does
SELECT ... FOR UPDATE,

To me it looks like only one Apache child is working and other are
just waiting for session commit.
        
Mmmh, yes this seems to be the case.

I am not sure how select for update works, for the other storages the
locking works the way, that multiple pages can read at the same time, only
if one page writes it tries to get an exclusive lock. So just do write only
at the end of the page, normaly solves this locking problem.
      

If thats what every other storage does, then every other storage has a
pretty severe race condition.  This is fine if the data you are
storing in %mdat (or %udat) isn't dependent on what the previous %mdat
data was (eg: storing "the last visitor" is ok, a page counter is not ok).

The problem is that perl cannot know when fetching the session data,
whether you are intending to modify it or not.  It seems the Postgres
backend is taking the conservative approach, unlike every other
Apache::Session store (and against the behaviour described in the
Apache::Session docs).
  

Well, Apache::Session docs say:

If you retrieve an existing session, Session immediately restores the object from storage [....] also obtains an non-exclusive lock on the session.
...

By default, most Apache::Session implementations only do locking to prevent data corruption. The locking scheme does not provide transactional consistency, such as you might get from a relational database. If you desire transactional consistency, you must provide the Transaction argument with a true value when you tie the session hash. For example:

 tie %s, 'Apache::Session::File', $id {
    Directory     => '/tmp/sessions',
    LockDirectory => '/var/lock/sessions',
    Transaction   => 1
 };

Note that the Transaction argument has no practical effect on the MySQL and Postgres implementations. The MySQL implementation only supports exclusive locking, and the Postgres implementation uses the transaction features of that database.


I believe that using simple read locks is plain stupid for %mdat because it works in most simple cases only, eg your 'last-visitor' example. Even in %udat you can get quite easy two childs writing to the same session, it is enough if user just clicks faster then backend processes requests (think of page with frames or long processing or SOAP etc). And when some updates are lost and the last child wins, your application is f* up.

I propose

- use write lock for %udat and %mdat as DEFAULT ( Transaction => 1 ) and saying that very loudly in the docs
- reverse %mdat back to the per-module behaviour or remove it completely
- maybe add %adat for application data that would be just a simple alias to some per-app global hash (and warn %adat will not survive apache restart, unlike udat/mdat)

Gerald?


PS. When I try to empty %mdat, maybe in base.html like
[-
   delete $mdat{$_} foreach keys %mdat;
   %mdat = ();
-]
it dumps empty session as expected. Now I comment out those delete
lines and reload and %mdat has the same content as before delete.
What's wrong?
        

Deleting all keys like this also deletes _session_id.  Its very silly,
but it seems the only place Apache::Session stores the session_id is
actually in $self->{data}->{_session_id}.

In lieu of anyone actually fixing this Apache::Session stupidity - you
will have to do something like this instead:
  [-  delete $mdat{$_} foreach grep !/^_/, keys %mdat  -]
  

Ups. I should have thought of this... :-( Thanks, Angus.


Reply via email to