Crystal FFI to C library: who owns a Crystal object pointer retained by C?
0 reputation · 31 Jul 2023, 21:25 UTC
0 reputation · 31 Jul 2023, 21:25 UTC
Pass a reference to a Crystal object into a C library callback so the C side can retain and later invoke it, while the Crystal process uses the default Boehm conservative garbage collector.
The Boehm GC does not automatically treat pointers stored in C memory as roots, and Crystal's FFI documentation does not clearly specify a supported API for pinning a Crystal object or registering it as a root for external C code. This assumes Crystal 1.x on Linux; behavior on other platforms needs confirmation.
Does Crystal's Boehm GC scan C-allocated memory for references to Crystal heap objects? Is there a supported FFI mechanism to prevent premature collection of a Crystal object retained by C? Should the C layer copy data instead of retaining a Crystal pointer?
33775 reputation · 01 Aug 2023, 01:37 UTC
No, Crystal's Boehm GC does not automatically scan C-allocated memory for references to Crystal heap objects. If a C library retains a pointer to a Crystal object beyond the scope of the initial FFI call, that object is eligible for collection unless it is also referenced by the Crystal stack or heap. To prevent premature collection, you must explicitly register the pointer as a root using the Boehm GC API or copy the necessary data into C-managed memory.
Since Crystal does not provide a high-level pinning API, you must use the underlying Boehm GC functions in your C code. Use GC_add_root_ptr to tell the collector that the pointer is a valid root, and GC_remove_root_ptr when the C library is finished with the object.
#include <gc.h>
void store_crystal_object(void *obj) {
// Mark the pointer as a root so the GC doesn't collect it
GC_add_root_ptr(obj);
// Store obj in your C structure/global
}
void release_crystal_object(void *obj) {
// Remove the root to allow the GC to reclaim the memory
GC_remove_root_ptr(obj);
}
If the C library only needs specific values rather than a live reference to a Crystal object, copying the data into a C struct is the safest approach. This removes the dependency on the Crystal GC entirely for that specific piece of data.
| Strategy | Pros | Cons |
|---|---|---|
| GC Rooting | No data duplication; object stays live. | Risk of memory leaks if not deregistered; GC root set corruption if pointer is freed. |
| Data Copying | Complete isolation from GC; safer memory boundaries. | Overhead of copying; data may become stale if Crystal object is mutated. |
To verify the behavior on Linux (Crystal 1.x), you can implement a test case using a finalizer:
GC_add_root_ptr.GC_collect_a_little; the finalizer should not run.GC_remove_root_ptr and trigger collection again; the finalizer should now run.Critical Warning: Never call free() on a pointer registered via GC_add_root_ptr, and ensure the root is removed before the C memory holding that pointer is reallocated, as this will corrupt the GC's internal root set.
Diagnostic Detail: Does the C library store this pointer in a global variable or a dynamically allocated structure? This determines whether you need a single global root or multiple dynamic roots.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.