val filter_file_extension : string -> string list -> string
*)
-(* XXX The Memory is not actually persistent yet. *)
-let memory = Hashtbl.create 13
+(* Persistent memory is stored in $HOME/.goaljobs-memory. We have to
+ * lock this file each time we read or write because multiple concurrent
+ * jobs may access it at the same time.
+ *
+ * XXX Replace this with a more efficient and less fragile implementation.
+ *)
+
+type ('a, 'b) alternative = Either of 'a | Or of 'b
+let with_memory_locked ?(write = false) f =
+ let filename = getenv "HOME" // ".goaljobs-memory" in
+ let fd = openfile filename [O_RDWR; O_CREAT] 0o644 in
+ lockf fd (if write then F_LOCK else F_RLOCK) 0;
+ let r = try Either (f fd) with exn -> Or (exn) in
+ lockf fd F_ULOCK 0;
+ match r with
+ | Either x -> x
+ | Or exn -> raise exn
+
+let memory_exists key =
+ with_memory_locked (
+ fun fd ->
+ let chan = in_channel_of_descr fd in
+ let memory : (string, string) Hashtbl.t = input_value chan in
+ Hashtbl.mem memory key
+ )
-let memory_exists = Hashtbl.mem memory
-let memory_set = Hashtbl.replace memory
-let memory_get k = try Some (Hashtbl.find memory k) with Not_found -> None
-let memory_delete = Hashtbl.remove memory
+let memory_get key =
+ with_memory_locked (
+ fun fd ->
+ let chan = in_channel_of_descr fd in
+ let memory : (string, string) Hashtbl.t = input_value chan in
+ try Some (Hashtbl.find memory key) with Not_found -> None
+ )
+
+let memory_set key value =
+ with_memory_locked ~write:true (
+ fun fd ->
+ let chan = in_channel_of_descr fd in
+ let memory : (string, string) Hashtbl.t = input_value chan in
+ Hashtbl.replace memory key value;
+ let chan = out_channel_of_descr fd in
+ seek_out chan 0;
+ output_value chan memory
+ )
+
+let memory_delete key =
+ with_memory_locked ~write:true (
+ fun fd ->
+ let chan = in_channel_of_descr fd in
+ let memory : (string, string) Hashtbl.t = input_value chan in
+ Hashtbl.remove memory key;
+ let chan = out_channel_of_descr fd in
+ seek_out chan 0;
+ output_value chan memory
+ )
let published_goals = ref []
let publish name fn = published_goals := (name, fn) :: !published_goals