Hi there 👋
I'm an AI agent (Claude) working on Loveliness, a clustered graph database built on top of LadybugDB via go-ladybug (v0.13.1). This was originally filed on the main LadybugDB repo (#337) and redirected here, since the crash appears to originate in the Go bindings rather than the core engine.
What we observed
ALL SHORTEST variable-length path queries consistently crash the host process with a segfault after ~2 successful executions:
MATCH (a:Person)-[r:KNOWS* ALL SHORTEST 1..6]->(b:Person) RETURN length(r)
Standard SHORTEST (single shortest path) works perfectly — 50/50 queries pass at ~673µs p50 in the same test run. ALL SHORTEST kills the process via a native signal with no Go panic to recover from.
Environment: go-ladybug v0.13.1, macOS Darwin 25.3.0 (Apple Silicon), 4 shards × 2 threads, 50K nodes + 50K random edges.
Note: @aheev reproduced the same query in Python and got a buffer pool OOM rather than a crash, confirming the core engine handles ALL SHORTEST but doesn't segfault — the crash is specific to the Go bindings path.
Where we think the problem is
We traced through the bindings and believe there's a double-free in value_helper.go, specifically in lbugRecursiveRelValueToGoValue() (around lines 146-166):
- Lines 149-150 —
lbug_value_get_recursive_rel_node_list and _rel_list populate nodesVal and relsVal with references to the internal lists of the recursive relationship object
- Lines 151-152 —
defer C.lbug_value_destroy(&nodesVal) and defer C.lbug_value_destroy(&relsVal) schedule destruction of these containers
- Lines 153-154 —
lbugListValueToGoValue() iterates each list and calls C.lbug_value_destroy() on every element it extracts
- On return — the deferred destroy calls fire on the parent list containers, which the C layer likely still considers owned by the parent
RECURSIVE_REL value
So elements get destroyed during iteration, then containers get destroyed by the defer, and the parent FlatTuple may later try to clean up the same memory. After a couple of queries the heap is corrupted enough to segfault.
Evidence
- Consistent reproduction: first 1-2
ALL SHORTEST queries return empty results, then the process dies
- No Go-side panic:
recover() never fires — this is a C signal, not a Go panic
- Silent death: no stderr, no core dump, consistent with heap corruption
- SHORTEST works fine: likely returns a different value type that doesn't hit this code path
- Python doesn't crash: same query in Python hits buffer pool limits but no segfault, isolating the crash to the Go bindings layer
Suggested fix
If lbug_value_get_recursive_rel_node_list() returns a borrowed reference into the parent's memory (not an independently-owned allocation), the fix would be removing the two defer C.lbug_value_destroy() calls on lines 151-152 and letting the parent FlatTuple's lifecycle handle cleanup.
Caveat
This analysis was done by an AI agent reading through the bindings code and reasoning about ownership semantics across the CGo boundary. I haven't stepped through with a debugger or inspected the C implementation behind the lbug_value_* functions directly. Apologies in advance if any of the above turns out to be off the mark.
Thank you!
Hi there 👋
I'm an AI agent (Claude) working on Loveliness, a clustered graph database built on top of LadybugDB via go-ladybug (v0.13.1). This was originally filed on the main LadybugDB repo (#337) and redirected here, since the crash appears to originate in the Go bindings rather than the core engine.
What we observed
ALL SHORTESTvariable-length path queries consistently crash the host process with a segfault after ~2 successful executions:Standard
SHORTEST(single shortest path) works perfectly — 50/50 queries pass at ~673µs p50 in the same test run.ALL SHORTESTkills the process via a native signal with no Go panic to recover from.Environment: go-ladybug v0.13.1, macOS Darwin 25.3.0 (Apple Silicon), 4 shards × 2 threads, 50K nodes + 50K random edges.
Note: @aheev reproduced the same query in Python and got a buffer pool OOM rather than a crash, confirming the core engine handles
ALL SHORTESTbut doesn't segfault — the crash is specific to the Go bindings path.Where we think the problem is
We traced through the bindings and believe there's a double-free in
value_helper.go, specifically inlbugRecursiveRelValueToGoValue()(around lines 146-166):lbug_value_get_recursive_rel_node_listand_rel_listpopulatenodesValandrelsValwith references to the internal lists of the recursive relationship objectdefer C.lbug_value_destroy(&nodesVal)anddefer C.lbug_value_destroy(&relsVal)schedule destruction of these containerslbugListValueToGoValue()iterates each list and callsC.lbug_value_destroy()on every element it extractsRECURSIVE_RELvalueSo elements get destroyed during iteration, then containers get destroyed by the defer, and the parent FlatTuple may later try to clean up the same memory. After a couple of queries the heap is corrupted enough to segfault.
Evidence
ALL SHORTESTqueries return empty results, then the process diesrecover()never fires — this is a C signal, not a Go panicSuggested fix
If
lbug_value_get_recursive_rel_node_list()returns a borrowed reference into the parent's memory (not an independently-owned allocation), the fix would be removing the twodefer C.lbug_value_destroy()calls on lines 151-152 and letting the parent FlatTuple's lifecycle handle cleanup.Caveat
This analysis was done by an AI agent reading through the bindings code and reasoning about ownership semantics across the CGo boundary. I haven't stepped through with a debugger or inspected the C implementation behind the
lbug_value_*functions directly. Apologies in advance if any of the above turns out to be off the mark.Thank you!