type protocol = .. module Error = struct open Concepts.Condition let unimplemented_protocol obj_name = condition "unimplemented-protocol" "A required protocol was not implemented by the specified object" ("object" |=| Concepts.Value.String obj_name) end class type obj = object method reference : bool method release : unit method is_managed : bool method hash : Concepts.Hash.hash method to_string : string method protocols : protocol list end type t = {valid: bool Atomic.t; refers_to: obj; allocated_at: Printexc.raw_backtrace} type 'a interface = {handle: t; interface: 'a} let object_of {valid; refers_to; _} = if (Atomic.get valid) then refers_to else failwith "Attempt to dereference an invalid handle!" let to_string h = let o = (object_of h) in "#<" ^ o#to_string ^ " " ^ (Concepts.Hash.to_hum_string o#hash) ^ ">" let invalidate {valid; _} = Atomic.compare_and_set valid true false let release handle = let o = object_of handle in if invalidate handle then o#release let on_gc ({valid; refers_to; allocated_at} as h) = (* TODO: get rid of this on release builds *) if refers_to#is_managed && (Atomic.get valid) then begin Logger.(log Warn ("Leaking handle to managed object " ^ (to_string h))); Logger.(log Warn (BatString.nreplace (* bleh *) ~str:(Printexc.raw_backtrace_to_string allocated_at) ~sub:"Raised by primitive operation" ~by:"Allocated")); release h end type _ Effect.t += Autorelease: t -> unit Effect.t | Keep: t -> unit Effect.t let with_autorelease f = let hs = Atomic.make BatSet.empty in Fun.protect ~finally:(fun () -> Atomic.get hs |> BatSet.iter release) (fun () -> try f () with | effect (Autorelease h), k -> Utilities.Atomic.swap hs (fun ft -> BatSet.add h ft) |> ignore; Effect.Deep.continue k () | effect (Keep h), k -> Utilities.Atomic.swap hs (fun ft -> BatSet.remove h ft) |> ignore; Effect.Deep.continue k ()) let without_autorelease f = try f () with | effect (Autorelease _), k -> Effect.Deep.continue k () | effect (Keep _), k -> Effect.Deep.continue k () let keep h = try Effect.perform (Keep h) with Effect.Unhandled _ -> () let autorelease h = try Effect.perform (Autorelease h) with Effect.Unhandled _ -> () let interface_of handle interface = {handle; interface} let make o = let h = {valid= Atomic.make true; refers_to= (o :> obj); allocated_at= Printexc.get_callstack 256} in Gc.finalise on_gc h; if o#is_managed then autorelease h; h let into handle f = List.find_map f (object_of handle)#protocols |> Option.map (interface_of handle) let invoke {handle; interface} f = let _ = object_of handle in f interface let from {handle; _} = handle let copy handle = let o = object_of handle in if o#reference then Some (make o) else None let move handle = let o = object_of handle in invalidate handle |> ignore; make o let equal h1 h2 = Concepts.Hash.hash_equals (object_of h1)#hash (object_of h2)#hash let hash h = (object_of h)#hash let protocols h = (object_of h)#protocols let require f h = Option.to_result ~none:(Error.unimplemented_protocol (to_string h)) (f h)