Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0d57343

Browse filesBrowse files
azjezzbcremerrobfrawley
committed
[HttpFoundation] Precalculate session expiry timestamp
Co-authored-by: Benjamin Cremer <b.cremer@shopware.com> Co-authored-by: Rob Frawley 2nd <rmf@src.run>
1 parent 320e495 commit 0d57343
Copy full SHA for 0d57343

File tree

3 files changed

+45
-23
lines changed
Filter options

3 files changed

+45
-23
lines changed

‎UPGRADE-4.4.md

Copy file name to clipboardExpand all lines: UPGRADE-4.4.md
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ HttpFoundation
109109

110110
* `ApacheRequest` is deprecated, use `Request` class instead.
111111
* Passing a third argument to `HeaderBag::get()` is deprecated since Symfony 4.4, use method `all()` instead
112+
* `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column,
113+
make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database
114+
to speed up garbage collection of expired sessions.
112115

113116
HttpKernel
114117
----------

‎src/Symfony/Component/HttpFoundation/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/CHANGELOG.md
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ CHANGELOG
77
* passing arguments to `Request::isMethodSafe()` is deprecated.
88
* `ApacheRequest` is deprecated, use the `Request` class instead.
99
* passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead
10-
10+
* `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column,
11+
make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database
12+
to speed up garbage collection of expired sessions.
13+
1114
4.3.0
1215
-----
1316

‎src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
+38-22Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ class PdoSessionHandler extends AbstractSessionHandler
6565
*/
6666
const LOCK_TRANSACTIONAL = 2;
6767

68+
private const MAX_LIFETIME = 315576000;
69+
6870
/**
6971
* @var \PDO|null PDO instance or null when not connected yet
7072
*/
@@ -237,6 +239,7 @@ public function createTable()
237239

238240
try {
239241
$this->pdo->exec($sql);
242+
$this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)");
240243
} catch (\PDOException $e) {
241244
$this->rollback();
242245

@@ -368,14 +371,14 @@ protected function doWrite($sessionId, $data)
368371
*/
369372
public function updateTimestamp($sessionId, $data)
370373
{
371-
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
374+
$expiry = time() + (int) ini_get('session.gc_maxlifetime');
372375

373376
try {
374377
$updateStmt = $this->pdo->prepare(
375-
"UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
378+
"UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"
376379
);
377380
$updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
378-
$updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
381+
$updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT);
379382
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
380383
$updateStmt->execute();
381384
} catch (\PDOException $e) {
@@ -402,14 +405,21 @@ public function close()
402405
$this->gcCalled = false;
403406

404407
// delete the session records that have expired
408+
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min";
409+
$stmt = $this->pdo->prepare($sql);
410+
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
411+
$stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT);
412+
$stmt->execute();
413+
// to be removed in 6.0
405414
if ('mysql' === $this->driver) {
406-
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time";
415+
$legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time";
407416
} else {
408-
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time - $this->timeCol";
417+
$legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol";
409418
}
410419

411-
$stmt = $this->pdo->prepare($sql);
420+
$stmt = $this->pdo->prepare($legacySql);
412421
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
422+
$stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT);
413423
$stmt->execute();
414424
}
415425

@@ -616,7 +626,12 @@ protected function doRead($sessionId)
616626
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
617627

618628
if ($sessionRows) {
619-
if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
629+
$expiry = (int) $sessionRows[0][1];
630+
if ($expiry <= self::MAX_LIFETIME) {
631+
$expiry += $sessionRows[0][2];
632+
}
633+
634+
if ($expiry < time()) {
620635
$this->sessionExpired = true;
621636

622637
return '';
@@ -747,6 +762,7 @@ private function getSelectSql(): string
747762
if (self::LOCK_TRANSACTIONAL === $this->lockMode) {
748763
$this->beginTransaction();
749764

765+
// stop selecting time column starting from 6.0
750766
switch ($this->driver) {
751767
case 'mysql':
752768
case 'oci':
@@ -775,18 +791,18 @@ private function getInsertStatement(string $sessionId, string $sessionData, int
775791
$data = fopen('php://memory', 'r+');
776792
fwrite($data, $sessionData);
777793
rewind($data);
778-
$sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :lifetime, :time) RETURNING $this->dataCol into :data";
794+
$sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data";
779795
break;
780796
default:
781797
$data = $sessionData;
782-
$sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
798+
$sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)";
783799
break;
784800
}
785801

786802
$stmt = $this->pdo->prepare($sql);
787803
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
788804
$stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
789-
$stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
805+
$stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT);
790806
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
791807

792808
return $stmt;
@@ -802,18 +818,18 @@ private function getUpdateStatement(string $sessionId, string $sessionData, int
802818
$data = fopen('php://memory', 'r+');
803819
fwrite($data, $sessionData);
804820
rewind($data);
805-
$sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data";
821+
$sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data";
806822
break;
807823
default:
808824
$data = $sessionData;
809-
$sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
825+
$sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id";
810826
break;
811827
}
812828

813829
$stmt = $this->pdo->prepare($sql);
814830
$stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
815831
$stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
816-
$stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
832+
$stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT);
817833
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
818834

819835
return $stmt;
@@ -826,7 +842,7 @@ private function getMergeStatement(string $sessionId, string $data, int $maxlife
826842
{
827843
switch (true) {
828844
case 'mysql' === $this->driver:
829-
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
845+
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ".
830846
"ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
831847
break;
832848
case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
@@ -837,10 +853,10 @@ private function getMergeStatement(string $sessionId, string $data, int $maxlife
837853
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
838854
break;
839855
case 'sqlite' === $this->driver:
840-
$mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
856+
$mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)";
841857
break;
842858
case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
843-
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
859+
$mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ".
844860
"ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
845861
break;
846862
default:
@@ -854,15 +870,15 @@ private function getMergeStatement(string $sessionId, string $data, int $maxlife
854870
$mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR);
855871
$mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR);
856872
$mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB);
857-
$mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT);
858-
$mergeStmt->bindValue(5, time(), \PDO::PARAM_INT);
859-
$mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB);
860-
$mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT);
861-
$mergeStmt->bindValue(8, time(), \PDO::PARAM_INT);
873+
$mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT);
874+
$mergeStmt->bindValue(4, time(), \PDO::PARAM_INT);
875+
$mergeStmt->bindParam(5, $data, \PDO::PARAM_LOB);
876+
$mergeStmt->bindValue(6, time() + $maxlifetime, \PDO::PARAM_INT);
877+
$mergeStmt->bindValue(6, time(), \PDO::PARAM_INT);
862878
} else {
863879
$mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
864880
$mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
865-
$mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
881+
$mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT);
866882
$mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
867883
}
868884

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.