Calling OCaml from C - random segfaults

If I pass a function from OCaml to C, and then store a reference to that OCaml function (to call it multiple times) in C do I need to make some special bookkeeping:
a) when I pass the function?
b) when I call it?

I get random segfaults when calling the OCaml closure for let’s say 1000th time. Can there be any race condition that interferes with gc?

GC moves things around in memory. If you save a pointer and return to OCaml, the pointer can very easily become invalid when some future GC is performed. The pointer will point to where the closure used to be, but something different (or nothing) could be there now. You can prevent this by marking the location where you have stored the pointer as a “global root”. Then the GC will update your location to contain the new pointer value.

This is Rule 4 of living in harmony with the garbage collector:

Global variables containing values must be registered with the garbage collector using the caml_register_global_root function.

You can find this in Chapter 19 of the OCaml manual.

5 Likes

Thank you. It indeed fixed the bug.

If I understand correctly, using the Callback.register approach would solve my problem, too. Do these two have different performance characteristics?

It would also solve it, but caml_named_value is not a constant-time operation, so you’d be making every access slower. A generational root is the proper mechanism for storing this pointer (since you won’t be changing the value, you may as well use caml_register_generational_global_root)

2 Likes

An alternative, mentioned specifically in the manual chapter, is to cache the return value of caml_named_value but not the value it points to. That is, you may cache the value* but not the value (since the latter may be moved by the GC).

Example from the manual:

void call_caml_f(int arg)
{
    static value * closure_f = NULL;
    if (closure_f == NULL) {
        /* First time around, look up by name */
        closure_f = caml_named_value("test function");
    }
    caml_callback(*closure_f, Val_int(arg));
}
2 Likes

That’s a good point, which I’d forgotten! FWIW, note that Callback.register uses caml_register_global_root rather than caml_register_generational_global_root.

Thanks for the detailed answers!