Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bug #53819 [Doctrine Messenger] Fix support for pgsql + pgbouncer. (j…
…wage) This PR was squashed before being merged into the 6.4 branch. Discussion ---------- [Doctrine Messenger] Fix support for pgsql + pgbouncer. | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | | License | MIT ## Problem When you use PgBouncer in front of a PostgreSQL server with transaction pooling mode enabled, the `INSERT` and the `lastInsertId()` happen in separate transactions which are separate connections/sessions when using PgBouncer. So the call to `lastInsertId()` fails with the following exception: ``` [Doctrine\DBAL\Exception\DriverException (7)] An exception occurred in the driver: SQLSTATE[55000]: Object not in prerequisite state: 7 ERROR: lastval is not yet defined in this session Exception trace: at /app/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php:87 Doctrine\DBAL\Driver\API\PostgreSQL\ExceptionConverter->convert() at /app/vendor/doctrine/dbal/src/Connection.php:1938 Doctrine\DBAL\Connection->handleDriverException() at /app/vendor/doctrine/dbal/src/Connection.php:1886 Doctrine\DBAL\Connection->convertException() at /app/vendor/doctrine/dbal/src/Connection.php:1253 Doctrine\DBAL\Connection->lastInsertId() at /app/vendor/symfony/doctrine-messenger/Transport/Connection.php:156 Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection->send() at /app/vendor/symfony/doctrine-messenger/Transport/DoctrineSender.php:46 Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineSender->send() at /app/vendor/symfony/doctrine-messenger/Transport/DoctrineTransport.php:72 Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport->send() at /app/vendor/symfony/messenger/EventListener/SendFailedMessageForRetryListener.php:81 Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener->onMessageFailed() at /app/vendor/symfony/event-dispatcher/EventDispatcher.php:220 Symfony\Component\EventDispatcher\EventDispatcher->callListeners() at /app/vendor/symfony/event-dispatcher/EventDispatcher.php:56 Symfony\Component\EventDispatcher\EventDispatcher->dispatch() at /app/vendor/symfony/messenger/Worker.php:198 Symfony\Component\Messenger\Worker->ack() at /app/vendor/symfony/messenger/Worker.php:174 Symfony\Component\Messenger\Worker->handleMessage() at /app/vendor/symfony/messenger/Worker.php:109 ``` ## Solution Wrap the `INSERT` and `lastInsertId()` with a single transaction, then when `lastInsertId()` is called, it will be within the same session that the message was inserted in. In addition, this PR adds the ability to use PostgresSQL `RETURNING id` clause instead of calling `lastInsertId()` so we can get the id of the inserted message in one operation instead of two. TODO: - [x] Add test for table not found scenario when inserting a message. - [x] Add tests for when lastInsertId returns false, int and string. - [x] Is there a place where I can write an integration test for this behavior that already exists? - [x] Investigate using pgsql RETURNING clause to simplify this. The insert can return the id after the message is inserted. - [ ] Squash commits to one clean commit before merge. Commits ------- c5830b4 [Doctrine Messenger] Fix support for pgsql + pgbouncer.
- Loading branch information
Showing
7 changed files
with
351 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Fixtures/pgbouncer/pgbouncer.ini
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[databases] | ||
postgres = host=localhost port=5432 user=postgres dbname=postgres pool_mode=transaction | ||
|
||
[pgbouncer] | ||
logfile = /var/log/postgresql/pgbouncer.log | ||
pidfile = /var/run/postgresql/pgbouncer.pid | ||
listen_addr = localhost | ||
listen_port = 6432 | ||
unix_socket_dir = /var/run/postgresql | ||
auth_type = md5 | ||
auth_file = /etc/pgbouncer/userlist.txt | ||
max_client_conn = 20 | ||
default_pool_size = 20 |
1 change: 1 addition & 0 deletions
1
src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Fixtures/pgbouncer/userlist.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"postgres" "md532e12f215ba27cb750c9e093ce4b5127" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
.../Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlPgbouncerIntegrationTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport; | ||
|
||
use Doctrine\DBAL\Configuration; | ||
use Doctrine\DBAL\Connection; | ||
use Doctrine\DBAL\DriverManager; | ||
use Doctrine\DBAL\Schema\AbstractSchemaManager; | ||
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; | ||
use Doctrine\DBAL\Tools\DsnParser; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage; | ||
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\PostgreSqlConnection; | ||
|
||
/** | ||
* This tests using PostgreSqlConnection with PgBouncer between pgsql and the application. | ||
* | ||
* @requires extension pdo_pgsql | ||
* | ||
* @group integration | ||
*/ | ||
class DoctrinePostgreSqlPgbouncerIntegrationTest extends TestCase | ||
{ | ||
private Connection $driverConnection; | ||
private PostgreSqlConnection $connection; | ||
|
||
public function testSendAndGetWithAutoSetupEnabledAndNotSetupAlready() | ||
{ | ||
$this->connection->send('{"message": "Hi"}', ['type' => DummyMessage::class]); | ||
|
||
$encoded = $this->connection->get(); | ||
$this->assertSame('{"message": "Hi"}', $encoded['body']); | ||
$this->assertSame(['type' => DummyMessage::class], $encoded['headers']); | ||
|
||
$this->assertNull($this->connection->get()); | ||
} | ||
|
||
public function testSendAndGetWithAutoSetupEnabledAndSetupAlready() | ||
{ | ||
$this->connection->setup(); | ||
|
||
$this->connection->send('{"message": "Hi"}', ['type' => DummyMessage::class]); | ||
|
||
$encoded = $this->connection->get(); | ||
$this->assertSame('{"message": "Hi"}', $encoded['body']); | ||
$this->assertSame(['type' => DummyMessage::class], $encoded['headers']); | ||
|
||
$this->assertNull($this->connection->get()); | ||
} | ||
|
||
protected function setUp(): void | ||
{ | ||
if (!$host = getenv('PGBOUNCER_HOST')) { | ||
$this->markTestSkipped('Missing PGBOUNCER_HOST env variable'); | ||
} | ||
|
||
$url = "pdo-pgsql://postgres:password@$host"; | ||
$params = class_exists(DsnParser::class) ? (new DsnParser())->parse($url) : ['url' => $url]; | ||
$config = new Configuration(); | ||
if (class_exists(DefaultSchemaManagerFactory::class)) { | ||
$config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); | ||
} | ||
|
||
$this->driverConnection = DriverManager::getConnection($params, $config); | ||
$this->connection = new PostgreSqlConnection(['table_name' => 'queue_table'], $this->driverConnection); | ||
} | ||
|
||
protected function tearDown(): void | ||
{ | ||
$this->createSchemaManager()->dropTable('queue_table'); | ||
$this->driverConnection->close(); | ||
} | ||
|
||
private function createSchemaManager(): AbstractSchemaManager | ||
{ | ||
return method_exists($this->driverConnection, 'createSchemaManager') | ||
? $this->driverConnection->createSchemaManager() | ||
: $this->driverConnection->getSchemaManager(); | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
...nt/Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlRegularIntegrationTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport; | ||
|
||
use Doctrine\DBAL\Configuration; | ||
use Doctrine\DBAL\DriverManager; | ||
use Doctrine\DBAL\Schema\AbstractSchemaManager; | ||
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; | ||
use Doctrine\DBAL\Tools\DsnParser; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage; | ||
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection; | ||
|
||
/** | ||
* This tests a using Doctrine PostgreSql connection without using PostgreSqlConnection | ||
* that gets used when use_notify is enabled. | ||
* | ||
* @requires extension pdo_pgsql | ||
* | ||
* @group integration | ||
*/ | ||
class DoctrinePostgreSqlRegularIntegrationTest extends TestCase | ||
{ | ||
private \Doctrine\DBAL\Connection $driverConnection; | ||
private Connection $connection; | ||
|
||
public function testSendAndGetWithAutoSetupEnabledAndNotSetupAlready() | ||
{ | ||
$this->connection->send('{"message": "Hi"}', ['type' => DummyMessage::class]); | ||
|
||
$encoded = $this->connection->get(); | ||
$this->assertSame('{"message": "Hi"}', $encoded['body']); | ||
$this->assertSame(['type' => DummyMessage::class], $encoded['headers']); | ||
|
||
$this->assertNull($this->connection->get()); | ||
} | ||
|
||
public function testSendAndGetWithAutoSetupEnabledAndSetupAlready() | ||
{ | ||
$this->connection->setup(); | ||
|
||
$this->connection->send('{"message": "Hi"}', ['type' => DummyMessage::class]); | ||
|
||
$encoded = $this->connection->get(); | ||
$this->assertSame('{"message": "Hi"}', $encoded['body']); | ||
$this->assertSame(['type' => DummyMessage::class], $encoded['headers']); | ||
|
||
$this->assertNull($this->connection->get()); | ||
} | ||
|
||
protected function setUp(): void | ||
{ | ||
if (!$host = getenv('POSTGRES_HOST')) { | ||
$this->markTestSkipped('Missing POSTGRES_HOST env variable'); | ||
} | ||
|
||
$url = "pdo-pgsql://postgres:password@$host"; | ||
$params = class_exists(DsnParser::class) ? (new DsnParser())->parse($url) : ['url' => $url]; | ||
$config = new Configuration(); | ||
if (class_exists(DefaultSchemaManagerFactory::class)) { | ||
$config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); | ||
} | ||
|
||
$this->driverConnection = DriverManager::getConnection($params, $config); | ||
$this->connection = new Connection(['table_name' => 'queue_table'], $this->driverConnection); | ||
} | ||
|
||
protected function tearDown(): void | ||
{ | ||
$this->createSchemaManager()->dropTable('queue_table'); | ||
$this->driverConnection->close(); | ||
} | ||
|
||
private function createSchemaManager(): AbstractSchemaManager | ||
{ | ||
return method_exists($this->driverConnection, 'createSchemaManager') | ||
? $this->driverConnection->createSchemaManager() | ||
: $this->driverConnection->getSchemaManager(); | ||
} | ||
} |
Oops, something went wrong.