1

We have a complex PHP program that's leaking one object. Is there any way to get callback every time reference count is increased so that I could log the stack trace of all locations that take a reference? The program is using RAII programming model and this leak is causing the destructor to be run much much later than expected (in worst case during process exit).

The code basically is

while (!$done)
{
  # create database connection for each try (serializable transactions with fallback servers)
  $connection = ...;
  $transaction = $connection->newTransaction(...);
  try
  {
    doStuffWithTransaction($transaction);
    $transaction->commit();
    $done = true;
    ...; # additional best-effort cleanup routines
  }
  catch (TransactionMustRetry $e)
  {
    $transaction->rollback();
  }
  catch (\Throwable $e)
  {
    ...
  }
  $connection->disconnect();
}

and $transaction is supposed to automatically rollback if \Throwable is thrown by some nested routine within doStuffWithTransaction(). However, the $transaction has refcount=2 before call to this function and 3 when catching the \Throwable which results in the rollback not getting rolled back automatically.

How to figure which code has acquired reference to $transaction?

Mikko Rantalainen
  • 14,132
  • 10
  • 74
  • 112
  • Did you look in `doStuffWithTransaction()`? – AbraCadaver Apr 20 '20 at 12:48
  • Yeah, it goes to recursively create 10000+ objects doing all kind of stuff. Not really easy to debug why it would leak a reference somewhere. – Mikko Rantalainen Apr 20 '20 at 12:55
  • You could create a kind of proxy for the transaction. Create a proxy class which implements every method of `$transaction` and delegates the call to an actual transaction. When you catch your throwable, you can remove the reference within the proxy to the transaction, forcing the refcount inside the application to 0. This doesn't hold up if the leaked reference is a statement/query/etc. returned by the transaction which itself contains a reference to the transsaction – JensV Apr 20 '20 at 12:57
  • I actually just figured the cause for this specific problem: the variable `$e` stays alive for the next loop iteration and contains the stack of the last exception. Because the stack contains the parameters to methods and functions, the missing reference to `$transaction` is nested inside the `$e`. Adding `unset($e);` immediately after logging the exception fixed the leak. However, I'm still looking for a method to debug this class of problems. – Mikko Rantalainen Apr 20 '20 at 12:57
  • @JensV: that would be pretty good workaround if I couldn't fix the actual code. However, I'm looking for a method to find the actual leak. With the method you suggest, the proxy would still leak but one could avoid leaking the transaction object. – Mikko Rantalainen Apr 20 '20 at 13:00
  • 1
    You are absolutely correct. I find anything related to seeing the owners of the reference, I can only come up with creating some other workarounds with weak references :P – JensV Apr 20 '20 at 13:02

0 Answers0