source/service/memory.ml (256 lines of code) (raw):

(* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) open Core module Gc = Caml.Gc module Set = Caml.Set module type KeyType = sig include SharedMemory.UserKeyType type out val from_string : string -> out end module type ValueType = sig include Value.Type val unmarshall : string -> t end module NoCache = struct module type S = sig include SharedMemory.NoCache type key_out end module Make (Key : KeyType) (Value : ValueType) : sig include S with type value = Value.t and type key = Key.t and type key_out = Key.out and module KeySet = Set.Make(Key) and module KeyMap = MyMap.Make(Key) end = struct type key_out = Key.out include SharedMemory.NoCache (Key) (Value) end end module WithCache = struct module type S = sig include SharedMemory.WithCache type key_out end module Make (Key : KeyType) (Value : ValueType) : sig include S with type value = Value.t and type key = Key.t and type key_out = Key.out and module KeySet = Set.Make(Key) and module KeyMap = MyMap.Make(Key) end = struct type key_out = Key.out include SharedMemory.WithCache (Key) (Value) end end type bytes = int type configuration = { heap_handle: SharedMemory.handle; minor_heap_size: bytes; } let configuration : configuration option ref = ref None (* Defined in `Gc` module. *) let best_fit_allocation_policy = 2 let worker_garbage_control = (* GC for the worker process. *) { (Gc.get ()) with Gc.minor_heap_size = 256 * 1024; allocation_policy = best_fit_allocation_policy; space_overhead = 120; } let initialize ~heap_size ~dep_table_pow ~hash_table_pow ~log_level () = match !configuration with | None -> (* 4 MB *) let minor_heap_size = 4 * 1024 * 1024 in (* GC for the master process. *) Gc.set { (Gc.get ()) with Gc.minor_heap_size; allocation_policy = best_fit_allocation_policy; space_overhead = 100; }; let shared_mem_config = { SharedMemory.global_size = 0; heap_size; dep_table_pow; hash_table_pow; shm_dirs = ["/dev/shm"; "/pyre"]; shm_min_avail = 1024 * 1024 * 512; (* 512 MB *) log_level; } in Log.info "Initializing shared memory [heap_size=%d, dep_table_pow=%d, hash_table_pow=%d]" heap_size dep_table_pow hash_table_pow; let heap_handle = SharedMemory.init shared_mem_config in configuration := Some { heap_handle; minor_heap_size }; { heap_handle; minor_heap_size } | Some configuration -> configuration let initialize_for_tests () = let heap_size = (* 1 GB *) 1024 * 1024 * 1024 in let dep_table_pow = 18 in let hash_table_pow = 18 in let log_level = 0 in let _ = initialize ~heap_size ~dep_table_pow ~hash_table_pow ~log_level () in () let get_heap_handle { Configuration.Analysis.debug; shared_memory = { heap_size; dependency_table_power; hash_table_power }; _; } = let log_level = if debug then 1 else 0 in let { heap_handle; _ } = initialize ~heap_size ~dep_table_pow:dependency_table_power ~hash_table_pow:hash_table_power ~log_level () in heap_handle let heap_size () = SharedMemory.heap_size () |> Float.of_int |> (fun size -> size /. 1.0e6) |> Int.of_float let report_statistics () = Measure.print_stats (); Measure.print_distributions () exception TarError of string type tar_structure = { directory: PyrePath.t; table_path: PyrePath.t; dependencies_path: PyrePath.t; } let prepare_saved_state_directory { Configuration.Analysis.log_directory; _ } = let root = PyrePath.create_relative ~root:log_directory ~relative:"saved_state" in let table_path = PyrePath.create_relative ~root ~relative:"table" in let dependencies_path = PyrePath.create_relative ~root ~relative:"deps" in let () = try Core.Unix.mkdir (PyrePath.absolute root) with (* [mkdir] on MacOSX returns [EISDIR] instead of [EEXIST] if the directory already exists. *) | Core.Unix.Unix_error ((EEXIST | EISDIR), _, _) -> PyrePath.remove_if_exists table_path; PyrePath.remove_if_exists dependencies_path | e -> raise e in { directory = root; table_path; dependencies_path } let run_tar arguments = let { Unix.Process_info.pid; _ } = Unix.create_process ~prog:"tar" ~args:arguments in if Result.is_error (Unix.waitpid pid) then raise (TarError (Format.sprintf "unable to run tar command %s " (List.to_string ~f:Fn.id arguments))) else () exception SavedStateLoadingFailure of string let save_shared_memory ~path ~configuration = SharedMemory.collect `aggressive; let { directory; table_path; dependencies_path } = prepare_saved_state_directory configuration in SharedMemory.save_table (PyrePath.absolute table_path); let _edges_count : bytes = SharedMemory.save_dep_table_sqlite (PyrePath.absolute dependencies_path) "0.0.0" in run_tar ["cf"; path; "-C"; PyrePath.absolute directory; "."] let load_shared_memory ~path ~configuration = let { directory; table_path; dependencies_path } = prepare_saved_state_directory configuration in run_tar ["xf"; path; "-C"; PyrePath.absolute directory]; try SharedMemory.load_table (PyrePath.absolute table_path); let _edges_count : bytes = SharedMemory.load_dep_table_sqlite (PyrePath.absolute dependencies_path) true in () with | SharedMemory.C_assertion_failure message -> let message = Format.sprintf "Assertion failure in shared memory loading: %s. This is likely due to a mismatch \ between the saved state and the binary version." message in raise (SavedStateLoadingFailure message) external pyre_reset : unit -> unit = "pyre_reset" let reset_shared_memory () = SharedMemory.invalidate_caches (); pyre_reset () module SingletonKey = struct type t = int let to_string = Int.to_string let compare = Int.compare type out = int let from_string = Int.of_string let key = 0 end module type ComparableValueType = sig include ValueType val compare : t -> t -> int end module type SerializableValueType = sig type t module Serialized : ValueType val serialize : t -> Serialized.t val deserialize : Serialized.t -> t end module Serializer (Value : SerializableValueType) = struct module Table = NoCache.Make (SingletonKey) (Value.Serialized) let store table = let data = Value.serialize table in Table.add SingletonKey.key data let load () = let table = Table.find_unsafe SingletonKey.key |> Value.deserialize in Table.remove_batch (Table.KeySet.singleton SingletonKey.key); table end module type InternerValueType = sig include ValueType val to_string : t -> string end (* Provide a unique integer for a given value. *) module Interner (Value : InternerValueType) = struct module Table = SharedMemory.WithCache (Int) (Value) type t = int let intern value = (* The shared memory implementation uses the first 8 bytes of the md5 as a * key to the hashtable. Since we already assume that there won't be * collisions there, let's use the same strategy here. *) let id = value |> Value.to_string |> Digest.string |> Md5_lib.to_binary |> Caml.Bytes.of_string |> fun md5 -> Caml.Bytes.get_int64_ne md5 0 |> Int64.to_int_trunc in Table.write_through id value; id let unintern id = match Table.get id with | Some value -> value | None -> Format.asprintf "Invalid intern key %d" id |> failwith let compare = Int.compare end module SharedMemory = Hack_parallel.Std.SharedMemory