# RFC: Introduce session_start() INI options as array

- Status: Implemented (PHP 7.0)
- Authors: Yasuo Ohgaki, yohgaki@ohgaki.net
- Date: 2014-02-02
- Version: 1.6
- Original: https://wiki.php.net/rfc/session-lock-ini
- Page: https://externals.io/rfc/session-lock-ini
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Read only, lazy write option (closed)

Yes: 9 · No: 1

Voters: ajf (Yes), bwoebi (Yes), cpriest (Yes), datibbaw (Yes), krakjoe (No), lstrojny (Yes), rasmus (Yes), stas (Yes), treffynnon (Yes), yohgaki (Yes)

### Unsafe lock option (closed)

Yes: 1 · No: 7

Voters: ajf (No), bwoebi (No), krakjoe (No), nikic (No), rasmus (No), stas (No), treffynnon (No), yohgaki (Yes)

### Lazy destroy option (closed)

Yes: 1 · No: 6

Voters: ajf (No), bwoebi (No), krakjoe (No), rasmus (No), stas (No), treffynnon (No), yohgaki (Yes)

## Discussions on the mailing list

- [\[RFC\] Introduce session.lock, session.lazy_write and session.lazy_destory](https://externals.io/message/70808.md) — started on 2013-12-21, 8 messages
- [\[VOTE\] Introduce session.lock, session.lazy_write and session.lazy_destory](https://externals.io/message/71287.md) — started on 2014-01-20, 42 messages
- [\[VOTE\] RFC: Introduce session_start() options - read_only, unsafe_lock, lazy_write and lazy_destroy](https://externals.io/message/72538.md) — started on 2014-02-13, 13 messages
- [\[RFC\] Revert/extend/postpone original RFC about read_only, lazy_write sessions](https://externals.io/message/73177.md) — started on 2014-03-15, 34 messages
- [Lazy writing in the session, session-lock-ini, and bug #68331](https://externals.io/message/78699.md) — started on 2014-11-05, 8 messages
- [PHP RFC: Introduce session_start() options - read_only, unsafe_lock, lazy_write and lazy_destroy](https://externals.io/message/78781.md) — started on 2014-11-06, 3 messages

---

## Introduction

**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.

- Script A renewed session ID
- Script B accessed to server with old session ID

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\]

## Proposal 0 session\_start() option

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));
```

## Proposal 1 read\_only option

Just read session data and close session immediately.

## Proposal 2 - lazy\_write

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.

## Proposal 3 unsafe\_lock option

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
```

## Proposal 4 - lazy\_destroy

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\_\_'\].

- Pros - simple and fast.
- Cons - exposed internal data.

**Alternative option:**

Introduce new save handler API that saves flag/time stamp else where.

- Pros - clean. no exposed data.
- Cons - complex and slow.

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**

- Destroying session data with lazy destroy, it can only accessible defined time after destroy. (60 sec by default)

- Leaving old data may keep old data forever. There is GC expire (default 1440 sec), but data may survive forever by accessing periodically.

It's clear that lazy\_destroy is much secure than leaving old session data while it is keeping reliable session ID regeneration.

## Backward Incompatible Changes

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.

## Proposed PHP Version(s)

PHP 5.6 and later.

## Impact to Existing Extensions

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.

## php.ini Defaults

No INI option is exposed.

## Open Issues

None.

## Related Features

Implemented

1. session\_commit()/session\_write\_close() - Implemented (PHP 4.0 and up). End session and write session data.
2. session\_abort() - Implemented (5.6 and up). End session without saving session data.
3. session\_regenerate\_id() - It cannot be delete old session reliably without session.lazy\_destroy. “$delete\_old\_session” option default changed to TRUE by default.
4. session\_destroy() - Added “$force\_deletion” option(Default: FALSE).
5. session\_create\_id() - Create session ID using session module code. Added by as part of RFC patch.
6. session\_set\_save\_handler() - Additional functions/methods added. Added by this RFC.

Not implemented

1. session\_unlock() - Unlock active session data. Session data is saved at close. i.e. session\_commit() or module shutdown. This will not be implemented as it could be complex and slow. 'unsafe\_lock' should be used instead.

## Proposed Voting Choices

- Proposal 1,2: Yes / No
- Proposal 3: Yes / No
- Proposal 4: Yes / No

**1) read\_only** for reading only.

**2) lazy\_write** that writes only session data has changed.

**Pros.**

1. lazy\_write=TRUE could improved 2 times or better performance by removing write() calls.
2. lazy\_write=TRUE provides better data consistency since only modified session data is written when session.lock=FALSE,

**Cons.**

1. Session behavior is changed. When lazy\_write=Off, “last session written” wins. When lazy\_write=TRUE, “last session modified” wins.
2. Programmers are responsible for ensuring data consistency when unsafe\_lock=TRUE.

**3) unsafe\_lock** that control session data locking.

**Pros**

1. Maximum concurrency (= better performance)
2. Programmer does not have to selectively start session. (i.e. read only or not) Simplify code.
3. Allow concurrency without explicit calls of session\_commit(), session\_write\_close(), session\_abort().
4. Simpler and faster than session\_lock() API. e.g. This API requires additional access to data storage which make data handling more complex and slower.

**Cons**

1. Misuse of this feature could be a cause of bugs.
2. Open all session with read\_only=TRUE and reopen session for writing as it is needed, would be safer for average users. (We may better to promote this usage pattern)

**4) lazy\_destroy** that allows delayed session data deletion for concurrent accesses and reliable session\_regenerate\_id() operation.

**Pros**

1. session\_regenerate\_id() becomes more reliable for reasons described below.
2. Ensures proper application behavior even when network connection is unreliable. This is important for mobile applications especially.
3. Mitigates race condition that creates unneeded sessions. NOTE: Unneeded session created by race condition could be known session to attacker. Unneeded session increase system load.

**Cons**

1. Deleted session is accessible by specified time. NOTE: Access to deleted session is required for reliable session\_regenerate\_id() operation.
2. Deleted session contains time stamp variable in $\_SESSION. NOTE: Time stamp is mandatory and storing it in $\_SESSION is the fastest and simplest way.

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.

## Benchmark

Note: read\_only=on will yield better result than this.

##### lazy\_write: on, unsafe\_lock: on

```
Requests per second:    1894.61 [#/sec] (mean)
100%     67 (longest request)
```

##### lazy\_write: off, unsafe\_lock: off

```
Requests per second:    445.19 [#/sec] (mean)
100%   1547 (longest request)
```

Benchmark detail is blow [https://externals.io/rfc/session-lock-ini#benchmark\_detail](https://externals.io/rfc/session-lock-ini#benchmark_detail)

## Vote

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.

**Vote: Read only, lazy write option** — Yes: 9 · No: 1 (closed)

3\) Introduce unsafe\_lock option to session\_start() option.

**Vote: Unsafe lock option** — Yes: 1 · No: 7 (closed)

4\) Introduce lazy\_destroy option to session\_start() option.

**Vote: Lazy destroy option** — Yes: 1 · No: 6 (closed)

Thank you for voting!

## Patches and Tests

<https://github.com/yohgaki/php-src/compare/PHP-5.6-rfc-session-lock>

## Implementation

After the project is implemented, this section should contain

1. the version(s) it was merged to
2. a link to the git commit(s)
3. a link to the PHP manual entry for the feature

## References

1. session\_regenerated\_id() discussion - [http://marc.info/?l=php-internals&amp;m=138242492914526&amp;w=2](http://marc.info/?l=php-internals&m=138242492914526&w=2)
2. session.lock discussion - [http://marc.info/?l=php-internals&amp;m=138445826032002&amp;w=2](http://marc.info/?l=php-internals&m=138445826032002&w=2)
3. VOTE discussion - [http://marc.info/?l=php-internals&amp;m=139017836113664&amp;w=2](http://marc.info/?l=php-internals&m=139017836113664&w=2)

## Benchmark Detail

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.

##### lazy\_write: on, unsafe\_lock: on

```
Requests per second:    1894.61 [#/sec] (mean)
100%     67 (longest request)
```

##### lazy\_write: off, unsafe\_lock: off

```
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
<?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);
```

# lazy\_write: on, unsafe\_lock: on

```
$ 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)
```

# lazy\_write: off, unsafe\_lock: off

```
$ 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)
```

## Rejected Features
