Skip to content
Merged
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
17 changes: 13 additions & 4 deletions packages/pools/src/Pools/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -376,19 +376,28 @@ public function pop(): Connection
});

if ($shouldCreateConnections) {
$reserved = true;

try {
$connection = $this->createConnection();
$this->pool->synchronized(function () use ($connection): void {
$this->active[$connection->getID()] = $connection;
});
$reserved = false;

return $connection;
} catch (\Exception $e) {
$this->pool->synchronized(function (): void {
$this->connectionsCreated--;
});
} catch (\Throwable $e) {
// Throwable, not Exception: the init callback is caller
// supplied, so an Error holds the slot just the same.
// Don't throw immediately - fall through to try getting
// an existing connection from the pool
$lastException = $e;
} finally {
if ($reserved) {
$this->pool->synchronized(function (): void {
$this->connectionsCreated--;
});
}
}
}

Expand Down
29 changes: 29 additions & 0 deletions packages/pools/tests/Pools/Scopes/PoolTestScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,35 @@ public function testPopRetriesAfterConnectionCreationFailure(): void
});
}

public function testPopReleasesReservedSlotWhenCreationThrowsError(): void
{
$this->execute(function (): void {
// pop() reserves capacity before the connection exists. An Error
// escaping the catch used to keep that slot, draining the pool one
// failed pop at a time.
$pool = new Pool($this->getAdapter(), 'test-error-leak', 2, function (): string {
throw new \TypeError('Connection init failed');
});
$pool->setReconnectAttempts(1);
$pool->setReconnectSleep(0);
$pool->setRetryAttempts(1);
$pool->setRetrySleep(0);

// More attempts than slots, so a kept reservation shows up as lost
// capacity by the end.
for ($i = 0; $i < 5; $i++) {
try {
$pool->pop();
$this->fail('Should have thrown');
} catch (Exception $e) {
$this->assertInstanceOf(\TypeError::class, $e->getPrevious());
}
}

$this->assertSame(2, $pool->count());
});
}

public function testPoolEmptyErrorIncludesActiveCount(): void
{
$this->execute(function (): void {
Expand Down
Loading