Skip to content

Segfault: coroutine finalizing with an open transaction releases into a torn-down pool #202

Description

@EdmondDantes

Summary

A coroutine that ends without committing pins its pooled connection (in_txn). When the PDO object is then destroyed, the pool teardown and the coroutine's finalize callback both try to hand that connection back, and the process segfaults.

Seven lines, no framework, 100% reproducible here.

Reproducer

<?php
$dsn = "mysql:host=localhost;unix_socket=/var/run/mysqld/mysqld.sock;dbname=test;charset=utf8";
$pdo = new PDO($dsn, "test", "test", [
    PDO::ATTR_ERRMODE      => PDO::ERRMODE_SILENT,
    PDO::ATTR_POOL_ENABLED => true,
    PDO::ATTR_POOL_MIN     => 0,
    PDO::ATTR_POOL_MAX     => 1,
]);

$a = Async\spawn(function () use ($pdo) { $pdo->beginTransaction(); });   // no commit
Async\await($a);
unset($pdo);
$ php m7.php
Segmentation fault (core dumped)     # exit 139

Conditions

Both must hold:

  1. binding->conn != NULL at coroutine finalize — i.e. the slot is pinned by an open transaction or a live statement, so it is not returned during the coroutine's normal run.
  2. The finalizing coroutine holds the last reference to the PDO object, so pdo_pool_destroy runs from inside that teardown.

Reading of it

pdo_pool_binding_on_coroutine_finish releases the still-held connection, and zend_async_pool_release pushes it into the pool's idle buffer — but the pool is being destroyed on the same stack, so the buffer is already gone.

In debug builds this lands on ZEND_ASSERT(buffer->data != NULL) in ext/async/pool.c. Release builds compile that assert out, so the same path writes through a freed/NULL buffer — heap corruption rather than a clean abort.

pdo_pool_binding_on_coroutine_finish also dereferences binding->dbh->pool_bindings after the release call, and binding->dbh can have been invalidated in the meantime.

Suggested fix

Re-check pool/binding->dbh liveness after the release, or hold a reference on the pool event across the coroutine-finalize release so the teardown cannot complete underneath it.

Environment

PHP        8.6.0-dev (cli) (ZTS DEBUG TrueAsync ABI v0.24.0)
true_async 0.7.10
OS         Linux 6.6.114.1-microsoft-standard-WSL2

Found while auditing the pool for #200. The release-at-finalize structure predates that fix.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions