2 * Copyright (c) 2015, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
11 open SaveStateServiceTypes
13 let get_errors_filename (filename
: string) : string = filename ^
".err"
15 let get_decls_filename (filename
: string) : string = filename ^
".decls"
17 (* Gets a set of file paths out of saved state errors *)
18 let fold_error_files (errors_in_phases
: saved_state_errors
) : Relative_path.Set.t
=
20 ~init
:Relative_path.Set.empty
21 ~f
:(fun acc
(_phase
, error_files
) -> Relative_path.Set.union acc error_files
)
24 (* Given saved state errors, produces 2 sets of file paths: one for those that
25 occurred in the given list of phases, and another one for all the rest.
26 The 'tf' prefix is in keeping with partition_tf, where it's supposed to
27 remind you which list matches the predicate (not actually a predicate in this case),
29 let partition_error_files_tf
30 (errors_in_phases
: saved_state_errors
)
31 (phases
: Errors.phase list
): (Relative_path.Set.t
* Relative_path.Set.t
) =
33 let (errors_in_phases_t
, errors_in_phases_f
) =
36 ~f
:(fun (phase
, _error_files
) -> List.mem ~equal
:(=) phases phase
) in
38 ((fold_error_files errors_in_phases_t
), (fold_error_files errors_in_phases_f
))
40 let load_contents_exn (input_filename
: string) : 'a
=
41 let ic = Pervasives.open_in_bin input_filename
in
42 let contents = Marshal.from_channel
ic in
43 Pervasives.close_in
ic;
46 let load_class_decls (input_filename
: string) : unit =
47 let start_t = Unix.gettimeofday
() in
48 Hh_logger.log
"Begin loading class declarations";
50 Hh_logger.log
"Unmarshalling class declarations from %s" input_filename
;
51 let decls = load_contents_exn input_filename
in
52 Hh_logger.log
"Importing class declarations...";
53 let classes = Decl_export.import_class_decls
decls in
54 let num_classes = SSet.cardinal
classes in
55 let msg = Printf.sprintf
"Loaded %d class declarations" num_classes in
56 ignore
@@ Hh_logger.log_duration
msg start_t
58 let stack = Printexc.get_backtrace
() in
59 HackEventLogger.load_decls_failure exn
stack;
60 Hh_logger.exc exn ~
stack ~prefix
:"Failed to load class declarations: "
62 (* Loads the file info and the errors, if any. *)
65 ~
(naming_table_fallback_path
: string option)
66 (saved_state_filename
: string)
67 : Naming_table.t
* saved_state_errors
=
68 let old_naming_table = match naming_table_fallback_path
with
70 Naming_table.load_from_sqlite ~update_reverse_entries
:true nt_path
72 let chan = In_channel.create ~binary
:true saved_state_filename
in
73 let (old_saved
: Naming_table.saved_state_info
) =
74 Marshal.from_channel
chan in
75 Sys_utils.close_in_no_fail saved_state_filename
chan;
76 Naming_table.from_saved old_saved
79 let errors_filename = get_errors_filename saved_state_filename
in
80 let (old_errors
: saved_state_errors
) = if not
(Sys.file_exists
errors_filename) then [] else
81 Marshal.from_channel
(In_channel.create ~binary
:true errors_filename) in
83 if load_decls
then load_class_decls (get_decls_filename saved_state_filename
);
85 (old_naming_table, old_errors
)
87 (* Writes some OCaml object to a file with the given filename. *)
89 (output_filename
: string)
90 (contents: 'a
) : unit =
91 let chan = Pervasives.open_out_bin output_filename
in
92 Marshal.to_channel
chan contents [];
93 Pervasives.close_out
chan
95 let get_hot_classes_filename () =
96 let prefix = Relative_path.(path_of_prefix Root
) in
97 let (/) = Filename.concat
in
98 prefix / "hack" / "hh_hot_classes.json"
100 let get_hot_classes (filename
: string) : SSet.t
=
101 if not
(Disk.file_exists filename
)
103 Hh_logger.log
"Hot classes file '%s' was not found" filename
;
108 |> Hh_json.json_of_string
109 |> Hh_json.get_object_exn
110 |> List.find_exn ~f
:(fun (k
, _
) -> k
= "classes")
112 |> Hh_json.get_array_exn
113 |> List.map ~f
:Hh_json.get_string_exn
117 let dump_class_decls filename
=
118 let start_t = Unix.gettimeofday
() in
119 Hh_logger.log
"Begin saving class declarations";
121 let hot_classes_filename = get_hot_classes_filename () in
122 Hh_logger.log
"Reading hot class names from %s" hot_classes_filename;
123 let classes = get_hot_classes hot_classes_filename in
124 Hh_logger.log
"Exporting %d class declarations..." @@ SSet.cardinal
classes;
125 let decls = Decl_export.export_class_decls
classes in
126 Hh_logger.log
"Marshalling class declarations...";
127 dump_contents filename
decls;
128 ignore
@@ Hh_logger.log_duration
"Saved class declarations" start_t
130 let stack = Printexc.get_backtrace
() in
131 HackEventLogger.save_decls_failure exn
stack;
132 Hh_logger.exc exn ~
stack ~
prefix:"Failed to save class declarations: "
134 (* Dumps the file info and the errors, if any. *)
137 (output_filename
: string)
138 (naming_table
: Naming_table.t
)
139 (errors
: Errors.t
) : unit =
140 Hh_logger.log
"Marshalling the naming table...";
141 let (naming_table_saved
: Naming_table.saved_state_info
) =
142 Naming_table.to_saved naming_table
144 dump_contents output_filename naming_table_saved
;
146 if not
(Sys.file_exists output_filename
) then
147 failwith
(Printf.sprintf
"Did not find file infos file '%s'" output_filename
)
149 Hh_logger.log
"Saved file infos to '%s'" output_filename
;
151 (* Let's not write empty error files. *)
152 if Errors.is_empty errors
then () else begin
153 let errors_in_phases =
155 ~f
:(fun phase
-> (phase
, Errors.get_failed_files errors phase
))
156 [ Errors.Parsing
; Errors.Decl
; Errors.Naming
; Errors.Typing
] in
157 dump_contents (get_errors_filename output_filename
) errors_in_phases
161 dump_class_decls (get_decls_filename output_filename
)
163 let update_save_state
164 ~
(enable_naming_table_fallback
: bool)
167 (output_filename
: string)
168 (replace_state_after_saving
: bool) : ServerEnv.env
* save_state_result
=
169 let t = Unix.gettimeofday
() in
170 let db_name = output_filename ^
".sql" in
171 if not
(RealDisk.file_exists
db_name) then
172 failwith
"Given existing save state SQL file missing";
173 let naming_table = env
.ServerEnv.naming_table in
174 let errors = env
.ServerEnv.errorl
in
175 dump_saved_state ~save_decls output_filename
naming_table errors;
176 let naming_table_rows_changed = if enable_naming_table_fallback
then begin
177 Hh_logger.log
"Updating naming table in place...";
178 Naming_table.save_incremental
naming_table db_name;
179 1 (* But it's a doozy! *)
181 Hh_logger.log
"skip writing file info to sqlite table";
184 let dep_table_edges_added = SharedMem.update_dep_table_sqlite
186 Build_id.build_revision
187 replace_state_after_saving
in
188 ignore
@@ Hh_logger.log_duration
"Updating saved state took" t;
189 let result = { naming_table_rows_changed; dep_table_edges_added } in
191 (* To support incremental generation we have to switch to a backed naming
193 if enable_naming_table_fallback
&& replace_state_after_saving
195 let naming_table = Naming_table.load_from_sqlite
196 ~update_reverse_entries
:false
199 { env
with ServerEnv.naming_table; }, result
202 (** Saves the saved state to the given path. Returns number of dependency
203 * edges dumped into the database. *)
205 ~
(enable_naming_table_fallback
: bool)
206 ~
(dep_table_as_blob
: bool)
209 (output_filename
: string)
210 ~
(replace_state_after_saving
: bool): ServerEnv.env
* save_state_result
=
211 let () = Sys_utils.mkdir_p
(Filename.dirname output_filename
) in
212 let db_name = output_filename ^
".sql" in
213 let () = if Sys.file_exists output_filename
then
214 failwith
(Printf.sprintf
"Cowardly refusing to overwrite '%s'." output_filename
)
216 let () = if Sys.file_exists
db_name then
217 failwith
(Printf.sprintf
"Cowardly refusing to overwrite '%s'." db_name)
219 match SharedMem.loaded_dep_table_filename
() with
221 let naming_table = env
.ServerEnv.naming_table in
222 let errors = env
.ServerEnv.errorl
in
223 let t = Unix.gettimeofday
() in
224 dump_saved_state ~save_decls output_filename
naming_table errors;
226 let naming_table_rows_changed =
227 if enable_naming_table_fallback
then
229 Hh_logger.log
"Saving file info (naming table) into a SQLite table.\n";
230 Naming_table.save
naming_table db_name
235 let dep_table_saver =
236 if dep_table_as_blob
then
237 SharedMem.save_dep_table_blob
239 SharedMem.save_dep_table_sqlite
in
241 let dep_table_edges_added =
244 Build_id.build_revision
245 replace_state_after_saving
in
246 let _ : float = Hh_logger.log_duration
"Saving saved state took" t in
247 let result = { naming_table_rows_changed; dep_table_edges_added } in
248 if replace_state_after_saving
&& enable_naming_table_fallback
250 let naming_table = Naming_table.load_from_sqlite ~update_reverse_entries
:false db_name in
251 { env
with ServerEnv.naming_table; }, result
253 | Some old_table_filename
->
254 (** If server is running from a loaded saved state, it's in-memory
255 * tracked depdnencies are incomplete - most of the actual dependencies
256 * are in the SQL table. We need to copy that file and update it with
257 * the in-memory edges. *)
258 let t = Unix.gettimeofday
() in
259 let content = RealDisk.cat old_table_filename
in
260 let () = RealDisk.mkdir_p
(Filename.dirname output_filename
) in
261 let () = RealDisk.write_file ~file
:db_name ~
contents:content in
262 let _ : float = Hh_logger.log_duration
"Made disk copy of loaded saved state. Took" t in
264 ~enable_naming_table_fallback
268 replace_state_after_saving
270 let get_in_memory_dep_table_entry_count () : (int, string) result =
271 Utils.try_with_stack
(fun () ->
272 SharedMem.get_in_memory_dep_table_entry_count ())
273 |> Result.map_error ~f
:(fun (exn
, _stack
) -> Exn.to_string exn
)
275 (* If successful, returns the # of edges from the dependency table that were written. *)
276 (* TODO: write some other stats, e.g., the number of names, the number of errors, etc. *)
278 ~
(enable_naming_table_fallback
: bool)
279 ~
(dep_table_as_blob
: bool)
282 (output_filename
: string)
283 ~
(replace_state_after_saving
: bool): ServerEnv.env
* (save_state_result
, string) result =
287 ~enable_naming_table_fallback
292 ~replace_state_after_saving
294 |> fun result -> match result with
295 | Ok
(env
, result) -> env
, Ok
result
296 | Error
(exn
, _stack
) -> env
, Error
(Exn.to_string exn
)