Keep real recheck stats around
[hiphop-php.git] / hphp / hack / src / server / serverEnv.ml
blobe1e1a12c0a9ed609755cae256199dbdfd716528b
1 (**
2 * Copyright (c) 2015, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 open Hh_core
12 (*****************************************************************************)
13 (* Recheck loop types. *)
14 (*****************************************************************************)
16 type recheck_loop_stats = {
17 (** Watchman subscription has gone down, so state of the world after the
18 * recheck loop may not reflect what is actually on disk. *)
19 updates_stale : bool;
20 rechecked_batches : int;
21 rechecked_count : int;
22 (* includes dependencies *)
23 total_rechecked_count : int;
24 } [@@deriving show]
26 let empty_recheck_loop_stats = {
27 updates_stale = false;
28 rechecked_batches = 0;
29 rechecked_count = 0;
30 total_rechecked_count = 0;
33 type recheck_info = {
34 stats: recheck_loop_stats;
35 recheck_id : string;
36 } [@@deriving show]
38 (*****************************************************************************)
39 (* The "static" environment, initialized first and then doesn't change *)
40 (*****************************************************************************)
42 type genv = {
43 options : ServerArgs.options;
44 config : ServerConfig.t;
45 local_config : ServerLocalConfig.t;
46 workers : MultiWorker.worker list option;
47 (* Returns the list of files under .hhconfig, subject to a filter *)
48 indexer : (string -> bool) -> (unit -> string list);
49 (* Each time this is called, it should return the files that have changed
50 * since the last invocation *)
51 notifier_async : unit -> ServerNotifierTypes.notifier_changes;
52 (* If this FD is readable, next call to notifier_async () should read
53 * something from it. *)
54 notifier_async_reader : unit -> Buffered_line_reader.t option;
55 notifier : unit -> SSet.t;
56 (* If daemons are spawned as part of the init process, wait for them here
57 * e.g. wait until dfindlib is ready (in the case that watchman is absent) *)
58 wait_until_ready : unit -> unit;
59 mutable debug_channels : (Timeout.in_channel * out_channel) option;
62 (*****************************************************************************)
63 (* The environment constantly maintained by the server *)
64 (*****************************************************************************)
66 type full_check_status =
67 (* Some updates have not been fully processed. We get into this state every
68 * time file contents change (on disk, or through IDE notifications).
69 * Operations that depend on global state (like taking full error list, or
70 * looking up things in dependency table) will have stale results. *)
71 | Full_check_needed
72 (* Same as above, except server will actively try to process outstanding
73 * changes (by going into ServerTypeCheck from main loop - this might need to
74 * be repeated several times before progressing to Full_check_done, due to
75 * ability to interrupt typecheck jobs).
76 * Server starts in this state, and we also enter it from Full_check_needed
77 * whenever there is a command requiring full check pending, or when user
78 * saves a file. *)
79 | Full_check_started
80 (* All the changes have been fully processed. *)
81 | Full_check_done
82 [@@deriving show]
84 (* In addition to this environment, many functions are storing and
85 * updating ASTs, NASTs, and types in a shared space
86 * (see respectively Parser_heap, Naming_table, Typing_env).
87 * The Ast.id are keys to index this shared space.
89 type env = {
90 naming_table : Naming_table.t;
91 tcopt : TypecheckerOptions.t;
92 popt : ParserOptions.t;
93 (* Errors are indexed by files that were known to GENERATE errors in
94 * corresponding phases. Note that this is different from HAVING errors -
95 * it's possible for checking of A to generate error in B - in this case
96 * Errors.get_failed_files Typing should contain A, not B.
97 * Conversly, if declaring A will require declaring B, we should put
98 * B in failed decl. Same if checking A will cause declaring B (via lazy
99 * decl).
101 * During recheck, we add those files to the set of files to reanalyze
102 * at each stage in order to regenerate their error lists. So those
103 * failed_ sets are the main piece of mutable state that incremental mode
104 * needs to maintain - the errors themselves are more of a cache, and should
105 * always be possible to be regenerated based on those sets. *)
106 errorl : Errors.t [@opaque];
107 (* failed_naming is used as kind of a dependency tracking mechanism:
108 * if files A.php and B.php both define class C, then those files are
109 * mutually depending on each other (edit to one might resolve naming
110 * ambiguity and change the interpretation of the other). Both of those
111 * files being inside failed_naming is how we track the need to
112 * check for this condition.
114 * See test_naming_errors.ml and test_failed_naming.ml
116 failed_naming : Relative_path.Set.t;
117 persistent_client : ClientProvider.client option [@opaque];
118 (* Whether last received IDE command was IDE_IDLE *)
119 ide_idle : bool;
120 (* Timestamp of last IDE file synchronization command *)
121 last_command_time : float;
122 (* Timestamp of last query for disk changes *)
123 last_notifier_check_time : float;
124 (* Timestamp of last ServerIdle.go run *)
125 last_idle_job_time : float;
126 (* The map from full path to synchronized file contents *)
127 editor_open_files : Relative_path.Set.t;
128 (* Files which parse trees were invalidated (because they changed on disk
129 * or in editor) and need to be re-parsed *)
130 ide_needs_parsing : Relative_path.Set.t;
131 disk_needs_parsing : Relative_path.Set.t;
132 (* Declarations that became invalidated and moved to "old" part of the heap.
133 * We keep them there to be used in "determining changes" step of recheck.
134 * (when they are compared to "new" versions). Depending on lazy decl to
135 * compute "new" versions in all the other scenarios (like IDE queries) *)
136 needs_phase2_redecl : Relative_path.Set.t;
137 (* Files that need to be typechecked before commands that depend on global
138 * state (like full list of errors, build, or find all references) can be
139 * executed . After full check this should be empty, unless that check was
140 * cancelled mid-flight, in which case full_check will be set to
141 * Full_check_started and entire thing will be retried on next iteration. *)
142 needs_recheck : Relative_path.Set.t;
143 init_env : init_env;
144 full_check : full_check_status;
145 prechecked_files : prechecked_files_status;
146 (* Not every caller of rechecks expects that they can be interrupted,
147 * so making it opt-in by setting this flag at call site *)
148 can_interrupt : bool;
149 interrupt_handlers: genv -> env ->
150 (Unix.file_descr * env MultiThreadedCall.interrupt_handler) list;
151 (* When persistent client sends a command that cannot be handled (due to
152 * thread safety) we put the continuation that finishes handling it here. *)
153 pending_command_needs_writes : (env -> env) option;
154 (* When persistent client sends a command that cannot be immediately handled
155 * (due to needing full check) we put the continuation that finishes handling
156 * it here. The string specifies a reason why this command needs full
157 * recheck (for logging/debugging purposes) *)
158 persistent_client_pending_command_needs_full_check:
159 ((env -> env) * string) option;
160 (* Same as above, but for non-persistent clients *)
161 default_client_pending_command_needs_full_check:
162 ((env -> env) * string * ClientProvider.client) option [@opaque];
163 (* The diagnostic subscription information of the current client *)
164 diag_subscribe : Diagnostic_subscription.t option;
165 recent_recheck_loop_stats : recheck_loop_stats;
166 last_recheck_info : recheck_info option;
167 (* Symbols for locally changed files *)
168 local_symbol_table : SearchUtils.local_tracking_env ref [@opaque];
170 [@@deriving show]
172 and dirty_deps = {
173 (* We are rechecking dirty files to bootstrap the dependency graph.
174 * After this is done we need to also recheck full fan-out (in this updated
175 * graph) of provided set. *)
176 dirty_local_deps : Typing_deps.DepSet.t;
177 (* The fan-outs of those nodes were not expanded yet. *)
178 dirty_master_deps : Typing_deps.DepSet.t;
179 (* Files that have been rechecked since server startup *)
180 rechecked_files : Relative_path.Set.t;
181 (* Those deps have already been checked against their interaction with
182 * dirty_master_deps. Storing them here to avoid checking it over and over *)
183 clean_local_deps : Typing_deps.DepSet.t;
186 (* When using prechecked files we split initial typechecking in two phases
187 * (dirty files and a subset of their fan-out). Other init types compute the
188 * full fan-out up-front. *)
189 and prechecked_files_status =
190 | Prechecked_files_disabled
191 | Initial_typechecking of dirty_deps
192 | Prechecked_files_ready of dirty_deps
194 and init_env = {
195 init_start_t : float;
196 (* Whether a full check was ever completed since init. *)
197 needs_full_init : bool;
198 (* Additional data associated with init that we want to log when a first full
199 * check completes. *)
200 state_distance : int option;
201 approach_name : string;
202 init_error : string option;
203 init_type : string;
206 let list_files env =
207 let acc = List.fold_right
208 ~f:begin fun error (acc : SSet.t) ->
209 let pos = Errors.get_pos error in
210 SSet.add (Relative_path.to_absolute (Pos.filename pos)) acc
212 ~init:SSet.empty
213 (Errors.get_error_list env.errorl) in
214 SSet.elements acc