Seitenanfang

Session-Trouble

Dieser Post wurde aus meiner alten WordPress-Installation importiert. Sollte es Darstellungsprobleme, falsche Links oder fehlende Bilder geben, bitte einfach hier einen Kommentar hinterlassen. Danke.


Why should I write about sessioning at all? A session is something which exists and should be as easy usable as possible. Is this the end of my blog post about sessioning?

No, it isn't, because sessions usually don't make any trouble, but not always.

Long term lock

I was told that PHP does some session locking:
  1. Lock the session ID
  2. Read the session data
  3. Process the request/run the script
  4. Write the session data
  5. Unlock the session ID
I don't know if PHP sessions are really working this way, but it's common, because this procedure is super-safe, but imagine a user which opens ten links in new tabs. They'll be processed sequentially, one starting after the previous has finished. The page might run a huge database request and run for 10 seconds (or more). Ten requests would take 100 seconds to finish (which isn't a big problem), but all those requests are waiting in webserver slots until they're allowed to start which could easily become a problem.

Here is a sample with two requests racing for the same session, each row happens at the same time:

Try to lock sessionTry to lock session
Got session lockWait
Read sessionWait
Run the scriptWait
Write sessionWait
Unlock the sessionWait
FinishedGot session lock
Read sesion
Run the script
Write session
Finished
This is safe, because the second request will see every change to the session data applied by the first request and may easily load the session into a variable/hash/"associated array", modify it and write the whole structure back to the session storage (database, disk, whatever you like) when the first request has done it's work and before the second request starts it's work.

No lock

The lock is blocking the second (and all other) requests, why not simply remove it. All requests will run in parallel and everything is fine - but it's not that easy. The session might have a simple counter which is increased by every page access:
Read the sessionRead the session
Current counter value: 5Current counter value: 5
Increased counter: 6Increased counter: 6
Write the sessionWrite the session
The user accessed the page two times - but only one has been counted, because the second request didn't know that the first already increased the value. There are many other potentially dangerous situations: The user might be allowed to request something once per hour, maybe he paid for one download but tried ten by opening them in different browser tabs - and got all ten downloads for the price of one.

You might think that this race condition is pure theory, but it isn't. There are common browser plugins - at least for Firefox and Opera - which open all links within a rubberband in new tabs. I like this, for example for opening a list of tickets in our ticket system instead of shift+click on every single link.

Synchronous storage access

Locking seems to be no option, but skipping the "read the session" part might be one. Perl could easily tie a hash to a module and pass every read or write access to the hash directly to a function of the module. Each read or write access to the session hash is translated into a function call, for example:
sub FETCH {  my ($tied_item, $key) = @_;  my @result = $DBH->selectrow_array(    'SELECT '.$DBH->quote_identifier($key).' FROM session_data WHERE id='    . $DBH->quote($tied_item->{id})  );  return $result[0];}
 

Every operation will work directly on the database. The counter sample from the "no lock" sample still might end up in a race condition as both requests might fetch the value, increase it and write it back at the same time. I experienced a project not long ago where a mySQL cluster was in use and the requests are waiting (shortly) until the slave is fresh. Two requests waiting for the same slave may be started with a (slight) delay but still run absolutely parallel from the "wait for slave" function until the end - and they did, resulting in duplicate data in a table where data must be unique. An existing unique index showed the problem, because one of them ran into a "duplicate key" error just after checking if the key exists, but problems are very less likely using synchronous storage access because the time where races might happy rapidly decreases.

Synchronous atomic storage access

Another approach doesn't use a hash at all but a session object which has get_value, set_value and some other functions. They face the same problems like the tied solution before except that operations with race-risks might be done atomic.

A atomic operation happends as one step. A inc_value method is a very good example:

sub inc_value {  my $self = shift;  my $key = shift;

my $quoted_key = $DBH->quote_identifier($key); $DBH->do("UPDATE session_data SET $quoted_key = $quoted_key + 1");}

There is no read, increase and write, everything is done in a single SQL command. Thousand or more requests might run parallel, but the counter will always have the correct result.

Using a custom session class/module isn't as easy as simply using anything provided from your framework, but it gives you much more control about what's happening.

Non-atomic operations (which require custom source to run between loading data from the storage and writing it back) may add locking again for very short-term locks on a session but they need additional care from the developer when and expecially how long to lock the session.

 

Noch keine Kommentare. Schreib was dazu

Schreib was dazu

Die folgenden HTML-Tags sind erlaubt:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>