Skip to content

Bug: reconnect() keeps the old transaction on a replaced MySQLi socket #10648

Open

Description

@KevinBlakeLopez

PHP Version

8.5

CodeIgniter4 Version

4.7.0 (measured). Still present on develop at df8b96bee (4.7.6-dev) by source read: BaseConnection::reconnect() still only calls close() and initialize() when ping() is false.

CodeIgniter4 Installation Method

Composer (as dependency to an existing project)

Which operating systems have you tested for this bug?

macOS

Which server did you use?

cli

Environment

testing

Database

MySQL, MySQLi driver. No production failure has been observed. This is a measured framework defect.

What happened?

reconnect() during an open transaction replaces the socket and leaves CodeIgniter's transaction state pointing at the transaction that died with the old socket.

Measured on PHP 8.5.6, CodeIgniter 4.7.0, MySQLi:

  1. SET SESSION wait_timeout = 1, then sleep 3 seconds.
  2. ping() is false.
  3. reconnect() runs.
  4. transDepth stays 1.
  5. transStatus() stays true. ping() uses simpleQuery(), and simpleQuery() does not call handleTransStatus(). A dead-socket ping therefore does not flip the flag. query() does call handleTransStatus().
  6. The row inserted before the timeout is already gone (the server rolled that transaction back when it closed the socket).
  7. transComplete() returns true.
  8. The row count stays 0.

A true return from transComplete() means CodeIgniter committed a transaction that the new socket does not have. The insert is not in the table.

BaseConnection::reconnect():

public function reconnect()
{
    if ($this->ping() === false) {
        $this->close();
        $this->initialize();
    }
}

Neither close() nor initialize() clears transDepth or transStatus.

The same reconnect() body is on the 4.8 branch (e710fc3d9, source read). This was not re-run there.

Steps to Reproduce

$db = db_connect();
$db->query('CREATE TABLE IF NOT EXISTS reconnect_probe (id INT PRIMARY KEY AUTO_INCREMENT, note VARCHAR(32))');
$db->query('DELETE FROM reconnect_probe');

$db->transStart();
$db->table('reconnect_probe')->insert(['note' => 'before-timeout']);

$db->query('SET SESSION wait_timeout = 1');
sleep(3);

// Preconditions, measured: ping is false, depth is 1, status is true.
$db->ping();
$db->reconnect();

$completed = $db->transComplete();
$count     = $db->table('reconnect_probe')->countAllResults();

Observed: $db->transDepth is 1 after reconnect(), transStatus() is true, $completed is true, $count is 0.

Expected Output

A replaced socket must not keep the old transDepth or transStatus. transComplete() after that reconnect() must not return true. The pre-timeout insert stays uncommitted, which is already the server outcome once the old socket is gone.

Anything else?

Searched issues and pull requests, open and closed, on codeigniter4/CodeIgniter4 before opening this. Nothing already covers this defect.

Neighbors that do not:

ping() → simpleQuery() → execute() never calls handleTransStatus(), so a failed ping cannot be what clears the transaction flag. The reset belongs in reconnect() when the socket is replaced.

Activity

  1. added 2 commits that reference this issue on Oct 22, 2026
    6369d6b
    1a7668c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions