Limits on OCaml finalizer execution order across runtime versions
0 reputation · 19 Aug 2023, 15:16 UTC
0 reputation · 19 Aug 2023, 15:16 UTC
OCaml’s language manual specifies that finalizers are executed when the garbage collector reclaims a value, but it does not define the order in which multiple finalizers run.
This implementation‑defined behavior can vary between OCaml releases (e.g., 4.12, 4.14, 5.0) and between compilation modes (native code vs. bytecode) or GC settings such as heap size.
Goal: understand whether any ordering guarantees exist for finalizers when objects become unreachable during a single GC cycle, and how runtime configuration influences that order.
Does the OCaml runtime guarantee any relative order of finalizers for objects collected in the same GC safepoint? Does compiling to native code versus bytecode affect the observed sequence? Can altering OCAMLRUNPARAM (e.g., minor heap size) change the finalizer execution order?
31775 reputation · 20 Aug 2023, 02:06 UTC
The OCaml runtime provides no guarantee regarding the relative order of finalizers for objects collected during the same GC safepoint. The execution sequence is implementation-defined and can vary based on the OCaml version, compilation mode (native vs. bytecode), and runtime configuration.
Gc.full_major() or handled via at_exit.In many practical scenarios, finalizers for objects reclaimed during a single minor GC appear to execute in allocation order. This is likely because the young-generation scan typically proceeds in the order of allocation. However, this is an implementation artifact, not a guarantee. This pattern can be disrupted by:
OCAMLRUNPARAM settings.While both runtimes utilize the same general GC algorithm, differences in stack layout and code generation can alter the exact moment objects become unreachable. This may change the set of objects collected in a specific cycle, thereby altering the observed sequence of finalizers.
Adjusting OCAMLRUNPARAM (e.g., changing the minor heap size with s) modifies the frequency and timing of collections. While this can change which objects are grouped together for reclamation, it does not introduce any formal ordering guarantees.
To observe this behavior in your specific environment, you can use a scoped test program that records the order of finalizer execution:
let () =
let order = ref [] in
let create_obj id =
let obj = object end in
Gc.finalise (fun () -> order := id :: !order) obj;
obj
in
let _ = create_obj 1 in
let _ = create_obj 2 in
let _ = create_obj 3 in
Gc.full_major ();
List.iter (Printf.printf "%d\n") (List.rev !order)
Run this test across different versions (e.g., 4.14, 5.0) and compilation modes (ocamlc vs ocamlopt) to verify the lack of consistent ordering.
Avoid using finalizers for any logic that requires a specific destruction order (e.g., closing a file before closing the device handle). Instead, use explicit cleanup functions or reference counting.
Use comments to ask for clarification. Post a solution as an answer.
31,775 reputation · 19 Aug 2023, 18:36 UTC
While the OCaml language manual leaves finalizer order unspecified, the runtime’s internal queues differ between the two major modes.
OCAMLRUNPARAM (e.g., minor heap size or hybrid GC) only affects when a GC is triggered; it does not reorder finalizers collected in the same cycle.Gc.full_major() on both native and bytecode builds will show the LIFO vs. FIFO pattern.