2 * Copyright (c) 2018, 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.
10 (* Lazy Initialization:
12 During Lazy initialization, hh_server tries to do as little work as possible.
13 The init_from_saved_state behavior is this:
15 Load from saved state -> Parse dirty files -> Naming -> Dirty Typecheck
17 The full_init behavior is this: (similar to eager init, but with lazy decl)
19 Full Parsing -> Naming -> Full Typecheck (with lazy decl)
22 (* module Hack_bucket = Bucket *)
25 (* module Bucket = Hack_bucket *)
28 open Reordered_argument_collections
29 open SearchServiceRunner
35 module SLC
= ServerLocalConfig
38 | SQLiteDeptable
of string
39 | CustomDeptable
of string
41 let deptable_with_filename ~
(is_64bit
: bool) (fn
: string) : deptable
=
47 let lock_and_load_deptable
48 (deptable
: deptable
) ~
(ignore_hh_version
: bool) ~
(fail_if_missing
: bool)
51 | SQLiteDeptable fn
->
52 if String.length fn
= 0 && not fail_if_missing
then
53 Hh_logger.log
"The dependency file was not specified - ignoring"
55 (* The SQLite deptable must be loaded in the master process *)
57 (* Take a lock on the info file for the SQLite *)
59 LoadScriptUtils.lock_saved_state fn
;
60 let start_t = Unix.gettimeofday
() in
61 SharedMem.load_dep_table_sqlite fn ignore_hh_version
;
63 Hh_logger.log_duration
"Did read the dependency file (sec)" start_t
65 HackEventLogger.load_deptable_end
start_t
67 | (SharedMem.Sql_assertion_failure
11 | SharedMem.Sql_assertion_failure
14)
70 let stack = Caml.Printexc.get_raw_backtrace
() in
71 LoadScriptUtils.delete_corrupted_saved_state fn
;
72 Caml.Printexc.raise_with_backtrace e
stack
74 | CustomDeptable fn
->
75 (* The new dependency graph is threaded through function calls
76 * instead of stored in a global *)
77 Hh_logger.log
"Custom dependency graph will be loaded lazily from %s" fn
79 let merge_saved_state_futures
81 (ctx
: Provider_context.t
)
82 (dependency_table_saved_state_future
:
83 (State_loader.native_load_result
, State_loader.error
) result
Future.t
)
84 (naming_table_saved_state_future
:
85 ( Saved_state_loader.Naming_table_info.t
Saved_state_loader.load_result
89 Future.t
) : (loaded_info
, load_state_error
) result
=
90 let t = Unix.gettimeofday
() in
92 let merge dependency_table_saved_state_result naming_table_saved_state_result
94 match dependency_table_saved_state_result
with
97 "Unhandled error from State_loader: %s"
98 (Future.error_to_string error
);
99 let e = Exception.wrap_unraised
(Future.error_to_exn error
) in
100 let exn = Exception.to_exn
e in
101 let stack = Utils.Callstack
(Exception.get_backtrace_string
e) in
102 Error
(Load_state_unhandled_exception
{ exn; stack })
103 | Ok
(Error error
) -> Error
(Load_state_loader_failure error
)
105 let ( downloaded_naming_table_path
,
106 naming_table_manifold_path
,
107 dirty_naming_files
) =
108 match naming_table_saved_state_result
with
109 | Ok
(Ok None
) -> (None
, None
, [])
114 Saved_state_loader.saved_state_info
;
119 Hh_logger.log_duration
"Finished downloading naming table." t
123 .Saved_state_loader.Naming_table_info.naming_table_path
125 (Some
(Path.to_string
path), Some manifold_path
, changed_files
)
127 Hh_logger.warn
"Failed to download naming table saved state: %s" err
;
131 "Failed to download the naming table saved state: %s"
132 (Future.error_to_string error
);
135 let ignore_hh_version =
136 ServerArgs.ignore_hh_version genv
.ServerEnv.options
138 let fail_if_missing = not genv
.local_config
.SLC.can_skip_deptable
in
140 deptable_with_filename
141 ~is_64bit
:result
.State_loader.deptable_is_64bit
142 result
.State_loader.deptable_fn
144 lock_and_load_deptable deptable ~
ignore_hh_version ~
fail_if_missing;
145 let load_decls = genv
.local_config
.SLC.load_decls_from_saved_state
in
146 let shallow_decls = genv
.local_config
.SLC.shallow_class_decl
in
147 let naming_table_fallback_path =
148 get_naming_table_fallback_path genv downloaded_naming_table_path
150 let (old_naming_table
, old_errors
) =
151 SaveStateService.load_saved_state
153 ~naming_table_path
:result
.State_loader.naming_table_path
154 ~
naming_table_fallback_path
157 ~hot_decls_paths
:result
.State_loader.hot_decls_paths
158 ~errors_path
:result
.State_loader.errors_path
160 let t = Unix.time
() in
161 (match result
.State_loader.dirty_files
|> Future.get ~timeout
:200 with
162 | Error error
-> Error
(Load_state_dirty_files_failure error
)
165 State_loader.master_changes
= dirty_master_files
;
166 local_changes
= dirty_local_files
;
168 let () = HackEventLogger.state_loader_dirty_files
t in
169 let dirty_naming_files = Relative_path.Set.of_list
dirty_naming_files in
170 let dirty_master_files = Relative_path.Set.of_list
dirty_master_files in
171 let dirty_local_files = Relative_path.Set.of_list
dirty_local_files in
174 naming_table_fn
= result
.State_loader.naming_table_path
;
175 deptable_fn
= result
.State_loader.deptable_fn
;
176 deptable_is_64bit
= result
.State_loader.deptable_is_64bit
;
177 naming_table_fallback_fn
= naming_table_fallback_path;
178 corresponding_rev
= result
.State_loader.corresponding_rev
;
179 mergebase_rev
= result
.State_loader.mergebase_rev
;
180 mergebase
= result
.State_loader.mergebase
;
186 state_distance
= Some result
.State_loader.state_distance
;
187 naming_table_manifold_path
;
190 let merge left right
= Ok
(merge left right
) in
193 dependency_table_saved_state_future
194 naming_table_saved_state_future
197 (* We don't call Future.get on the merged future until it's ready because
198 the implementation of Future.get blocks on the first future until it's
199 ready, then moves on to the second future, but the second future, since
200 it's composed of bound continuations, will not be making as much progress
201 on its own in this case. *)
202 let rec wait_until_ready future =
203 if not
(Future.is_ready
future) then begin
204 Sys_utils.sleep ~seconds
:0.04;
205 wait_until_ready future
207 match Future.get
future ~timeout
:0 with
208 | Ok result
-> result
209 | Error error
-> failwith
(Future.error_to_string error
)
211 wait_until_ready future
213 let download_and_load_state_exn
214 ~
(target
: ServerMonitorUtils.target_saved_state
option)
215 ~
(genv
: ServerEnv.genv
)
216 ~
(ctx
: Provider_context.t)
217 ~
(root
: Path.t) : (loaded_info
, load_state_error
) result
=
218 let open ServerMonitorUtils
in
219 let saved_state_handle =
223 { saved_state_everstore_handle
; target_global_rev
; watchman_mergebase
}
227 State_loader.saved_state_everstore_handle
;
228 saved_state_for_rev
= Hg.Global_rev target_global_rev
;
232 let ignore_hh_version = ServerArgs.ignore_hh_version genv
.options
in
233 let ignore_hhconfig = ServerArgs.saved_state_ignore_hhconfig genv
.options
in
234 let use_prechecked_files =
235 ServerPrecheckedFiles.should_use genv
.options genv
.local_config
237 let naming_table_saved_state_future =
238 if genv
.local_config
.ServerLocalConfig.enable_naming_table_fallback
then begin
239 Hh_logger.log
"Starting naming table download.";
241 State_loader_futures.load
243 Saved_state_loader.Watchman_options.{ root
; sockname
= None
}
245 ~saved_state_type
:Saved_state_loader.Naming_table
246 |> Future.with_timeout ~timeout
:60
248 Future.continue_and_map_err
loader_future @@ fun result
->
250 | Ok
(Ok load_state
) -> Ok
(Some load_state
)
251 | Ok
(Error
e) -> Error
(Saved_state_loader.long_user_message_of_error
e)
252 | Error
e -> Error
(Future.error_to_string
e)
254 Future.of_value
(Ok None
)
256 let dependency_table_saved_state_future :
257 (State_loader.native_load_result
, State_loader.error
) result
Future.t =
258 State_loader.mk_state_future
259 ~config
:genv
.local_config
.SLC.state_loader_timeouts
260 ~load_64bit
:genv
.local_config
.SLC.load_state_natively_64bit
262 ~config_hash
:(ServerConfig.config_hash genv
.config
)
266 ~
use_prechecked_files
268 merge_saved_state_futures
271 dependency_table_saved_state_future
272 naming_table_saved_state_future
274 let use_precomputed_state_exn
275 (genv
: ServerEnv.genv
)
276 (ctx
: Provider_context.t)
277 (info
: ServerArgs.saved_state_target_info
)
278 (profiling
: CgroupProfiler.Profiling.t) : loaded_info
=
280 ServerArgs.naming_table_path
;
281 corresponding_base_revision
;
290 let ignore_hh_version = ServerArgs.ignore_hh_version genv
.ServerEnv.options
in
291 let fail_if_missing = not genv
.local_config
.SLC.can_skip_deptable
in
293 deptable_with_filename ~is_64bit
:deptable_is_64bit deptable_fn
295 CgroupProfiler.collect_cgroup_stats ~profiling ~stage
:"load deptable"
297 lock_and_load_deptable deptable ~
ignore_hh_version ~
fail_if_missing;
298 let changes = Relative_path.set_of_list
changes in
299 let naming_changes = Relative_path.set_of_list
naming_changes in
300 let prechecked_changes = Relative_path.set_of_list
prechecked_changes in
301 let load_decls = genv
.local_config
.SLC.load_decls_from_saved_state
in
302 let shallow_decls = genv
.local_config
.SLC.shallow_class_decl
in
303 let naming_table_fallback_path = get_naming_table_fallback_path genv None
in
304 let hot_decls_paths =
307 legacy_hot_decls_path
=
308 ServerArgs.legacy_hot_decls_path_for_target_info info
;
309 shallow_hot_decls_path
=
310 ServerArgs.shallow_hot_decls_path_for_target_info info
;
313 let errors_path = ServerArgs.errors_path_for_target_info info
in
314 let (old_naming_table
, old_errors
) =
315 CgroupProfiler.collect_cgroup_stats ~profiling ~stage
:"load saved state"
317 SaveStateService.load_saved_state
320 ~
naming_table_fallback_path
327 naming_table_fn
= naming_table_path
;
330 naming_table_fallback_fn
= naming_table_fallback_path;
332 Hg.Global_rev
(int_of_string corresponding_base_revision
);
333 mergebase_rev
= None
;
334 mergebase
= Future.of_value None
;
335 dirty_naming_files = naming_changes;
336 dirty_master_files = prechecked_changes;
337 dirty_local_files = changes;
340 state_distance
= None
;
341 naming_table_manifold_path
= None
;
344 (* Run naming from a fast generated from saved state.
345 * No errors are generated because we assume the fast is directly from
349 (ctx
: Provider_context.t)
350 (fast
: FileInfo.names
Relative_path.Map.t)
351 (t : float) : float =
352 Relative_path.Map.iter fast ~f
:(fun k info
->
354 FileInfo.n_classes
= classes
;
355 n_record_defs
= record_defs
;
362 Naming_global.ndecl_file_fast
370 HackEventLogger.fast_naming_end
t;
372 Hh_logger.log_duration
"Naming fast" t
374 let naming_from_saved_state
375 (ctx
: Provider_context.t)
376 (old_naming_table
: Naming_table.t)
377 (parsing_files
: Relative_path.Set.t)
378 (naming_table_fallback_fn
: string option)
380 ~
(profiling
: CgroupProfiler.Profiling.t) : float =
381 CgroupProfiler.collect_cgroup_stats
383 ~stage
:"naming from saved state"
385 (* If we're falling back to SQLite we don't need to explicitly do a naming
386 pass, but if we're not then we do. *)
387 match naming_table_fallback_fn
with
389 (* Set the SQLite fallback path for the reverse naming table, then block out all entries in
390 any dirty files to make sure we properly handle file deletes. *)
391 Relative_path.Set.iter parsing_files
(fun k
->
392 match Naming_table.get_file_info old_naming_table k
with
394 (* If we can't find the file in [old_naming_table] we don't consider that an error, since
395 * it could be a new file that was added. *)
398 let backend = Provider_context.get_backend ctx
in
399 Naming_provider.remove_type_batch
401 (v
.FileInfo.classes
|> List.map ~f
:snd
|> SSet.of_list
);
402 Naming_provider.remove_type_batch
404 (v
.FileInfo.typedefs
|> List.map ~f
:snd
|> SSet.of_list
);
405 Naming_provider.remove_type_batch
407 (v
.FileInfo.record_defs
|> List.map ~f
:snd
|> SSet.of_list
);
408 Naming_provider.remove_fun_batch
410 (v
.FileInfo.funs
|> List.map ~f
:snd
|> SSet.of_list
);
411 Naming_provider.remove_const_batch
413 (v
.FileInfo.consts
|> List.map ~f
:snd
|> SSet.of_list
));
416 (* Name all the files from the old fast (except the new ones we parsed) *)
418 Naming_table.filter old_naming_table
(fun k _v
->
419 not
(Relative_path.Set.mem parsing_files k
))
421 naming_with_fast ctx
(Naming_table.to_fast
old_hack_names) t
423 (* Prechecked files are gated with a flag and not supported in AI/check/saving
424 * of saved state modes. *)
425 let use_prechecked_files (genv
: ServerEnv.genv
) : bool =
426 ServerPrecheckedFiles.should_use genv
.options genv
.local_config
427 && Option.is_none
(ServerArgs.ai_mode genv
.options
)
428 && (not
(ServerArgs.check_mode genv
.options
))
429 && Option.is_none
(ServerArgs.save_filename genv
.options
)
430 && Option.is_none
(ServerArgs.save_with_spec genv
.options
)
433 (old_naming_table
: Naming_table.t)
434 (fast
: FileInfo.names
Relative_path.Map.t)
435 (dirty
: Relative_path.Set.t) : FileInfo.names
Relative_path.Map.t =
436 Relative_path.Set.fold
441 let dirty_fast = Relative_path.Map.find_opt fast fn
in
443 Naming_table.get_file_info old_naming_table fn
444 |> Option.map ~f
:FileInfo.simplify
447 Option.merge dirty_old_fast dirty_fast FileInfo.merge_names
450 | Some
fast -> Relative_path.Map.add acc ~key
:fn ~data
:fast
453 ~init
:Relative_path.Map.empty
455 let names_to_deps (deps_mode
: Typing_deps_mode.t) (names
: FileInfo.names
) :
456 Typing_deps.DepSet.t =
457 let open Typing_deps
in
458 let { FileInfo.n_funs
; n_classes
; n_record_defs
; n_types
; n_consts
} =
461 let add_deps_of_sset dep_ctor sset depset
=
462 SSet.fold sset ~init
:depset ~f
:(fun n acc
->
463 DepSet.add acc
(Dep.make
(hash_mode deps_mode
) (dep_ctor n
)))
466 add_deps_of_sset (fun n
-> Dep.Fun n
) n_funs
(DepSet.make deps_mode
)
468 let deps = add_deps_of_sset (fun n
-> Dep.FunName n
) n_funs
deps in
469 let deps = add_deps_of_sset (fun n
-> Dep.Class n
) n_classes
deps in
470 let deps = add_deps_of_sset (fun n
-> Dep.RecordDef n
) n_record_defs
deps in
471 let deps = add_deps_of_sset (fun n
-> Dep.Class n
) n_types
deps in
472 let deps = add_deps_of_sset (fun n
-> Dep.GConst n
) n_consts
deps in
473 let deps = add_deps_of_sset (fun n
-> Dep.GConstName n
) n_consts
deps in
476 (** Compare declarations loaded from the saved state to declarations based on
477 the current versions of dirty files. This lets us check a smaller set of
478 files than the set we'd check if old declarations were not available.
479 To be used only when load_decls_from_saved_state is enabled. *)
480 let get_files_to_undecl_and_recheck
481 (genv
: ServerEnv.genv
)
482 (env
: ServerEnv.env
)
483 (old_naming_table
: Naming_table.t)
484 (new_fast
: FileInfo.names
Relative_path.Map.t)
485 (dirty_fast : FileInfo.names
Relative_path.Map.t)
486 (files_to_redeclare
: Relative_path.Set.t) :
487 Relative_path.Set.t * Relative_path.Set.t =
488 let bucket_size = genv
.local_config
.SLC.type_decl_bucket_size
in
490 Relative_path.Set.fold
492 ~init
:Relative_path.Map.empty
494 match Relative_path.Map.find_opt
dirty_fast path with
495 | Some info
-> Relative_path.Map.add acc
path info
498 let get_classes path =
500 Naming_table.get_file_info old_naming_table
path
501 |> Option.map ~f
:FileInfo.simplify
503 let new_names = Relative_path.Map.find_opt new_fast
path in
504 let classes_from_names x
= x
.FileInfo.n_classes
in
505 let old_classes = Option.map
old_names classes_from_names in
506 let new_classes = Option.map
new_names classes_from_names in
507 Option.merge old_classes new_classes SSet.union
508 |> Option.value ~default
:SSet.empty
511 Relative_path.Map.fold
dirty_fast ~init
:FileInfo.empty_names ~f
:(fun _
->
512 FileInfo.merge_names
)
514 let ctx = Provider_utils.ctx_from_server_env env
in
515 Decl_redecl_service.oldify_type_decl
520 ~previously_oldified_defs
:FileInfo.empty_names
522 let { Decl_redecl_service.to_redecl
; to_recheck
; _
} =
523 Decl_redecl_service.redo_type_decl
524 ~conservative_redecl
:false
529 ~previously_oldified_defs
:dirty_names
532 Decl_redecl_service.remove_old_defs
ctx ~
bucket_size genv
.workers
dirty_names;
533 let deps = Typing_deps.add_all_deps env
.deps_mode to_redecl
in
534 let deps = Typing_deps.DepSet.union
deps to_recheck
in
535 let files_to_undecl = Typing_deps.Files.get_files to_redecl
in
536 let files_to_recheck = Typing_deps.Files.get_files
deps in
537 (files_to_undecl, files_to_recheck)
539 (* We start off with a list of files that have changed since the state was
540 * saved (dirty_files), and two maps of the class / function declarations
541 * -- one made when the state was saved (old_fast) and one made for the
542 * current files in the repository (new_fast). We grab the declarations from
543 * both, to account for both the declaratons that were deleted and those that
544 * are newly created. Then we use the deptable to figure out the files that
545 * referred to them. Finally we recheck the lot.
549 * genv, env : environments
550 * old_fast: old file-ast from saved state
551 * new_fast: newly parsed file ast
552 * dirty_master_files and dirty_local_files: we need to typecheck these and,
553 * since their decl have changed, also all of their dependencies
554 * similar_files: we only need to typecheck these,
555 * not their dependencies since their decl are unchanged
558 (genv
: ServerEnv.genv
)
559 (env
: ServerEnv.env
)
560 (old_naming_table
: Naming_table.t)
561 (new_fast
: FileInfo.names
Relative_path.Map.t)
562 ~
(dirty_master_files_unchanged_hash
: Relative_path.Set.t)
563 ~
(dirty_master_files_changed_hash
: Relative_path.Set.t)
564 ~
(dirty_local_files_unchanged_hash
: Relative_path.Set.t)
565 ~
(dirty_local_files_changed_hash
: Relative_path.Set.t)
567 (profiling
: CgroupProfiler.Profiling.t) : ServerEnv.env
* float =
568 let start_t = Unix.gettimeofday
() in
571 |> Telemetry.float_ ~key
:"start_time" ~
value:start_t
572 |> Telemetry.string_ ~key
:"reason" ~
value:"lazy_dirty_init"
574 let dirty_files_unchanged_hash =
575 Relative_path.Set.union
576 dirty_master_files_unchanged_hash
577 dirty_local_files_unchanged_hash
579 let dirty_files_changed_hash =
580 Relative_path.Set.union
581 dirty_master_files_changed_hash
582 dirty_local_files_changed_hash
584 let dirty_changed_fast =
585 get_dirty_fast old_naming_table new_fast
dirty_files_changed_hash
588 Relative_path.Map.fold
593 if Relative_path.Set.mem s k
then
594 FileInfo.merge_names v acc
598 ~init
:FileInfo.empty_names
600 let ctx = Provider_utils.ctx_from_server_env env
in
601 let deps_mode = Provider_context.get_deps_mode
ctx in
603 names dirty_master_files_changed_hash
|> names_to_deps deps_mode
606 names dirty_local_files_changed_hash
|> names_to_deps deps_mode
608 (* Include similar_files in the dirty_fast used to determine which loaded
609 declarations to oldify. This is necessary because the positions of
610 declarations may have changed, which affects error messages and FIXMEs. *)
611 let get_files_to_undecl_and_recheck =
612 get_files_to_undecl_and_recheck genv env old_naming_table new_fast
617 dirty_files_unchanged_hash
619 let (env
, to_undecl
, to_recheck
) =
620 if use_prechecked_files genv
then
621 (* Start with dirty files and fan-out of local changes only *)
622 let (to_undecl
, to_recheck
) =
623 if genv
.local_config
.SLC.load_decls_from_saved_state
then
624 get_files_to_undecl_and_recheck dirty_local_files_changed_hash
626 let deps = Typing_deps.add_all_deps env
.deps_mode local_deps in
627 (Relative_path.Set.empty
, Typing_deps.Files.get_files
deps)
629 ( ServerPrecheckedFiles.set
631 (Initial_typechecking
633 rechecked_files
= Relative_path.Set.empty
;
634 dirty_local_deps
= local_deps;
635 dirty_master_deps
= master_deps;
636 clean_local_deps
= Typing_deps.(DepSet.make
deps_mode);
641 (* Start with full fan-out immediately *)
642 let (to_undecl
, to_recheck
) =
643 if genv
.local_config
.SLC.load_decls_from_saved_state
then
644 get_files_to_undecl_and_recheck dirty_files_changed_hash
646 let deps = Typing_deps.DepSet.union
master_deps local_deps in
647 let deps = Typing_deps.add_all_deps env
.deps_mode deps in
648 (Relative_path.Set.empty
, Typing_deps.Files.get_files
deps)
650 (env
, to_undecl
, to_recheck
)
652 (* We still need to typecheck files whose declarations did not change *)
654 Relative_path.Set.union
to_recheck dirty_files_unchanged_hash
656 let fast = extend_fast genv
dirty_changed_fast env
.naming_table
to_recheck in
657 let files_to_check = Relative_path.Map.keys
fast in
659 (* HACK: dump the fanout that we calculated and exit. This is for
660 `hh_fanout`'s regression testing vs. `hh_server`. This can be deleted once
661 we no longer worry about `hh_fanout` regressing vs. `hh_server`. Deletion
662 is tracked at T65464119. *)
663 if ServerArgs.dump_fanout genv
.options
then (
664 Hh_json.json_to_multiline_output
671 |> List.map ~f
:Relative_path.to_absolute
672 |> List.map ~f
:Hh_json.string_
) );
676 (* In case we saw that any hot decls had become invalid, we have to remove them.
677 Note: we don't need to do a full "redecl" of them since their fanout has
678 already been encompassed by to_recheck. *)
679 let names_to_undecl =
680 Relative_path.Set.fold
682 ~init
:FileInfo.empty_names
684 match Naming_table.get_file_info old_naming_table file
with
687 let names = FileInfo.simplify info
in
688 FileInfo.merge_names acc
names)
690 let ctx = Provider_utils.ctx_from_server_env env
in
691 Decl_redecl_service.remove_defs
695 ~collect_garbage
:false;
697 let env = { env with changed_files
= dirty_files_changed_hash } in
701 ~key
:"dirty_master_files_unchanged_hash"
702 ~
value:(Relative_path.Set.cardinal dirty_master_files_unchanged_hash
)
704 ~key
:"dirty_master_files_changed_hash"
705 ~
value:(Relative_path.Set.cardinal dirty_master_files_changed_hash
)
707 ~key
:"dirty_local_files_unchanged_hash"
708 ~
value:(Relative_path.Set.cardinal dirty_local_files_unchanged_hash
)
710 ~key
:"dirty_local_files_changed_hash"
711 ~
value:(Relative_path.Set.cardinal dirty_local_files_changed_hash
)
713 ~key
:"dirty_files_unchanged_hash"
714 ~
value:(Relative_path.Set.cardinal
dirty_files_unchanged_hash)
716 ~key
:"dirty_files_changed_hash"
717 ~
value:(Relative_path.Set.cardinal
dirty_files_changed_hash)
720 ~
value:(Relative_path.Set.cardinal
to_recheck)
729 ~profile_label
:"type check dirty files"
732 HackEventLogger.type_check_dirty
734 ~dirty_count
:(Relative_path.Set.cardinal
dirty_files_changed_hash)
735 ~recheck_count
:(Relative_path.Set.cardinal
to_recheck);
737 "ServerInit type_check_dirty count: %d. recheck count: %d"
738 (Relative_path.Set.cardinal
dirty_files_changed_hash)
739 (Relative_path.Set.cardinal
to_recheck);
742 let get_updates_exn ~
(genv
: ServerEnv.genv
) ~
(root
: Path.t) :
743 Relative_path.Set.t =
744 let start_t = Unix.gettimeofday
() in
745 Hh_logger.log
"Getting files changed while parsing...";
746 let files_changed_while_parsing =
747 ServerNotifierTypes.(
748 genv
.wait_until_ready ();
749 match genv
.notifier_async
() with
750 | Notifier_state_enter _
751 | Notifier_state_leave _
752 | Notifier_unavailable
->
753 Relative_path.Set.empty
754 | Notifier_synchronous_changes updates
755 | Notifier_async_changes updates
->
756 let root = Path.to_string
root in
757 let filter p
= string_starts_with p
root && FindUtils.file_filter p
in
758 SSet.filter updates ~f
:filter
759 |> Relative_path.relativize_set
Relative_path.Root
)
762 ( Hh_logger.log_duration
763 "Finished getting files changed while parsing"
766 HackEventLogger.changed_while_parsing_end
start_t;
767 files_changed_while_parsing
769 let initialize_naming_table
770 (progress_message
: string)
771 ?
(fnl
: Relative_path.t list
option = None
)
772 ?
(do_naming
: bool = false)
773 (genv
: ServerEnv.genv
)
774 (env : ServerEnv.env)
775 (profiling
: CgroupProfiler.Profiling.t) : ServerEnv.env * float =
776 SharedMem.cleanup_sqlite
();
777 ServerProgress.send_progress_to_monitor
"%s" progress_message
;
778 let (get_next
, count
, t) =
781 ( MultiWorker.next genv
.workers fnl
,
782 Some
(List.length fnl
),
783 Unix.gettimeofday
() )
785 let (get_next
, t) = indexing genv
in
788 (* The full_fidelity_parser currently works better in both memory and time
789 with a full parse rather than parsing decl asts and then parsing full ones *)
790 let lazy_parse = not genv
.local_config
.SLC.use_full_fidelity_parser
in
791 (* full init - too many files to trace all of them *)
802 ~profile_label
:"parsing"
805 if not do_naming
then
808 let ctx = Provider_utils.ctx_from_server_env
env in
815 ~profile_label
:"update file deps"
818 naming
env t ~profile_label
:"naming" ~profiling
820 let write_symbol_info_init
821 (genv
: ServerEnv.genv
)
822 (env : ServerEnv.env)
823 (profiling
: CgroupProfiler.Profiling.t) : ServerEnv.env * float =
825 match ServerArgs.write_symbol_info genv
.options
with
826 | None
-> failwith
"No write directory specified for --write-symbol-info"
830 initialize_naming_table
831 "write symbol info initialization"
836 let ctx = Provider_utils.ctx_from_server_env
env in
843 ~profile_label
:"update file deps"
846 let (env, t) = naming
env t ~profile_label
:"naming" ~profiling
in
847 let index_paths = env.swriteopt
.symbol_write_index_paths
in
849 if List.length
index_paths > 0 then
850 List.fold
index_paths ~init
:[] ~f
:(fun acc
path ->
851 if Sys.file_exists
path then
852 Relative_path.from_root ~suffix
:path :: acc
856 let fast = Naming_table.to_fast
env.naming_table
in
857 let failed_parsing = Errors.get_failed_files
env.errorl
Errors.Parsing
in
859 Relative_path.Set.fold
861 ~f
:(fun x m
-> Relative_path.Map.remove m x
)
864 let exclude_hhi = not
env.swriteopt
.symbol_write_include_hhi
in
865 let ignore_paths = env.swriteopt
.symbol_write_ignore_paths
in
866 Relative_path.Map.fold
fast ~init
:[] ~f
:(fun path _ acc
->
867 match Naming_table.get_file_info
env.naming_table
path with
871 (exclude_hhi && Relative_path.is_hhi
(Relative_path.prefix
path))
872 || List.exists
ignore_paths (fun ignore
->
873 String.equal
(Relative_path.S.to_string
path) ignore
)
879 (* Ensure we are writing to fresh files *)
882 if not
(Sys.is_directory
out_dir) then
885 Array.length
(Sys.readdir
out_dir) > 0
887 Sys_utils.mkdir_p
out_dir;
890 if is_invalid then failwith
"JSON write directory is invalid or non-empty";
892 Hh_logger.log
"Writing JSON to: %s" out_dir;
894 let ctx = Provider_utils.ctx_from_server_env
env in
895 let root_path = env.swriteopt
.symbol_write_root_path
in
896 let hhi_path = env.swriteopt
.symbol_write_hhi_path
in
897 (* TODO(milliechen): log memory for this step *)
898 Symbol_info_writer.go genv
.workers
ctx out_dir root_path hhi_path files;
902 (* If we fail to load a saved state, fall back to typechecking everything *)
904 (genv
: ServerEnv.genv
)
905 (env : ServerEnv.env)
906 (profiling
: CgroupProfiler.Profiling.t) : ServerEnv.env * float =
909 |> Telemetry.float_ ~key
:"start_time" ~
value:(Unix.gettimeofday
())
910 |> Telemetry.string_ ~key
:"reason" ~
value:"lazy_full_init"
912 let is_check_mode = ServerArgs.check_mode genv
.options
in
915 initialize_naming_table
917 "full initialization"
922 if not
is_check_mode then
923 SearchServiceRunner.update_fileinfo_map
env.naming_table
SearchUtils.Init
;
924 let fast = Naming_table.to_fast
env.naming_table
in
925 let failed_parsing = Errors.get_failed_files
env.errorl
Errors.Parsing
in
927 Relative_path.Set.fold
929 ~f
:(fun x m
-> Relative_path.Map.remove m x
)
932 let fnl = Relative_path.Map.keys
fast in
934 if is_check_mode then
935 start_delegate_if_needed
env genv
(List.length
fnl) env.errorl
945 ~profile_label
:"type check"
948 let run_experiment () =
949 let ctx = Provider_utils.ctx_from_server_env
env in
950 let t_full_init = Unix.gettimeofday
() in
951 let fast = Direct_decl_service.go
ctx genv
.workers
(fst
(indexing genv
)) in
952 let t = Hh_logger.log_duration
"parsing decl" t_full_init in
953 let naming_table = Naming_table.update_many
env.naming_table fast in
954 let t = Hh_logger.log_duration
"updating naming table" t in
955 let env = { env with naming_table } in
962 ~profile_label
:"update files"
965 let (env, t) = naming
env t ~profile_label
:"naming" ~profiling
in
966 let fnl = Relative_path.Map.keys
fast in
967 if not
is_check_mode then
968 SearchServiceRunner.update_fileinfo_map
env.naming_table SearchUtils.Init
;
969 let type_check_result =
976 ~profile_label
:"type check"
979 Hh_logger.log_duration
"full init" t_full_init |> ignore
;
983 GlobalOptions.tco_use_direct_decl_parser
984 (ServerConfig.parser_options genv
.config
)
986 Hh_logger.log
"full init experiment";
989 Hh_logger.log
"full init";
994 (genv
: ServerEnv.genv
)
995 (env : ServerEnv.env)
996 (profiling
: CgroupProfiler.Profiling.t) : ServerEnv.env * float =
997 initialize_naming_table "parse-only initialization" genv
env profiling
999 let get_mergebase (mergebase_future
: Hg.hg_rev
option Future.t) :
1001 match Future.get mergebase_future
with
1004 match mergebase
with
1005 | Some mergebase
-> Hh_logger.log
"Got mergebase hash: %s" mergebase
1006 | None
-> Hh_logger.log
"No mergebase hash"
1011 "Getting mergebase hash failed: %s"
1012 (Future.error_to_string error
);
1015 let post_saved_state_initialization
1016 ~
(genv
: ServerEnv.genv
)
1017 ~
(env : ServerEnv.env)
1018 ~
(state_result
: loaded_info
* Relative_path.Set.t)
1019 (profiling
: CgroupProfiler.Profiling.t) : ServerEnv.env * float =
1020 let ((loaded_info
: ServerInitTypes.loaded_info
), changed_while_parsing
) =
1023 let trace = genv
.local_config
.SLC.trace_parsing
in
1024 let hg_aware = genv
.local_config
.SLC.hg_aware in
1026 naming_table_fallback_fn
;
1036 naming_table_fn
= _
;
1037 corresponding_rev
= _
;
1039 naming_table_manifold_path
;
1043 if hg_aware then Option.iter mergebase_rev ~f
:ServerRevisionTracker.initialize
;
1050 mergebase
= get_mergebase mergebase
;
1051 naming_table_manifold_path
;
1054 ( if deptable_is_64bit
then
1055 match ServerArgs.save_64bit genv
.options
with
1056 | Some new_edges_dir
->
1057 Typing_deps_mode.SaveCustomMode
1058 { graph
= Some deptable_fn
; new_edges_dir
}
1059 | None
-> Typing_deps_mode.CustomMode
(Some deptable_fn
)
1061 Typing_deps_mode.SQLiteMode
);
1065 let (decl_and_typing_error_files
, naming_and_parsing_error_files
) =
1066 SaveStateService.partition_error_files_tf
1068 [Errors.Decl
; Errors.Typing
]
1070 let (_old_parsing_phase
, old_parsing_error_files
) =
1072 List.find old_errors ~f
:(fun (phase
, _files
) ->
1074 | Errors.Parsing
-> true
1077 | Some
(a
, b
) -> (a
, b
)
1078 | None
-> (Errors.Parsing
, Relative_path.Set.empty
)
1081 "Number of files with Decl and Typing errors: %d"
1082 (Relative_path.Set.cardinal decl_and_typing_error_files
);
1085 "Number of files with Naming and Parsing errors: %d"
1086 (Relative_path.Set.cardinal naming_and_parsing_error_files
);
1088 let (decl_and_typing_error_files
, naming_and_parsing_error_files
) =
1089 SaveStateService.partition_error_files_tf
1091 [Errors.Decl
; Errors.Typing
]
1093 (* Parse and name all dirty files uniformly *)
1096 ~init
:Relative_path.Set.empty
1097 ~f
:Relative_path.Set.union
1099 naming_and_parsing_error_files
;
1105 let t = Unix.gettimeofday
() in
1106 let dirty_files = Relative_path.Set.union
dirty_files changed_while_parsing
in
1108 Relative_path.Set.filter dirty_files ~f
:FindUtils.path_filter
1110 ( CgroupProfiler.collect_cgroup_stats ~stage
:"remove fixmes" ~profiling
1111 @@ fun () -> Fixme_provider.remove_batch
parsing_files );
1112 let parsing_files_list = Relative_path.Set.elements
parsing_files in
1113 (* Parse dirty files only *)
1114 let next = MultiWorker.next genv
.workers
parsing_files_list in
1121 ~count
:(List.length
parsing_files_list)
1124 ~profile_label
:"parse dirty files"
1127 SearchServiceRunner.update_fileinfo_map
1129 SearchUtils.TypeChecker
;
1130 let ctx = Provider_utils.ctx_from_server_env
env in
1137 ~profile_label
:"update file deps"
1141 naming_from_saved_state
1145 naming_table_fallback_fn
1149 (* Do global naming on all dirty files *)
1150 let (env, t) = naming
env t ~profile_label
:"naming dirty files" ~profiling
in
1152 (* Add all files from fast to the files_info object *)
1153 let fast = Naming_table.to_fast
env.naming_table in
1154 let failed_parsing = Errors.get_failed_files
env.errorl
Errors.Parsing
in
1156 Relative_path.Set.fold
1158 ~f
:(fun x m
-> Relative_path.Map.remove m x
)
1164 disk_needs_parsing
=
1165 Relative_path.Set.union
env.disk_needs_parsing changed_while_parsing
;
1168 (* Separate the dirty files from the files whose decl only changed *)
1169 (* Here, for each dirty file, we compare its hash to the one saved
1170 in the saved state. If the hashes are the same, then the declarations
1171 on the file have not changed and we only need to retypecheck that file,
1172 not all of its dependencies.
1173 We call these files "similar" to their previous versions. *)
1174 let partition_similar dirty_files =
1175 Relative_path.Set.partition
1177 let info1 = Naming_table.get_file_info old_naming_table f
in
1178 let info2 = Naming_table.get_file_info
env.naming_table f
in
1179 match (info1, info2) with
1180 | (Some x
, Some y
) ->
1181 (match (x
.FileInfo.hash
, y
.FileInfo.hash
) with
1182 | (Some x
, Some y
) -> Int64.equal x y
1187 let (dirty_master_files_unchanged_hash
, dirty_master_files_changed_hash
) =
1188 partition_similar dirty_master_files
1190 let (dirty_local_files_unchanged_hash
, dirty_local_files_changed_hash
) =
1191 partition_similar dirty_local_files
1196 naming_table = Naming_table.combine old_naming_table
env.naming_table;
1197 (* The only reason old_parsing_error_files are added to disk_needs_parsing
1198 here is because of an issue that seems to be already tracked in T30786759 *)
1199 disk_needs_parsing
= old_parsing_error_files
;
1201 Relative_path.Set.union
env.needs_recheck decl_and_typing_error_files
;
1204 (* Update the fileinfo object's dependencies now that we have full fast *)
1211 ~profile_label
:"update files again"
1219 ~dirty_master_files_unchanged_hash
1220 ~dirty_master_files_changed_hash
1221 ~dirty_local_files_unchanged_hash
1222 ~dirty_local_files_changed_hash
1226 let saved_state_init
1227 ~
(load_state_approach
: load_state_approach
)
1228 (genv
: ServerEnv.genv
)
1229 (env : ServerEnv.env)
1231 (profiling
: CgroupProfiler.Profiling.t) :
1232 ( (ServerEnv.env * float) * (loaded_info
* Relative_path.Set.t),
1235 let t = Unix.gettimeofday
() in
1236 let attempt_fix = genv
.local_config
.SLC.attempt_fix_credentials
in
1238 match Security.check_credentials ~
attempt_fix with
1240 HackEventLogger.credentials_check_end
1241 (Printf.sprintf
"saved_state_init: %s" (Security.show_success success
))
1244 let kind = Security.to_error_kind_string error
in
1245 let message = Security.to_error_message_string error
in
1246 Hh_logger.log
"Error kind: %s\nError message: %s" kind message;
1247 HackEventLogger.credentials_check_failure
1248 (Printf.sprintf
"saved_state_init: [%s]" kind)
1252 ServerProgress.send_progress_to_monitor
"loading saved state";
1254 let ctx = Provider_utils.ctx_from_server_env
env in
1255 (* A historical quirk: we allowed the timeout once while downloading+loading *)
1256 (* saved-state, and then once again while waiting to get dirty files from hg *)
1257 let timeout = 2 * genv
.local_config
.SLC.load_state_script_timeout
in
1258 (* following function will be run under the timeout *)
1259 let do_ (_id
: Timeout.t) : (loaded_info
, load_state_error
) result =
1261 CgroupProfiler.collect_cgroup_stats ~profiling ~stage
:"load saved state"
1263 match load_state_approach
with
1264 | Precomputed info
->
1265 Ok
(use_precomputed_state_exn genv
ctx info profiling
)
1266 | Load_state_natively
->
1267 download_and_load_state_exn ~target
:None ~genv ~
ctx ~
root
1268 | Load_state_natively_with_target target
->
1269 download_and_load_state_exn ~target
:(Some target
) ~genv ~
ctx ~
root
1276 Timeout.with_timeout
1279 ~on_timeout
:(fun (_
: Timeout.timings
) -> Error Load_state_timeout
)
1281 | Error error
-> Error error
1283 let changed_while_parsing = get_updates_exn genv
root in
1284 Ok
(loaded_info
, changed_while_parsing)
1286 let stack = Utils.Callstack
(Printexc.get_backtrace
()) in
1287 Error
(Load_state_unhandled_exception
{ exn; stack })
1289 match state_result with
1290 | Error err
-> Error err
1291 | Ok
state_result ->
1292 ServerProgress.send_progress_to_monitor
"loading saved state succeeded";
1294 post_saved_state_initialization ~
state_result ~
env ~genv profiling
1296 Ok
((env, t), state_result)