I've created a PHP extension with SWIG and everything works fine, but I'm observing some strange garbage collection behavior when chaining method calls. For example, this works:
$results = $response->results();
$row = $results->get(0)->iterator()->next();
printf('%s %s' . "\n", $row->getString(0), $row->getString(1));
But this seg faults:
$row = $response->results()->get(0)->iterator()->next();
printf('%s %s' . "\n", $row->getString(0), $row->getString(1));
The only difference is that the first creates $results
, while the second chains the calls together.
SWIG actually only exposes functions to PHP and generates PHP proxy classes to interact with them. These proxy classes basically hold a resource that is passed to each of the exposed functions along with whatever other arguments those functions would normally take. Thinking that maybe these proxy classes were the problem, I reworked the code to bypass them and instead use the exposed functions directly. As before, this works:
$results = InvocationResponse_results($response->_cPtr);
$row = TableIterator_next(Table_iterator(Tables_get($results, 0)));
printf('%s %s' . "\n", Row_getString($row, 0), Row_getString($row, 1));
And again, this seg faults:
$row = TableIterator_next(Table_iterator(Tables_get(InvocationResponse_results($response->_cPtr), 0)));
printf('%s %s' . "\n", Row_getString($row, 0), Row_getString($row, 1));
Again, the only difference is that the first creates $results
, while the second chains the calls together.
At this point, I spent awhile debugging in gdb/valgrind and determined that the destructor for what InvocationResponse_results
returns is called too early when chaining calls together. To observe, I inserted std::cout
statements at the tops of the exposed C++ functions and their destructors. This is the output without chaining:
InvocationResponse_results()
Tables_get()
Table_iterator()
TableIterator_next()
__wrap_delete_TableIterator
Row_getString()
Row_getString()
Hola Mundo
---
__wrap_delete_InvocationResponse
__wrap_delete_Row
__wrap_delete_Tables
I printed ---
at the end of the script to be able to differentiate what happens during the script's execution and what happens after. Hola Mundo
is from printf
. The rest is from C++. As you can see, everything gets called in the expected order. Destructors are only called after the script's execution, though the TableIterator
destructor is called earlier than I would have expected. However, this has not caused any problems and is likely unrelated. Now compare this to the output with chaining:
InvocationResponse_results()
Tables_get()
__wrap_delete_Tables
Table_iterator()
TableIterator_next()
__wrap_delete_TableIterator
Row_getString()
Segmentation fault (core dumped)
Without the return value of InvocationResponse_results
being saved into $results
, it is happily garbage collected before execution even gets out of the call chain (between Tables_get
and Table_iterator
) and this quickly causes problems down the road, ultimately leading to a seg fault.
I also inspected reference counts using xdebug_debug_zval()
in various places, but didn't spot anything unusual. Here is its output on $results
and $row
without chaining:
results: (refcount=1, is_ref=0)=resource(18) of type (_p_std__vectorT_voltdb__Table_t)
row: (refcount=1, is_ref=0)=resource(21) of type (_p_voltdb__Row)
And on $row
with chaining:
row: (refcount=1, is_ref=0)=resource(21) of type (_p_voltdb__Row)
I've spent a couple days on this now and I'm just about out of ideas, so really any insight on how to go about solving this would be greatly appreciated.