You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
5.8 KiB
178 lines
5.8 KiB
(*
|
|
* Copyright (c) 2009 - 2013 Monoidics ltd.
|
|
* Copyright (c) 2013 - present Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*)
|
|
|
|
open! IStd
|
|
|
|
module L = Logging
|
|
module F = Format
|
|
|
|
(** Generic serializer *)
|
|
type 'a serializer =
|
|
{
|
|
read_from_string: string -> 'a option;
|
|
read_from_file: DB.filename -> 'a option;
|
|
update_file : f:('a option -> 'a) -> DB.filename -> unit;
|
|
write_to_file: data:'a -> DB.filename -> unit;
|
|
}
|
|
|
|
module Key = struct
|
|
|
|
(** Serialization key, used to distinguish versions of serializers and avoid assert faults *)
|
|
type t = int
|
|
|
|
(** current key for tenv, procedure summary, cfg, error trace, call graph *)
|
|
let tenv, summary, cfg, trace, cg,
|
|
analysis_results, cluster, attributes, lint_issues =
|
|
425184201, 160179325, 1062389858, 221487792, 477305409,
|
|
799050016, 579094948, 972393003, 852343110
|
|
end
|
|
|
|
(** version of the binary files, to be incremented for each change *)
|
|
let version = 27
|
|
|
|
|
|
(** Retry the function while an exception filtered is thrown,
|
|
or until the timeout in seconds expires. *)
|
|
let retry_exception ~timeout ~catch_exn ~f x =
|
|
let init_time = Unix.gettimeofday () in
|
|
let expired () =
|
|
Unix.gettimeofday () -. init_time >= timeout in
|
|
let rec retry () =
|
|
try f x with
|
|
| e when catch_exn e && not (expired ()) ->
|
|
retry () in
|
|
retry ()
|
|
|
|
type 'a write_command =
|
|
| Replace of 'a
|
|
| Update of ('a option -> 'a)
|
|
|
|
let create_serializer (key : Key.t) : 'a serializer =
|
|
let read_data ((key': Key.t), (version': int), (value: 'a)) source_msg =
|
|
if key <> key' then
|
|
begin
|
|
L.err "Wrong key in when loading data from %s@\n" source_msg;
|
|
None
|
|
end
|
|
else if version <> version' then
|
|
begin
|
|
L.err "Wrong version in when loading data from %s@\n" source_msg;
|
|
None
|
|
end
|
|
else Some value in
|
|
let read_from_string (str : string) : 'a option =
|
|
try
|
|
read_data (Marshal.from_string str 0) "string"
|
|
with Sys_error _ -> None in
|
|
(* The reads happen without synchronization.
|
|
The writes are synchronized with a .lock file. *)
|
|
let read_from_file (fname : DB.filename) : 'a option =
|
|
let fname_str = DB.filename_to_string fname in
|
|
match open_in_bin fname_str with
|
|
| exception Sys_error _ ->
|
|
None
|
|
| inc ->
|
|
let read () =
|
|
try
|
|
In_channel.seek inc 0L ;
|
|
read_data (Marshal.from_channel inc) fname_str
|
|
with
|
|
| Sys_error _ -> None in
|
|
let catch_exn = function
|
|
| End_of_file -> true
|
|
| Failure _ -> true (* handle input_value: truncated object *)
|
|
| _ -> false in
|
|
(* Retry to read for 1 second in case of end of file, *)
|
|
(* which indicates that another process is writing the same file. *)
|
|
SymOp.try_finally
|
|
(fun () -> retry_exception ~timeout:1.0 ~catch_exn ~f:read ())
|
|
(fun () -> In_channel.close inc) in
|
|
|
|
let write_file_with_locking ?(delete=false) ~do_write fname =
|
|
let file_descr = Unix.openfile ~mode:[Unix.O_WRONLY; Unix.O_CREAT] fname in
|
|
let outc = Unix.out_channel_of_descr file_descr in
|
|
if Unix.flock file_descr Unix.Flock_command.lock_exclusive
|
|
then
|
|
begin
|
|
do_write outc;
|
|
flush outc;
|
|
ignore (Unix.flock file_descr Unix.Flock_command.unlock);
|
|
end;
|
|
Out_channel.close outc;
|
|
if delete
|
|
then
|
|
try Unix.unlink fname with
|
|
| Unix.Unix_error _ -> () in
|
|
|
|
let write_to_tmp_file fname data =
|
|
let fname_tmp = Filename.temp_file
|
|
~in_dir:(Filename.dirname fname) (Filename.basename fname) ".tmp" in
|
|
write_file_with_locking
|
|
fname_tmp
|
|
~do_write:(fun outc -> Marshal.to_channel outc (key, version, data) []);
|
|
fname_tmp in
|
|
|
|
(* The .lock file is used to synchronize the writers.
|
|
Once a lock on `file.lock` is obtained, the new data is written into a temporary file
|
|
and rename is used to move it atomically to `file` *)
|
|
let execute_write_command_with_lock (fname : DB.filename) (cmd : 'a write_command) =
|
|
let fname_str = DB.filename_to_string fname in
|
|
let fname_str_lock = fname_str ^ ".lock" in
|
|
|
|
write_file_with_locking
|
|
fname_str_lock
|
|
~delete:true
|
|
~do_write:(fun _outc ->
|
|
let (data_to_write : 'a) = match cmd with
|
|
| Replace data ->
|
|
data
|
|
| Update upd ->
|
|
let old_data_opt =
|
|
if DB.file_exists fname
|
|
then
|
|
(* Because of locking, this should be the latest data written
|
|
by any writer, and can be used for updating *)
|
|
read_from_file fname
|
|
else
|
|
None in
|
|
upd old_data_opt in
|
|
|
|
let fname_str_tmp = write_to_tmp_file fname_str data_to_write in
|
|
(* Rename is atomic: the readers can only see one version of this file,
|
|
possibly stale but not corrupted. *)
|
|
Unix.rename ~src:fname_str_tmp ~dst:fname_str) in
|
|
let write_to_file ~(data : 'a) (fname : DB.filename) =
|
|
execute_write_command_with_lock fname (Replace data) in
|
|
let update_file ~f (fname : DB.filename) =
|
|
execute_write_command_with_lock fname (Update f) in
|
|
{read_from_string; read_from_file; update_file; write_to_file; }
|
|
|
|
|
|
let read_from_string s =
|
|
s.read_from_string
|
|
|
|
let read_from_file s =
|
|
s.read_from_file
|
|
|
|
let update_file s =
|
|
s.update_file
|
|
|
|
let write_to_file s =
|
|
s.write_to_file
|
|
|
|
(*
|
|
(** Generate random keys, to be used in an ocaml toplevel *)
|
|
let generate_keys () =
|
|
Random.self_init ();
|
|
let max_rand_int = 0x3FFFFFFF (* determined by Rand library *) in
|
|
let gen () = Random.int max_rand_int in
|
|
gen (), gen (), gen (), gen (), gen (), gen ()
|
|
*)
|