Skip to content

Don't defeat session.lazy_write in the php session driver (#9885) - #10248

Merged
alecpl merged 1 commit into
roundcube:masterfrom
MiMoHo:session-php-lazy-write
Jul 19, 2026
Merged

Don't defeat session.lazy_write in the php session driver (#9885)#10248
alecpl merged 1 commit into
roundcube:masterfrom
MiMoHo:session-php-lazy-write

Conversation

@MiMoHo

@MiMoHo MiMoHo commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Supersedes #9885, implementing what was agreed there between @tpayen and @alecpl about a year ago (the PR was not updated since); original analysis and measurement by @tpayen, credited as co-author.

Problem

rcube_session_php::write_close() sets $_SESSION['__MTIME'] = time() on every request. The variable is not read anywhere in the codebase (verified via grep — the $changed property alecpl mentioned in #9885 has meanwhile been removed entirely by the expires_at rework). Its only effect is that session data changes on every request, which defeats PHP's session.lazy_write optimization: PHP writes the full session to storage on every request instead of skipping unchanged sessions. With a network-backed session.save_handler (e.g. phpredis, as in #9885), that is significant unnecessary traffic — the original reporter measured ~50% of their Redis session traffic caused by this.

Change

Remove the __MTIME update (as agreed in #9885, rather than the throttling originally proposed there).

  • Keep-alive is not lost: with lazy_write, PHP calls the save handler's updateTimestamp() for unchanged sessions, which refreshes the TTL (phpredis issues EXPIRE) or the file mtime (files handler). session.gc_maxlifetime is already set from session_lifetime in rcube::session_init().
  • __IP is kept — it is read back in start() and required for ip_check — and it is idempotent from the second request on, so it does not prevent lazy_write.
  • Only the php driver is affected; the db/redis/memcache drivers register their own save handler and manage expiry via expires_at themselves.

Testing

  • Behavior demonstrated with a counting SessionHandlerInterface + SessionUpdateTimestampHandlerInterface over two simulated requests (fixed session id, lazy_write=1, second request changes nothing):
    • with __MTIME (current master): write, write
    • without __MTIME (this PR): write, updateTimestamp
  • Full PHPUnit suite passes on the branch (PHP 8.5.8; single pre-existing local failure of RcubeTest::test_exec, BSD vs GNU date on macOS, unrelated).
  • php-cs-fixer clean on the changed file.

This contribution was prepared with AI assistance (Claude Code); the analysis and test results above were independently verified as described.

🤖 Generated with Claude Code

@alecpl

alecpl commented Jul 18, 2026

Copy link
Copy Markdown
Member

Please, remove the changelog changes.

…9885)

The php session driver updated $_SESSION['__MTIME'] on every request.
The variable is not read anywhere, but mutating session data on each
request prevents PHP's session.lazy_write optimization from skipping
storage writes for unchanged sessions - every request caused a full
session write. With a network-backed session.save_handler (e.g.
Redis) this is significant unnecessary traffic; the original reporter
measured about half of their Redis traffic caused by it.

Remove the __MTIME update. Session keep-alive does not need it: with
lazy_write, PHP calls the save handler's updateTimestamp() for
unchanged sessions, which refreshes the TTL (phpredis) or mtime
(files). The __IP variable is kept, it is required for ip_check and
is idempotent, so it does not prevent lazy_write.

Verified with a counting save handler over two simulated requests:
with __MTIME PHP calls write() on both requests; without it, the
second request triggers updateTimestamp() only.

Implements what was agreed with the maintainer in PR roundcube#9885.

Co-authored-by: Thomas P <[email protected]>
Co-Authored-By: Claude Fable 5 <[email protected]>
@MiMoHo
MiMoHo force-pushed the session-php-lazy-write branch from 32940cf to 2946ace Compare July 19, 2026 02:26
@MiMoHo

MiMoHo commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Done — dropped the Changelog entry.

@alecpl alecpl added this to the 1.7.3 milestone Jul 19, 2026
@alecpl
alecpl merged commit 32fd022 into roundcube:master Jul 19, 2026
17 checks passed
alecpl pushed a commit that referenced this pull request Jul 19, 2026
…0248)

The php session driver updated $_SESSION['__MTIME'] on every request.
The variable is not read anywhere, but mutating session data on each
request prevents PHP's session.lazy_write optimization from skipping
storage writes for unchanged sessions - every request caused a full
session write. With a network-backed session.save_handler (e.g.
Redis) this is significant unnecessary traffic; the original reporter
measured about half of their Redis traffic caused by it.

Remove the __MTIME update. Session keep-alive does not need it: with
lazy_write, PHP calls the save handler's updateTimestamp() for
unchanged sessions, which refreshes the TTL (phpredis) or mtime
(files). The __IP variable is kept, it is required for ip_check and
is idempotent, so it does not prevent lazy_write.

Verified with a counting save handler over two simulated requests:
with __MTIME PHP calls write() on both requests; without it, the
second request triggers updateTimestamp() only.

Implements what was agreed with the maintainer in PR #9885.

Co-authored-by: Thomas P <[email protected]>
Co-authored-by: Claude Fable 5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants