proposal 0: options by session_start()
session_start() does not have parameter currently. INI and other options may be passed as parameter. Add array parameter to session_start() to specify options.
Following options are new options for session and these options are not INI option and could only be specified via session_start() option parameter.
proposal 1: read_only
Open session for reading. Close session immediately after read. This is the fastest way to read session. This is the fastest way to read session data when update is not needed.
proposal 2: lazy_write
Session data does not have to write back to session data storage if session data has not changed. By omitting write, overall system performance may improve. It also helps keep session integrity for unsafe_lock=on session access by omitting writes when data is not updated.
proposal 3: unsafe_lock
Add unsafe_lock option for maximum concurrent script execution. Currently, script execution is serialized when session is used. unsafe_lock=on allows script access session data concurrently. When lazy_write=on, only updated data is saved. Applications can maintain consistency by saving updated session data only.
Suppose there is concurrent access from a same client Connection A and B with unsafe_lock=On , lazy_write=On.
Connection A Connection B ↓ ↓ Open Session Open Session ↓ ↓ Login Does not care auth status. Just read session. ↓ ↓ Close Close (Session data unchanged)
Auth info set by Connection A never deleted by Connection B. Read only session achieves similar execution, but it has to wait lock. Therefore, previous access became
Connection A Connection B ↓ ↓ Open Session Wait lock ↓ ↓ Login ↓ ↓ ↓ Close ↓ Open Session ↓ Does not care auth status. Just read session. ↓ Close (Session data unchanged)
Usage Tip for unsafe_lock=On: Update $_SESSION only when update is strictly needed. For example, change $_SESSION only when authentication/authorization information has changed. Alternatively, user may start all session with read_only=TRUE and reopen session with read/write only when authentication/authorization information has to be changed.
proposal 4: lazy_destroy
When session ID is renewed, race condition may occur.
Current session module(session_destroy()/session_regenerate_id()) simply deletes session data with $delete_old_session=true. When $delete_old_session=false(default), session_regenerate_id() simply creates new session ID and leaves old session ID/data.
Even when old session ID is destroyed, script B can access server with old session ID. Without session.strict_mode=On, session module may reinitialize session data with old ID which may be known to attackers.
Lazy session data deletion solves this issue. It also solves reliability issue of session_regenerate_id() on unreliable networks.
Summary There is no new INI option and new options could be specified via session_start(). Users cannot change session behavior by mistake. [0]
Read only session makes sense since there are applications that do not require writing at all except when regenerating session ID/update authentication status. [1]
Currently, programmer has no control which session data is saved. New behavior allows programmer to control save changed session data when there are concurrent access. [2]
Session lock control could be API. e.g. session_lock() However, changing lock status after session started is complex. session_start() option is simpler. [3]
When client access to server concurrently, the order of requests cannot be guaranteed especially under mobile applications. This results in unreliable session_regenerate_id() operation that is closely related to web security. New behavior mitigate race condition. [4]
Add option array parameter to session_start(). New option of this RFC are not INI option, but only a option for session_start(). All session INI options may be specified, otherwise INI options are used. (INI option handling is not in the patch)
Example:
session_start(array('lazy_write'=>true, 'lazy_destroy'=>120, 'unsafe_lock'=>false));
Just read session data and close session immediately.
Introduce lazy_write option to session_start() that enable/disable lazy session data writing.
lazy_write - FALSE by default
When lazy_write=FALSE, session module works exactly as it is now.
When lazy_write=TRUE, session module save session data only when session data has been changed.
When reading session data, session module takes MD5 hash for read data. When writing session data, session module compares old MD5 and new MD5 of session data and save session data only when MD5 differs.
Since write is omitted, save handler may not change “last updated time”. This may result deleting session data by timeout. To prevent unwanted expiration of session, introduce new save handler API that update “last updated time” of session data.
PS_UPDATE_FUNC()
Session module calls PS_UPDATE_FUNC() instead of PS_WRITE_FUNC() when lazy_write=On.
NOTE: save handler implementation
For files save handler, PS_UPDATE_FUNC() is not needed as it can update mtime at PS_OPEN_FUNC(). Files save handler's PS_UPDATE_FUNC() will be empty function that simply return true.
For memcache/memcached/redis/etc save handlers, PS_UPDATE_FUNC() is not needed since read access(PS_READ_FUNC()) updates last access time stamp. PS_UPDATE_FUNC() may return success simply.
Other save handlers should support PS_UPDATE_FUNC() API to update last access time. Otherwise, garbage correction may remove active sessions. Alternatively, save handler may update last access time when PS_OPEN()/PS_READ_FUNC() is called, then PS_UPDATE_FUNC() may return success simply.
Session module locks session data while executing script by default. This make sure session data integrity by serializing script execution. i.e. Only a script may have access to session data at the same time.
Current behavior ensures session data integrity, but it serializes script execution and slows down application performance.
For user defined save handlers, locking is up to users. Memcached session save handler has lock ini option for better performance by sacrificing integrity a little. Second proposal mitigates broken integrity by unlocked session data access.
Introduce unsafe_lock option to session_start() that enable/disable session data lock.
unsafe_lock - FALSE by default.
When unsafe_lock=FALSE, save handler works exactly as it is now.
When unsafe_lock=TRUE, save handler lock session data only when locking is strictly needed. For files save handler, it locks only when reading/writing data.
Current behavior:
Open session data ↓ Read session data ----------------- ↓ ↑ (Script execution) Locked ↓ ↓ Write session data ------------------ ↓ Close session data
New behavior with unsafe_lock=On:
Open session data ↓ Read session data ← Locked ↓ (Script execution) ↓ Write session data ← Locked ↓ Close session data
Introduce lazy_destroy option to session_start() that enable/disable lazy session data deletion. Delayed deletion is required for secure/reliable session_regenerate_id() operation.
lazy_destroy - 60 (seconds) by default. Seconds that app may access session marked to be deleted.
In order to session module to know if the session data is already deleted and accessible, there must be flag/time stamp for deletion/access control.
First option:
Save time stamp in $_SESSION['__PHP_SESSION_DESTROYED__'].
Alternative option:
Introduce new save handler API that saves flag/time stamp else where.
Since alternative option could impact performance a lot. This RFC chooses first option.
By introducing this feature, session_regenerate_id(true) ($delete_old_session) can be a default.
session_destroy() no longer deletes session data, but set destroyed flag. To force deletion, add $force_deletion option to session_destroy(). (Default: FALSE)
Difference between lazy destroy and leaving old session data
It's clear that lazy_destroy is much secure than leaving old session data while it is keeping reliable session ID regeneration.
None for most applications.
lazy_destroy is enabled by default for reliable session_regenerate_id() operation. This could be issue for test scripts. For this case set $delete_old_session=TRUE.
session_destroy() no longer deletes session data. Like session_regenerate_id(), this could be a issue for test scripts. Use session_destroy(TRUE) to delete data actually.
PHP 5.6 and later.
None other than session.
Old save handler works without new INI or API support.
Save handler module needs to be recompiled, but recompile is required for new release anyway.
No INI option is exposed.
None.
Implemented
Not implemented
1) read_only for reading only.
2) lazy_write that writes only session data has changed.
Pros.
Cons.
3) unsafe_lock that control session data locking.
Pros
Cons
4) lazy_destroy that allows delayed session data deletion for concurrent accesses and reliable session_regenerate_id() operation.
Pros
Cons
Please note that lazy_destroy is mandatory for reliable session_regenerate_id()/session_destroy() operation. Please refer to referenced discussion for details.
Please propose alternative solution if you vote no to this proposal. This is security related change so there must be alternative solution. session_regenerate_id() cannot delete old session without delayed deletion or alternative solution if there is.
Note: read_only=on will yield better result than this.
Requests per second: 1894.61 [#/sec] (mean) 100% 67 (longest request)
Requests per second: 445.19 [#/sec] (mean) 100% 1547 (longest request)
Benchmark detail is blow https://wiki.php.net/rfc/session-lock-ini#benchmark_detail
Please vote feature by feature.
The voting period is 2014/02/12 until 2014/02/19.
1,2) Introduce read_only, lazy_write to session_start() option.
3) Introduce unsafe_lock option to session_start() option.
4) Introduce lazy_destroy option to session_start() option.
Thank you for voting!
After the project is implemented, this section should contain
Since files save handler does not flush data to disk, it's like a accessing memory and unsafe_lock=on could be a bit slower. lazy_write=on may not change performance since disk access is not a performance bottle neck.
To see the benefit, database storage and more complex script would be needed.
To make benchmark is more realistic, following script generates 10 sessions, client concurrently connects to apache using 50 connection. (5 connections/session)
http://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser
Most scripts are much more complex, so there is usleep() simulate it.
Requests per second: 1894.61 [#/sec] (mean) 100% 67 (longest request)
Requests per second: 445.19 [#/sec] (mean) 100% 1547 (longest request)
This test case is rather extreme, but if everything in web page (i.e. images, etc) is access controlled by session, this can be a usual case.
<?php ini_set('session.use_trans_sid',TRUE); ini_set('session.use_cookies',FALSE); ini_set('session.use_only_cookies',FALSE); ini_set('session.use_strict_mode',FALSE); ini_set('session.save_path', '/usr/tmp/'); session_id('TESTID'.mt_rand(1, 10)); session_start(['lazy_write'=>$_GET['lazy_write'], 'unsafe_lock'=>$_GET['unsafe_lock']]); if (!isset($session['x'])) { $_SESSION['x'] = str_repeat('x', 1024*10); } echo session_id(); var_dump(session_module_feature()); usleep(20000);
$ ab -c 50 -n 20000 'http://localhost:1080/session.php?lazy_write=1&unsafe_lock=1' This is ApacheBench, Version 2.3 <$Revision: 1430300 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 2000 requests Completed 4000 requests Completed 6000 requests Completed 8000 requests Completed 10000 requests Completed 12000 requests Completed 14000 requests Completed 16000 requests Completed 18000 requests Completed 20000 requests Finished 20000 requests Server Software: Apache/2.2.22 Server Hostname: localhost Server Port: 1080 Document Path: /session.php?lazy_write=1&unsafe_lock=1 Document Length: 246 bytes Concurrency Level: 50 Time taken for tests: 10.556 seconds Complete requests: 20000 Failed requests: 1983 (Connect: 0, Receive: 0, Length: 1983, Exceptions: 0) Write errors: 0 Total transferred: 11661983 bytes HTML transferred: 4921983 bytes Requests per second: 1894.61 [#/sec] (mean) Time per request: 26.391 [ms] (mean) Time per request: 0.528 [ms] (mean, across all concurrent requests) Transfer rate: 1078.85 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 6 Processing: 21 26 4.4 25 66 Waiting: 18 26 4.2 25 66 Total: 21 26 4.4 26 67 Percentage of the requests served within a certain time (ms) 50% 26 66% 28 75% 29 80% 30 90% 32 95% 34 98% 37 99% 40 100% 67 (longest request)
$ ab -c 50 -n 20000 'http://localhost:1080/session.php?lazy_write=0&unsafe_lock=0' This is ApacheBench, Version 2.3 <$Revision: 1430300 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 2000 requests Completed 4000 requests Completed 6000 requests Completed 8000 requests Completed 10000 requests Completed 12000 requests Completed 14000 requests Completed 16000 requests Completed 18000 requests Completed 20000 requests Finished 20000 requests Server Software: Apache/2.2.22 Server Hostname: localhost Server Port: 1080 Document Path: /session.php?lazy_write=0&unsafe_lock=0 Document Length: 246 bytes Concurrency Level: 50 Time taken for tests: 44.924 seconds Complete requests: 20000 Failed requests: 1938 (Connect: 0, Receive: 0, Length: 1938, Exceptions: 0) Write errors: 0 Total transferred: 11661938 bytes HTML transferred: 4921938 bytes Requests per second: 445.19 [#/sec] (mean) Time per request: 112.310 [ms] (mean) Time per request: 2.246 [ms] (mean, across all concurrent requests) Transfer rate: 253.51 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 2 Processing: 21 112 131.0 61 1547 Waiting: 20 112 131.0 61 1547 Total: 21 112 131.0 61 1547 Percentage of the requests served within a certain time (ms) 50% 61 66% 90 75% 124 80% 160 90% 275 95% 390 98% 545 99% 647 100% 1547 (longest request)