How does the session locks work in Moodle (part 1)?

The “problem” of session locking is not specific to Moodle. In general, the way web applications work is that any authenticated user will have his session data stored somehow on the server. At the beginnig of the HTTP request, his session data is retrieved and at the end of the request stored back.

What happens when the same user sends second request, before the first one finishes?

If PHP allowed the access to the same session for more than 1 request, then the data could easily get corrupted - by 2 separate processed reading & writing to it in parallel. So instead, the session is locked. The second request will get the session data only after the first requests finishes (and his session data is safely written).

To illustrate it - lets say we have 2 PHP scripts: sleep1.php and sleep2.php. They are exaclty the same and all they do is sleep for 5 seconds:

echo "Time start: " . date("H:i:s") . "<br />";
sleep(5);
echo "Time end: " . date("H:i:s") . "<br />";

When I run them in the browser, one after another, in separate browser tabs I get the results:

Time start: 20:11:17
Time end: 20:11:22
Time start: 20:11:18
Time end: 20:11:23

I have run the second tab 1 second after the first one, both took 5 seconds to finish. Simple.

Now I replace the code of both with a call to session_start():

echo "Time start: " . date("H:i:s") . "<br />";
session_start();
echo "After session_start(): " . date("H:i:s") . "<br />";
sleep(5);
echo "Time end: " . date("H:i:s") . "<br />";

And just like before I open sleep1.php in the first tab and sleep2.php second:

Time start: 20:25:33
After session_start(): 20:25:33
Time end: 20:25:38
Time start: 20:25:34
After session_start(): 20:25:38
Time end: 20:25:43

I clicked to open sleep1.php at 20:25:33. It has acquired the session immediately at 20:25:33 and then kept running until 20:25:38. Meanwhile, the second script was run at 20:25:34 but the call to session_start() has blocked it until 20:25:33 - exactly until the time the first script finished. Then, after getting the session, sleep1.php has run for 5 seconds until 20:25:43.

To alleviate the problem caused by the session lock, we can voluntarily release the lock before we finish the script. Imagine that during those 5 seconds (we sleep now - but let’s pretend this is some work done), we can write back to session what we need after the first 2 seconds.

Then, during the remaining 3 seconds we do some other processing, that we know will not need to update the user’s session. To release the lock earlier, we can use session_write_close().

Let’s extend our scripts:

echo "Time start: " . date("H:i:s") . "<br />";
session_start();
echo "After session_start(): " . date("H:i:s") . "<br />";
sleep(2);
session_write_close();
echo "After session_write_close(): " . date("H:i:s") . "<br />";
sleep(3);
echo "Time end: " . date("H:i:s") . "<br />";

The result now:

Time start: 16:28:24
After session_start(): 16:28:24
After session_write_close(): 16:28:26
Time end: 16:28:29
Time start: 16:28:25
After session_start(): 16:28:26
After session_write_close(): 16:28:28
Time end: 16:28:31

That’s better! Previously the time from start to end for script2.php was 9 seconds - 4 seconds waiting for the lock + 5 seconds of its own processing. Now the wait for the lock down to 1 second and script2.php run took 6 seconds. The second script gets the lock as soon as session_write_close() is called in the first one.

The session locking is sometimes problematic in applications like Moodle - because nearly all HTTP requests in Moodle come from authenticated users. Those users have the session created for them and therefore the locking described above affects them.

Move on to part 2.