OCaml 5: forcing objects to be collected and finalized

In a real set of bindings we have a C finalizer that closes the handle automatically. When testing this we have code which looks something like the simplified example below:

let handle_closed = ref 0
let final _ = incr handle_closed

let () = assert (!handle_closed = 0)

let () =
  let obj = ref [] in
  Gc.finalise final obj

let () = Gc.full_major ()

(* this assert fails in OCaml 5, but not in OCaml <= 4 *)
let () = assert (!handle_closed = 1)

In OCaml 5, Gc.full_major does not collect the handle and run the finalizer (neither in the simplified example above, nor in the real program). However this test worked in OCaml <= 4.

I verified using OCAMLRUNPARAM that the GC is really being run 3 times. Is there some reason why the handle would not be collected? Is there a way to force it?

Note if it matters we have enabled flambda. I don’t know if that is related.

Here’s the real test: libguestfs/ocaml/t/guestfs_065_implicit_close.ml at master · libguestfs/libguestfs · GitHub

I would expect obj to be lifted to an immortal toplevel binding. It should be possible to prevent this by installing the finalizer in a noinline function and calling it. See section 8.2.

Without flambda, the following code doesn’t work:

let obj = ref []
let () = Gc.finalise obj

Here, even if obj does not appear in the .mli file, it is considered a toplevel value and never collected.
With flambda, the criterion for toplevel values is not syntactic anymore; anything not under a function can be turned into a toplevel value, including obj in your example.
So the solution usually looks like this:

let[@inline never][@local never] run () =
  (* Put all your code here *)

let () = run ()

We’ve actually had similar issues in the compiler’s testsuite; we decided that changing the testsuite to wrap every test in a function was the easiest solution, but if you want something better it should be possible to add a compilation flag for tests so that the compiler introduces a wrapper for you automatically. It would mean waiting for at least 5.2 though.

2 Likes

Thanks, that worked. The upstream fix: ocaml: Fix guestfs_065_implicit_close.ml for OCaml 5 · libguestfs/libguestfs@7d4e9c9 · GitHub

1 Like