8000 [Session] fix PDO transaction aborted under PostgreSQL · symfony/symfony@f8eefa0 · GitHub
[go: up one dir, main page]

Skip to content

Commit f8eefa0

Browse files
committed
[Session] fix PDO transaction aborted under PostgreSQL
1 parent 86552ea commit f8eefa0

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
@@ -510,54 +510,51 @@ private function doRead($sessionId)
510510
$selectSql = $this->getSelectSql();
511511
$selectStmt = $this->pdo->prepare($selectSql);
512512
$selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
513-
$selectStmt->execute();
514513

515-
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
514+
do {
515+
$selectStmt->execute();
516+
$sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
516517

517-
if ($sessionRows) {
518-
if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
519-
$this->sessionExpired = true;
518+
if ($sessionRows) {
519+
if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
520+
$this->sessionExpired = true;
520521

521-
return '';
522-
}
522+
return '';
523+
}
523524

524-
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
525-
}
525+
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
526+
}
526527

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

553-
return '';
552+
throw $e;
554553
}
555-
556-
throw $e;
557554
}
558-
}
559555

560-
return '';
556+
return '';
557+
} while (true);
561558
}
562559

563560
/**

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