10000 bug #19101 [Session] fix PDO transaction aborted under PostgreSQL (To… · symfony/symfony@9df08fa · GitHub
[go: up one dir, main page]

Skip to content

Commit 9df08fa

Browse files
committed
bug #19101 [Session] fix PDO transaction aborted under PostgreSQL (Tobion)
This PR was merged into the 2.7 branch. Discussion ---------- [Session] fix PDO transaction aborted under PostgreSQL | Q | A | ------------- | --- | Branch? | 2.7 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #14641 | License | MIT | Doc PR | Fixes the transactional concurrency error handling for PostgreSQL which does not allow to execute further queries in a transaction with an error. Because of the loop, look at the diff with whitespace ignored to see the difference: https://github.com/symfony/symfony/pull/19101/files?w=1 Commits ------- f8eefa0 [Session] fix PDO transaction aborted under PostgreSQL
2 parents 6a8a113 + f8eefa0 commit 9df08fa

File tree

2 files changed

+39
-38
lines changed

2 files changed

+39
-38
lines changed

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

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -504,54 +504,51 @@ private function doRead($sessionId)
504504
$selectSql = $this->getSelectSql();
505505
$selectStmt = $this->pdo->prepare($selectSql);
506506
$selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
507-
$selectStmt->execute();
508507

509-
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
508+
do {
509+
$selectStmt->execute();
510+
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
510511

511-
if ($sessionRows) {
512-
if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
513-
$this->sessionExpired = true;
512+
if ($sessionRows) {
513+
if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
514+
$this->sessionExpired = true;
514515

515-
return '';
516-
}
516+
return '';
517+
}
517518

518-
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
519-
}
519+
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
520+
}
520521

521-
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
522-
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
523-
// until other connections to the session are committed.
524-
try {
525-
$insertStmt = $this->pdo->prepare(
526-
"INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"
527-
);
528-
$insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
529-
$insertStmt->bindValue(':data', '', \PDO::PARAM_LOB);
530-
$insertStmt->bindValue(':lifetime', 0, \PDO::PARAM_INT);
531-
$insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
532-
$insertStmt->execute();
533-
} catch (\PDOException $e) {
534-
// Catch duplicate key error because other connection created the session already.
535-
// It would only not be the case when the other connection destroyed the session.
536-
if (0 === strpos($e->getCode(), '23')) {
537-
// Retrieve finished session data written by concurrent connection. SELECT
538-
// FOR UPDATE is necessary to avoid deadlock of connection that starts reading
539-
// before we write (transform intention to real lock).
540-
$selectStmt->execute();
541-
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
542-
543-
if ($sessionRows) {
544-
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
522+
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
523+
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
524+
// until other connections to the session are committed.
525+
try {
526+
$insertStmt = $this->pdo->prepare(
527+
"INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"
528+
);
529+
$insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
530+
$insertStmt->bindValue(':data', '', \PDO::PARAM_LOB);
531+
$insertStmt->bindValue(':lifetime', 0, \PDO::PARAM_INT);
532+
$insertStmt->bindValue(':time', time(), \PDO::PARAM_INT);
533+
$insertStmt->execute();
534+
} catch (\PDOException $e) {
535+
// Catch duplicate key error because other connection created the session already.
536+
// It would only not be the case when the other connection destroyed the session.
537+
if (0 === strpos($e->getCode(), '23')) {
538+
// Retrieve finished session data written by concurrent connection by restarting the loop.
539+
// We have to start a new transaction as a failed query will mark the current transaction as
540+
// aborted in PostgreSQL and disallow further queries within it.
541+
$this->rollback();
542+
$this->beginTransaction();
543+
continue;
545544
}
546545

547-
return '';
546+
throw $e;
548547
}
549-
550-
throw $e;
551548
}
552-
}
553549

554-
return '';
550+
return '';
551+
} while (true);
555552
}
556553

557554
/**

src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,4 +362,8 @@ public function prepare($statement, $driverOptions = array())
362362
public function beginTransaction()
363363
{
364364
}
365+
366+
public function rollBack()
367+
{
368+
}
365369
}

0 commit comments

Comments
 (0)
0