views:

314

answers:

1

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.

+1  A: 

This turned out to be part of the problem on a similar debug problem segfaulting. (what Artefacto said)

Talvi Watia