adding comments and small refactors
[hiphop-php.git] / hphp / hack / src / server / serverEnv.ml
bloba1ba37f82a9c8e74d506a2854f33d56133fb3f23
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 per_batch_telemetry: Telemetry.t list;
21 rechecked_count: int;
22 (* includes dependencies *)
23 total_rechecked_count: int;
24 duration: float;
25 (* in seconds *)
26 recheck_id: string;
27 any_full_checks: bool;
29 [@@deriving show]
31 let empty_recheck_loop_stats ~(recheck_id : string) : recheck_loop_stats =
33 updates_stale = false;
34 per_batch_telemetry = [];
35 rechecked_count = 0;
36 total_rechecked_count = 0;
37 duration = 0.;
38 recheck_id;
39 any_full_checks = false;
42 (** The format of this json is user-facing, returned from 'hh check --json' *)
43 let recheck_loop_stats_to_user_telemetry (stats : recheck_loop_stats) :
44 Telemetry.t =
45 Telemetry.create ()
46 |> Telemetry.string_ ~key:"id" ~value:stats.recheck_id
47 |> Telemetry.float_ ~key:"time" ~value:stats.duration
48 |> Telemetry.int_ ~key:"count" ~value:stats.total_rechecked_count
49 |> Telemetry.int_ ~key:"reparse_count" ~value:stats.rechecked_count
50 |> Telemetry.object_list
51 ~key:"per_batch"
52 ~value:(List.rev stats.per_batch_telemetry)
53 |> Telemetry.bool_ ~key:"updates_stale" ~value:stats.updates_stale
54 |> Telemetry.bool_ ~key:"any_full_checks" ~value:stats.any_full_checks
56 (*****************************************************************************)
57 (* The "static" environment, initialized first and then doesn't change *)
58 (*****************************************************************************)
60 type genv = {
61 options: ServerArgs.options;
62 config: ServerConfig.t;
63 local_config: ServerLocalConfig.t;
64 (* Early-initialized workers to be used in MultiWorker jobs
65 * They are initialized early to keep their heaps as empty as possible. *)
66 workers: MultiWorker.worker list option;
67 (* Returns the list of files under .hhconfig, subject to a filter *)
68 indexer: (string -> bool) -> unit -> string list;
69 (* Each time this is called, it should return the files that have changed
70 * since the last invocation *)
71 notifier_async: unit -> ServerNotifierTypes.notifier_changes;
72 (* If this FD is readable, next call to notifier_async () should read
73 * something from it. *)
74 notifier_async_reader: unit -> Buffered_line_reader.t option;
75 notifier: unit -> SSet.t;
76 (* If daemons are spawned as part of the init process, wait for them here
77 * e.g. wait until dfindlib is ready (in the case that watchman is absent) *)
78 wait_until_ready: unit -> unit;
79 mutable debug_channels: (Timeout.in_channel * out_channel) option;
82 (*****************************************************************************)
83 (* The environment constantly maintained by the server *)
84 (*****************************************************************************)
86 type full_check_status =
87 (* Some updates have not been fully processed. We get into this state every
88 * time file contents change (on disk, or through IDE notifications).
89 * Operations that depend on global state (like taking full error list, or
90 * looking up things in dependency table) will have stale results. *)
91 | Full_check_needed
92 (* Same as above, except server will actively try to process outstanding
93 * changes (by going into ServerTypeCheck from main loop - this might need to
94 * be repeated several times before progressing to Full_check_done, due to
95 * ability to interrupt typecheck jobs).
96 * Server starts in this state, and we also enter it from Full_check_needed
97 * whenever there is a command requiring full check pending, or when user
98 * saves a file. *)
99 | Full_check_started
100 (* All the changes have been fully processed. *)
101 | Full_check_done
102 [@@deriving show]
104 let is_full_check_done = function
105 | Full_check_done -> true
106 | _ -> false
108 let is_full_check_needed = function
109 | Full_check_needed -> true
110 | _ -> false
112 let is_full_check_started = function
113 | Full_check_started -> true
114 | _ -> false
116 (* In addition to this environment, many functions are storing and
117 * updating ASTs, NASTs, and types in a shared space
118 * (see respectively Parser_heap, Naming_table, Typing_env).
119 * The Ast.id are keys to index this shared space.
121 type env = {
122 naming_table: Naming_table.t;
123 typing_service: typing_service;
124 tcopt: TypecheckerOptions.t;
125 popt: ParserOptions.t;
126 gleanopt: GleanOptions.t;
127 swriteopt: SymbolWriteOptions.t;
128 (* Errors are indexed by files that were known to GENERATE errors in
129 * corresponding phases. Note that this is different from HAVING errors -
130 * it's possible for checking of A to generate error in B - in this case
131 * Errors.get_failed_files Typing should contain A, not B.
132 * Conversly, if declaring A will require declaring B, we should put
133 * B in failed decl. Same if checking A will cause declaring B (via lazy
134 * decl).
136 * During recheck, we add those files to the set of files to reanalyze
137 * at each stage in order to regenerate their error lists. So those
138 * failed_ sets are the main piece of mutable state that incremental mode
139 * needs to maintain - the errors themselves are more of a cache, and should
140 * always be possible to be regenerated based on those sets. *)
141 errorl: Errors.t; [@opaque]
142 (* failed_naming is used as kind of a dependency tracking mechanism:
143 * if files A.php and B.php both define class C, then those files are
144 * mutually depending on each other (edit to one might resolve naming
145 * ambiguity and change the interpretation of the other). Both of those
146 * files being inside failed_naming is how we track the need to
147 * check for this condition.
149 * See test_naming_errors.ml and test_failed_naming.ml
151 failed_naming: Relative_path.Set.t;
152 persistent_client: ClientProvider.client option; [@opaque]
153 (* Whether last received IDE command was IDE_IDLE *)
154 ide_idle: bool;
155 (* Timestamp of last IDE file synchronization command *)
156 last_command_time: float;
157 (* Timestamp of last query for disk changes *)
158 last_notifier_check_time: float;
159 (* Timestamp of last ServerIdle.go run *)
160 last_idle_job_time: float;
161 (* The map from full path to synchronized file contents *)
162 editor_open_files: Relative_path.Set.t;
163 (* Files which parse trees were invalidated (because they changed on disk
164 * or in editor) and need to be re-parsed *)
165 ide_needs_parsing: Relative_path.Set.t;
166 disk_needs_parsing: Relative_path.Set.t;
167 (* Declarations that became invalidated and moved to "old" part of the heap.
168 * We keep them there to be used in "determining changes" step of recheck.
169 * (when they are compared to "new" versions). Depending on lazy decl to
170 * compute "new" versions in all the other scenarios (like IDE queries) *)
171 needs_phase2_redecl: Relative_path.Set.t;
172 (* Files that need to be typechecked before commands that depend on global
173 * state (like full list of errors, build, or find all references) can be
174 * executed . After full check this should be empty, unless that check was
175 * cancelled mid-flight, in which case full_check will be set to
176 * Full_check_started and entire thing will be retried on next iteration. *)
177 needs_recheck: Relative_path.Set.t;
178 init_env: init_env;
179 (* Set by `hh --pause` or `hh --resume`. Indicates whether full/global recheck
180 should be triggered on file changes. If paused, it would still be triggered
181 by commands that require a full recheck, such as STATUS, i.e., `hh`
182 on the command line. *)
183 full_recheck_on_file_changes: full_recheck_on_file_changes;
184 full_check: full_check_status;
185 prechecked_files: prechecked_files_status;
186 changed_files: Relative_path.Set.t;
187 (* Not every caller of rechecks expects that they can be interrupted,
188 * so making it opt-in by setting this flag at call site *)
189 can_interrupt: bool;
190 interrupt_handlers:
191 genv ->
192 env ->
193 (Unix.file_descr * env MultiThreadedCall.interrupt_handler) list;
194 (* Whether we should force remote type checking or not *)
195 remote: bool;
196 (* When persistent client sends a command that cannot be handled (due to
197 * thread safety) we put the continuation that finishes handling it here. *)
198 pending_command_needs_writes: (env -> env) option;
199 (* When persistent client sends a command that cannot be immediately handled
200 * (due to needing full check) we put the continuation that finishes handling
201 * it here. The string specifies a reason why this command needs full
202 * recheck (for logging/debugging purposes) *)
203 persistent_client_pending_command_needs_full_check:
204 ((env -> env) * string) option;
205 (* Same as above, but for non-persistent clients *)
206 default_client_pending_command_needs_full_check:
207 ((env -> env) * string * ClientProvider.client) option;
208 [@opaque]
209 (* The diagnostic subscription information of the current client *)
210 diag_subscribe: Diagnostic_subscription.t option;
211 last_recheck_loop_stats: recheck_loop_stats;
212 last_recheck_loop_stats_for_actual_work: recheck_loop_stats option;
213 (* Symbols for locally changed files *)
214 local_symbol_table: SearchUtils.si_env; [@opaque]
216 [@@deriving show]
218 (* Global rechecks in response to file changes can be paused. If the user
219 changes the state to `Paused` during an ongoing recheck, we should cancel
220 that recheck.
222 The effect of the `PAUSE true` RPC during a recheck is that the recheck will
223 be canceled, while the result of `PAUSE false` is that the client will wait
224 for the recheck to be finished.
226 NOTE:
227 Interrupt handlers are currently set up and used during type checking.
228 MultiWorker executor (MultiThreadedCall) selects worker file descriptors as
229 well as some input channels from clients. If a client is sending an RPC over
230 such a channel, the executor will call that channel's designated handler.
232 `ServerMain` sets up a few of these handlers, and the one we're interested in
233 for this change is the __priority__ client interrupt handler. This handler is
234 only listening to RPCs sent over the priority channel. The client decides
235 which RPCs are sent over the priority channel vs. the default channel.
237 In the priority client interrupt handler, we actually handle the RPC that
238 the client is sending. We then return the updated environment to
239 the MultiWorker executor, along with the decision on whether it should
240 continue executing or cancel. We examine the environment after handling
241 the RPC to check whether the user paused file changes-driven global rechecks
242 during the *current* recheck. If that is the case, then the current recheck
243 will be canceled.
245 The reason we care about whether it's the current recheck that's paused or
246 some other one is because global rechecks can still happen even if
247 *file changes-driven* global rechecks are paused. This is because the user
248 can explicitly request a full recheck by running an `hh_client` command that
249 *requires* a full recheck. There are a number of such commands, but the most
250 obvious one is just `hh` (defaults to `hh check`).
252 It is possible to immediately set the server into paused mode AND cancel
253 the current recheck. There are two cases the user might wish to do this in:
254 1) The user notices that some change they made is taking a while to
255 recheck, so they want to cancel the recheck because they want to make
256 further changes, or retry the recheck with the `--remote` argument
257 2) While the server is in the paused state, the user explicitly starts
258 a full recheck, but then decides that they want to cancel it
260 In both cases, running `hh --pause` on the command line should stop
261 the recheck if it's in the middle of the type checking phase.
263 Note that interrupts are only getting set up for the type checking phase,
264 so if the server is in the middle of, say, the redecl phase, it's not going
265 to be interrupted until it gets to the type checking itself. In some cases,
266 redecling can be very costly. For example, if we redecl using the folded decl
267 approach, adding `extends ISomeInterface` to `IBaseInterface` that has many
268 descendants (implementers and their descendants) requires redeclaring all
269 the descendants of `IBaseInterface`. However, redecling using the shallow
270 decl approach should be considerably less costly, therefore it may not be
271 worth it to support interrupting redecling. *)
272 and full_recheck_on_file_changes =
273 | Not_paused
274 | Paused of paused_env
275 | Resumed
277 and paused_env = { paused_recheck_id: string option }
279 and dirty_deps = {
280 (* We are rechecking dirty files to bootstrap the dependency graph.
281 * After this is done we need to also recheck full fan-out (in this updated
282 * graph) of provided set. *)
283 dirty_local_deps: Typing_deps.DepSet.t;
284 (* The fan-outs of those nodes were not expanded yet. *)
285 dirty_master_deps: Typing_deps.DepSet.t;
286 (* Files that have been rechecked since server startup *)
287 rechecked_files: Relative_path.Set.t;
288 (* Those deps have already been checked against their interaction with
289 * dirty_master_deps. Storing them here to avoid checking it over and over *)
290 clean_local_deps: Typing_deps.DepSet.t;
293 and typing_service = {
294 delegate_state: Typing_service_delegate.state; [@opaque]
295 enabled: bool;
298 (* When using prechecked files we split initial typechecking in two phases
299 * (dirty files and a subset of their fan-out). Other init types compute the
300 * full fan-out up-front. *)
301 and prechecked_files_status =
302 | Prechecked_files_disabled
303 | Initial_typechecking of dirty_deps
304 | Prechecked_files_ready of dirty_deps
306 and init_env = {
307 approach_name: string;
308 (* The info describing the CI job the server is a part of, if any *)
309 ci_info: Ci_util.info option Future.t option; [@opaque]
310 init_error: string option;
311 init_id: string;
312 init_start_t: float;
313 init_type: string;
314 mergebase: string option;
315 (* Whether a full check was ever completed since init, and why it was needed *)
316 why_needed_full_init: Telemetry.t option;
317 recheck_id: string option;
318 (* Additional data associated with init that we want to log when a first full
319 * check completes. *)
320 state_distance: int option;
323 let list_files env =
324 let acc =
325 List.fold_right
327 begin
328 fun error (acc : SSet.t) ->
329 let pos = Errors.get_pos error in
330 SSet.add (Relative_path.to_absolute (Pos.filename pos)) acc
332 ~init:SSet.empty
333 (Errors.get_error_list env.errorl)
335 SSet.elements acc
337 let add_changed_files env changed_files =
339 env with
340 changed_files = Relative_path.Set.union env.changed_files changed_files;