Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/Internal/PgSqlHandle.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,17 @@ public function statementDeallocate(string $name): void
return; // Statement already deallocated.
}

$this->query(\sprintf("DEALLOCATE %s", $name));
try {
// Send the DEALLOCATE without building a result object: an error result
// must not be processed through createResult(), whose fatal-error drain
// would consume results belonging to an operation dispatched after this
// one and leave that operation awaiting a response forever.
$this->send(\pg_send_query(...), \sprintf("DEALLOCATE %s", $name));
} catch (\Throwable) {
// Ignore failures: the server may have already dropped the statement,
// e.g. via the DISCARD ALL issued when the pool resets connections.
}

unset($this->statements[$name]);
});
$storage->future->ignore();
Expand Down
9 changes: 8 additions & 1 deletion src/Internal/PqHandle.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,16 @@ public function statementDeallocate(string $name): void
return; // Statement already deallocated.
}

$this->send(null, $statement->deallocateAsync(...));
try {
$this->send(null, $statement->deallocateAsync(...));
} catch (\Throwable) {
// Ignore failures: the server may have already dropped the statement,
// e.g. via the DISCARD ALL issued when the pool resets connections.
}

unset($this->statements[$name]);
});
$storage->future->ignore();
}

#[\Override]
Expand Down
51 changes: 51 additions & 0 deletions test/StatementDeallocateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types=1);

namespace Amp\Postgres\Test;

use Amp\PHPUnit\AsyncTestCase;
use Amp\Postgres\PostgresConfig;
use Amp\Postgres\PostgresConnectionPool;
use Revolt\EventLoop;

/**
* @requires extension pgsql
*/
class StatementDeallocateTest extends AsyncTestCase
{
public function testDeallocateRacingConnectionResetDoesNotBreakSubsequentOperations(): void
{
if (EventLoop::getDriver()->getHandle() instanceof \EvLoop) {
$this->markTestSkipped("ext-pgsql is not compatible with pecl-ev");
}

$this->setTimeout(5);

$pool = new PostgresConnectionPool(
PostgresConfig::fromString('host=localhost user=postgres password=postgres'),
1,
);

try {
$statement = $pool->prepare('SELECT 1 AS value');
$result = $statement->execute();
\iterator_to_array($result);

// Dropping the statement and result queues a DEALLOCATE for the prepared
// statement, while the pool's DISCARD ALL reset on the next checkout drops
// it server-side. The failing DEALLOCATE must not consume responses that
// belong to operations dispatched after it.
unset($result, $statement);

for ($i = 1; $i <= 3; ++$i) {
$rows = \iterator_to_array($pool->query(\sprintf('SELECT %d AS value', $i)));
self::assertSame($i, $rows[0]['value']);
}

$statement = $pool->prepare('SELECT 2 AS value');
$rows = \iterator_to_array($statement->execute());
self::assertSame(2, $rows[0]['value']);
} finally {
$pool->close();
}
}
}
Loading