
The other day I ran into a very interesting problem. In the system with which I understood, the mechanism used to limit the lifetime of the session was used. The validation of this time was shifted on the shoulders of the garbage collector, which for some reason did not perform it in good faith, or even did not do it at all. As it turned out, these mistakes are common, so I would like to tell you about the intricacies of working with GC.
In php, 3 parameters are responsible for the GC for sessions:
session.gc_probability ,
session.gc_divisor and
session.gc_maxlifetime .
These parameters speak of the following: in
gc_probability from
gc_divisor of starts of session_start, the GC is started, which should clear the sessions with the last access time more than
gc_maxlifetime .
We do everything, or example number 1
Let's try to test the work of GC in a small script:
<?php ini_set("session.gc_maxlifetime", 1); session_start(); if (isset($_SESSION['value'])) { $_SESSION['value'] += 1; } else { $_SESSION['value'] = 0; } echo $_SESSION['value']; ?>
We will update this file 10 times with an interval of 10-15 seconds (it is possible and more, it is important that the gap be higher than 1 second). As a result, we get "unexpected answers":
0 1 2 3 ...
The reason is quite simple and, I would say, obvious:
gc will run only in 1 out of 1000 requests, and we have made only 15.
')
Note: most of the systems that I saw worked on this algorithm and did not go deeper.Bypass the bug at any cost, or example number 2
The solution of the problem seems simple - what if the launch of GC is forced?
<?php ini_set("session.gc_maxlifetime", 1); ini_set("session.gc_divisor", 1); ini_set("session.gc_probability", 1); session_start(); if (isset($_SESSION['value'])) { $_SESSION['value'] += 1; } else { $_SESSION['value'] = 0; } echo $_SESSION['value']; ?>
But the behavior of this script becomes much more unexpected. Let's try to repeat the same steps as for example No. 1:
0 1 0 1 ...
Debriefing, or why it happens
If we hang the handlers using session_set_save_handler, then we can easily restore the order of loading / processing the session:
- open
- read
- gc
- PROGRAM
- close
Those. The garbage collector was launched after reading the session, which means the $ _SESSION array is already full. From here comes the unexpected unit in the second example!
Let's go back to the first example.
As we now see, the garbage collector may start at the 3rd step, but what happens if it does not start? After all, with the standard settings, the chance to launch is only 1 out of 1000.
The outdated session will successfully open, read, and at the end of the work, the time of the last access to the file will be updated - in this case, this session becomes almost endless. But, at the same time, if our script uses 1000 different users, then you can forget about the session “infinity”, since GC is likely to start for any of the users, the lifetime will start to work correctly (or rather, almost true). This behavior of the system is ambiguous and unpredictable, and this will potentially lead to a large number of difficult to detect problems.
And what to do now, or exits
The most correct decision is to use your session validation mechanism. The documentation clearly states that
“Session.gc_maxlifetime sets the time delay in seconds, after which the data will be considered as garbage and will potentially be deleted. Garbage collection can occur during a session start (depending on the values ​​of session.gc_probability and session.gc_divisor). ”The words“ potentially ”and“ may ”just say that gc is not intended to limit the lifetime of the session. In those places where the lifetime of the session is important, and the emergence of artifacts, as in example No. 2 is critical, use your lifetime validation.
Exit number 2, bad and wrong
We know that the established "forced mode" of gc will work in step # 3 of the start of the session. Those. in fact, after the start of an outdated session, the data in the $ _SESSION array is present, and the file has already been deleted. In this case, it is logical to try to re-create the session, that is, in fact, make launch 2 of the start of session_start:
<?php ini_set("session.gc_maxlifetime", 1); ini_set("session.gc_divisor", 1); ini_set("session.gc_probability", 1); session_start(); if (isset($_SESSION['value'])) { $_SESSION['value'] += 1; } else { $_SESSION['value'] = 0; } echo $_SESSION['value']; session_commit(); session_start(); echo ' '.$_SESSION['value']; ?>
The results of the script will be:
0 0 1 0 0 1 ...
This behavior is clear from the order of the session processing, but (remember the documentation, and generally take a good look) you should not do so.
Hooray, figured out - conclusion
I was surprised that most, even experienced, developers never thought about the behavior of GC, carelessly trusting him to limit the lifetime of the session. While the documentation clearly states that this is not worth doing, the name of the Garbage Collector (not the Session Validator, or Session Expire) speaks for itself. But the main conclusion, of course, is that you should carefully check even the seemingly obvious parts of the system. Errors of system functions or methods are sometimes their incorrect interpretation, and not errors as such.
Thank you all for reading to the end. I hope this article has been helpful to you.