Gc.finalise について(続き)

じゃ、ここでパズルです。値 v とその finaliser f があります。v に対して GC が起こった場合 f v を呼び出します。プログラム終了時までに v が GC されなかった場合は、at_exit を使ってやはり f v を呼び出したい。そんな関数 finalise_or_at_exit が定義できますか? この finalise_or_at_exit 関数は、場合によっては非常に沢山の値に対して使われるかもしれませんから、不必要なメモリリークは避けなければいけません。

というちょっと的外れなパズルを出して恥ずかしい限りですが、私が考えていたのはこんなのです:

module M : sig
  type 'a t
  val finalise_or_at_exit : ('a -> unit) -> 'a -> 'a t
end = struct
  let finalisers_at_exit = Doubly_linked.create ()

  let now_at_exit = ref false

  let _ = 
    at_exit (fun () -> 
      now_at_exit := true;
      Doubly_linked.iter ~f:(fun f -> f ()) finalisers_at_exit)
  
  type 'a t = { elt : (unit -> unit) Doubly_linked.Elt.t; value : 'a }

  let finalise outer =
    if not !now_at_exit then begin
      Doubly_linked.Elt.value outer.elt ();
      Doubly_linked.remove finalisers_at_exit outer.elt
    end
      
  let finalise_or_at_exit f v =
    let elt = Doubly_linked.insert_last finalisers_at_exit (fun () -> 
      f v)
    in
    let wrapped = { elt = elt; value = v } in
    Gc.finalise finalise outer;
    wrapped
end

対象となる値 v の外側に殻 wrap をくっつけて、殻に対して Gc.finalise します。殻には f を入れておきますが、 polymorphic なインターフェースにしたいので、 (fun () - f v) という型変数のないクロージャにして殻に入れておく。この殻は doubly linked list に入れておきます。(doubly linked list の実装は、まあ判るよね) GC が殻に対して発生したら、クロージャを実行して dlist から取り除きます。at_exit になったら、dlist にのこったクロージャを実行。一度殻を作ったら、中身を取り出せないようにしなければいけません。
でも、id:osiire さんの at_exit Gc.full_major のほうが簡単ですね。at_exit が呼ばれるときはもう殆どの値は到達可能では無いはずだから、full_major して全ての到達可能な値を捜し出すのにもあまり時間がかかりません。といっても実験してみたら、 1G ノード full_major して GC するのに3秒かかったけど、、、上の方法なら、full_major の必要はないから早いです。でも、、、at_exit だからそれが気になる状況というのは実はあまりないと思われます。
というわけで、上のパズルはあまり意味が無かったんだけど、外殻に対して Gc.finalise しておけば、内側は到達可能でもok、というテクニックはいろんな所で使えます。