server.ml 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. open Printf
  2. open Globals
  3. open Ast
  4. open Common
  5. open CompilationServer
  6. open DisplayTypes.DisplayMode
  7. open Timer
  8. open Type
  9. open DisplayOutput
  10. open Json
  11. exception Dirty of path
  12. exception ServerError of string
  13. let prompt = ref false
  14. let start_time = ref (Timer.get_time())
  15. let is_debug_run = try Sys.getenv "HAXEDEBUG" = "1" with _ -> false
  16. type context = {
  17. com : Common.context;
  18. mutable flush : unit -> unit;
  19. mutable setup : unit -> unit;
  20. mutable messages : compiler_message list;
  21. mutable has_next : bool;
  22. mutable has_error : bool;
  23. }
  24. let s_version with_build =
  25. let pre = Option.map_default (fun pre -> "-" ^ pre) "" version_pre in
  26. let build =
  27. match with_build, Version.version_extra with
  28. | true, Some (_,build) -> "+" ^ build
  29. | _, _ -> ""
  30. in
  31. Printf.sprintf "%d.%d.%d%s%s" version_major version_minor version_revision pre build
  32. let check_display_flush ctx f_otherwise = match ctx.com.json_out with
  33. | None ->
  34. begin match ctx.com.display.dms_kind with
  35. | DMDiagnostics global->
  36. List.iter (fun msg ->
  37. let msg,p,kind = match msg with
  38. | CMInfo(msg,p) -> msg,p,DisplayTypes.DiagnosticsSeverity.Information
  39. | CMWarning(msg,p) -> msg,p,DisplayTypes.DiagnosticsSeverity.Warning
  40. | CMError(msg,p) -> msg,p,DisplayTypes.DiagnosticsSeverity.Error
  41. in
  42. add_diagnostics_message ctx.com msg p DisplayTypes.DiagnosticsKind.DKCompilerError kind
  43. ) (List.rev ctx.messages);
  44. raise (Completion (Diagnostics.print ctx.com global))
  45. | _ ->
  46. f_otherwise ()
  47. end
  48. | Some api ->
  49. if ctx.has_error then begin
  50. let errors = List.map (fun msg ->
  51. let msg,p,i = match msg with
  52. | CMInfo(msg,p) -> msg,p,3
  53. | CMWarning(msg,p) -> msg,p,2
  54. | CMError(msg,p) -> msg,p,1
  55. in
  56. JObject [
  57. "severity",JInt i;
  58. "location",Genjson.generate_pos_as_location p;
  59. "message",JString msg;
  60. ]
  61. ) (List.rev ctx.messages) in
  62. api.send_error errors
  63. end
  64. let default_flush ctx =
  65. check_display_flush ctx (fun () ->
  66. List.iter
  67. (fun msg -> match msg with
  68. | CMInfo _ -> print_endline (compiler_message_string msg)
  69. | CMWarning _ | CMError _ -> prerr_endline (compiler_message_string msg)
  70. )
  71. (List.rev ctx.messages);
  72. if ctx.has_error && !prompt then begin
  73. print_endline "Press enter to exit...";
  74. ignore(read_line());
  75. end;
  76. if ctx.has_error then exit 1
  77. )
  78. let create_context params =
  79. let ctx = {
  80. com = Common.create version (s_version true) params;
  81. flush = (fun()->());
  82. setup = (fun()->());
  83. messages = [];
  84. has_next = false;
  85. has_error = false;
  86. } in
  87. ctx.flush <- (fun() -> default_flush ctx);
  88. ctx
  89. let parse_hxml_data data =
  90. let lines = Str.split (Str.regexp "[\r\n]+") data in
  91. List.concat (List.map (fun l ->
  92. let l = unquote (ExtString.String.strip l) in
  93. if l = "" || l.[0] = '#' then
  94. []
  95. else if l.[0] = '-' then
  96. try
  97. let a, b = ExtString.String.split l " " in
  98. [unquote a; unquote (ExtString.String.strip b)]
  99. with
  100. _ -> [l]
  101. else
  102. [l]
  103. ) lines)
  104. let parse_hxml file =
  105. let ch = IO.input_channel (try open_in_bin file with _ -> raise Not_found) in
  106. let data = IO.read_all ch in
  107. IO.close_in ch;
  108. parse_hxml_data data
  109. let ssend sock str =
  110. let rec loop pos len =
  111. if len = 0 then
  112. ()
  113. else
  114. let s = Unix.send sock str pos len [] in
  115. loop (pos + s) (len - s)
  116. in
  117. loop 0 (Bytes.length str)
  118. let current_stdin = ref None
  119. let parse_file cs com file p =
  120. let cc = CommonCache.get_cache cs com in
  121. let ffile = Path.unique_full_path file in
  122. let is_display_file = ffile = (DisplayPosition.display_position#get).pfile in
  123. match is_display_file, !current_stdin with
  124. | true, Some stdin when Common.defined com Define.DisplayStdin ->
  125. TypeloadParse.parse_file_from_string com file p stdin
  126. | _ ->
  127. let ftime = file_time ffile in
  128. let data = Std.finally (Timer.timer ["server";"parser cache"]) (fun () ->
  129. try
  130. let cfile = cc#find_file ffile in
  131. if cfile.c_time <> ftime then raise Not_found;
  132. Parser.ParseSuccess(cfile.c_package,cfile.c_decls)
  133. with Not_found ->
  134. let parse_result = TypeloadParse.parse_file com file p in
  135. let info,is_unusual = match parse_result with
  136. | ParseError(_,_,_) -> "not cached, has parse error",true
  137. | ParseDisplayFile _ -> "not cached, is display file",true
  138. | ParseSuccess data ->
  139. begin try
  140. (* We assume that when not in display mode it's okay to cache stuff that has #if display
  141. checks. The reasoning is that non-display mode has more information than display mode. *)
  142. if not com.display.dms_display then raise Not_found;
  143. let ident = Hashtbl.find Parser.special_identifier_files ffile in
  144. Printf.sprintf "not cached, using \"%s\" define" ident,true
  145. with Not_found ->
  146. cc#cache_file ffile ftime data;
  147. "cached",false
  148. end
  149. in
  150. if is_unusual then ServerMessage.parsed com "" (ffile,info);
  151. parse_result
  152. ) () in
  153. data
  154. module ServerCompilationContext = struct
  155. type t = {
  156. (* If true, prints some debug information *)
  157. verbose : bool;
  158. (* The list of changed directories per-signature *)
  159. changed_directories : (Digest.t,cached_directory list) Hashtbl.t;
  160. (* A reference to the compilation server instance *)
  161. cs : CompilationServer.t;
  162. (* A list of class paths per-signature *)
  163. class_paths : (Digest.t,string list) Hashtbl.t;
  164. (* Increased for each typed module *)
  165. mutable mark_loop : int;
  166. (* Increased for each compilation *)
  167. mutable compilation_step : int;
  168. (* The [mark_loop] value at which we started the current compilation *)
  169. mutable compilation_mark : int;
  170. (* A list of delays which are run after compilation *)
  171. mutable delays : (unit -> unit) list;
  172. (* True if it's an actual compilation, false if it's a display operation *)
  173. mutable was_compilation : bool;
  174. }
  175. let create verbose cs = {
  176. verbose = verbose;
  177. cs = cs;
  178. class_paths = Hashtbl.create 0;
  179. changed_directories = Hashtbl.create 0;
  180. compilation_step = 0;
  181. compilation_mark = 0;
  182. mark_loop = 0;
  183. delays = [];
  184. was_compilation = false;
  185. }
  186. let add_delay sctx f =
  187. sctx.delays <- f :: sctx.delays
  188. let run_delays sctx =
  189. let fl = sctx.delays in
  190. sctx.delays <- [];
  191. List.iter (fun f -> f()) fl
  192. let reset sctx =
  193. Hashtbl.clear sctx.changed_directories;
  194. sctx.was_compilation <- false
  195. end
  196. open ServerCompilationContext
  197. let stat dir =
  198. (Unix.stat (Path.remove_trailing_slash dir)).Unix.st_mtime
  199. (* Gets a list of changed directories for the current compilation. *)
  200. let get_changed_directories sctx (ctx : Typecore.typer) =
  201. let t = Timer.timer ["server";"module cache";"changed dirs"] in
  202. let cs = sctx.cs in
  203. let com = ctx.Typecore.com in
  204. let sign = Define.get_signature com.defines in
  205. let dirs = try
  206. (* First, check if we already have determined changed directories for current compilation. *)
  207. Hashtbl.find sctx.changed_directories sign
  208. with Not_found ->
  209. let dirs = try
  210. (* Next, get all directories from the cache and filter the ones that haven't changed. *)
  211. let all_dirs = cs#find_directories sign in
  212. let dirs = List.fold_left (fun acc dir ->
  213. try
  214. let time' = stat dir.c_path in
  215. if dir.c_mtime < time' then begin
  216. dir.c_mtime <- time';
  217. let sub_dirs = Path.find_directories (platform_name com.platform) false [dir.c_path] in
  218. List.iter (fun dir ->
  219. if not (cs#has_directory sign dir) then begin
  220. let time = stat dir in
  221. ServerMessage.added_directory com "" dir;
  222. cs#add_directory sign (CompilationServer.create_directory dir time)
  223. end;
  224. ) sub_dirs;
  225. (CompilationServer.create_directory dir.c_path time') :: acc
  226. end else
  227. acc
  228. with Unix.Unix_error _ ->
  229. cs#remove_directory sign dir.c_path;
  230. ServerMessage.removed_directory com "" dir.c_path;
  231. acc
  232. ) [] all_dirs in
  233. ServerMessage.changed_directories com "" dirs;
  234. dirs
  235. with Not_found ->
  236. (* There were no directories in the cache, so this must be a new context. Let's add
  237. an empty list to make sure no crazy recursion happens. *)
  238. cs#add_directories sign [];
  239. (* Register the delay that is going to populate the cache dirs. *)
  240. sctx.delays <- (fun () ->
  241. let dirs = ref [] in
  242. let add_dir path =
  243. try
  244. let time = stat path in
  245. dirs := CompilationServer.create_directory path time :: !dirs
  246. with Unix.Unix_error _ ->
  247. ()
  248. in
  249. List.iter add_dir com.class_path;
  250. List.iter add_dir (Path.find_directories (platform_name com.platform) true com.class_path);
  251. ServerMessage.found_directories com "" !dirs;
  252. cs#add_directories sign !dirs
  253. ) :: sctx.delays;
  254. (* Returning [] should be fine here because it's a new context, so we won't do any
  255. shadowing checks anyway. *)
  256. []
  257. in
  258. Hashtbl.add sctx.changed_directories sign dirs;
  259. dirs
  260. in
  261. t();
  262. dirs
  263. (* Checks if module [m] can be reused from the cache and returns None in that case. Otherwise, returns
  264. [Some m'] where [m'] is the module responsible for [m] not being reusable. *)
  265. let check_module sctx ctx m p =
  266. let com = ctx.Typecore.com in
  267. let cc = CommonCache.get_cache sctx.cs com in
  268. let content_changed m file =
  269. let ffile = Path.unique_full_path file in
  270. try
  271. let cfile = cc#find_file ffile in
  272. (* We must use the module path here because the file path is absolute and would cause
  273. positions in the parsed declarations to differ. *)
  274. let new_data = TypeloadParse.parse_module ctx m.m_path p in
  275. cfile.c_decls <> snd new_data
  276. with Not_found ->
  277. true
  278. in
  279. let check_module_shadowing paths m =
  280. List.iter (fun dir ->
  281. let file = (dir.c_path ^ (snd m.m_path)) ^ ".hx" in
  282. if Sys.file_exists file then begin
  283. let time = file_time file in
  284. if time > m.m_extra.m_time then begin
  285. ServerMessage.module_path_changed com "" (m,time,file);
  286. raise Not_found
  287. end
  288. end
  289. ) paths
  290. in
  291. let mark = sctx.mark_loop in
  292. let start_mark = sctx.compilation_mark in
  293. let rec check m =
  294. let check_module_path () =
  295. let directories = get_changed_directories sctx ctx in
  296. match m.m_extra.m_kind with
  297. | MFake | MImport -> () (* don't get classpath *)
  298. | MExtern ->
  299. (* if we have a file then this will override our extern type *)
  300. let has_file = (try check_module_shadowing directories m; false with Not_found -> true) in
  301. if has_file then begin
  302. if sctx.verbose then print_endline ("A file is masking the library file " ^ s_type_path m.m_path); (* TODO *)
  303. raise Not_found;
  304. end;
  305. let rec loop = function
  306. | [] ->
  307. if sctx.verbose then print_endline ("No library file was found for " ^ s_type_path m.m_path); (* TODO *)
  308. raise Not_found (* no extern registration *)
  309. | (file,load) :: l ->
  310. match load m.m_path p with
  311. | None -> loop l
  312. | Some _ ->
  313. if Path.unique_full_path file <> m.m_extra.m_file then begin
  314. if sctx.verbose then print_endline ("Library file was changed for " ^ s_type_path m.m_path); (* TODO *)
  315. raise Not_found;
  316. end
  317. in
  318. loop com.load_extern_type
  319. | MCode -> check_module_shadowing directories m
  320. | MMacro when ctx.Typecore.in_macro -> check_module_shadowing directories m
  321. | MMacro ->
  322. (*
  323. Creating another context while the previous one is incomplete means we have an infinite loop in the compiler.
  324. Most likely because of circular dependencies in base modules (e.g. `StdTypes` or `String`)
  325. Prevents spending another 5 hours for debugging.
  326. @see https://github.com/HaxeFoundation/haxe/issues/8174
  327. *)
  328. if not ctx.g.complete && ctx.in_macro then
  329. raise (ServerError ("Infinite loop in Haxe server detected. "
  330. ^ "Probably caused by shadowing a module of the standard library. "
  331. ^ "Make sure shadowed module does not pull macro context."));
  332. let _, mctx = MacroContext.get_macro_context ctx p in
  333. check_module_shadowing (get_changed_directories sctx mctx) m
  334. in
  335. let has_policy policy = List.mem policy m.m_extra.m_check_policy || match policy with
  336. | NoCheckShadowing | NoCheckFileTimeModification when !ServerConfig.do_not_check_modules && !Parser.display_mode <> DMNone -> true
  337. | _ -> false
  338. in
  339. let check_file () =
  340. if file_time m.m_extra.m_file <> m.m_extra.m_time then begin
  341. if has_policy CheckFileContentModification && not (content_changed m m.m_extra.m_file) then begin
  342. ServerMessage.unchanged_content com "" m.m_extra.m_file;
  343. end else begin
  344. ServerMessage.not_cached com "" m;
  345. if m.m_extra.m_kind = MFake then Hashtbl.remove Typecore.fake_modules m.m_extra.m_file;
  346. raise Not_found;
  347. end
  348. end
  349. in
  350. let check_dependencies () =
  351. PMap.iter (fun _ m2 -> match check m2 with
  352. | None -> ()
  353. | Some path -> raise (Dirty path)
  354. ) m.m_extra.m_deps;
  355. in
  356. begin match m.m_extra.m_dirty with
  357. | Some path ->
  358. Some path
  359. | None ->
  360. if m.m_extra.m_mark = mark then
  361. None
  362. else try
  363. let old_mark = m.m_extra.m_mark in
  364. m.m_extra.m_mark <- mark;
  365. if old_mark <= start_mark then begin
  366. if not (has_policy NoCheckShadowing) then check_module_path();
  367. if not (has_policy NoCheckFileTimeModification) || file_extension m.m_extra.m_file <> "hx" then check_file();
  368. end;
  369. if not (has_policy NoCheckDependencies) then check_dependencies();
  370. None
  371. with
  372. | Not_found ->
  373. m.m_extra.m_dirty <- Some m.m_path;
  374. Some m.m_path
  375. | Dirty path ->
  376. m.m_extra.m_dirty <- Some path;
  377. Some path
  378. end
  379. in
  380. check m
  381. (* Adds module [m] and all its dependencies (recursively) from the cache to the current compilation
  382. context. *)
  383. let add_modules sctx ctx m p =
  384. let com = ctx.Typecore.com in
  385. let rec add_modules tabs m0 m =
  386. if m.m_extra.m_added < sctx.compilation_step then begin
  387. (match m0.m_extra.m_kind, m.m_extra.m_kind with
  388. | MCode, MMacro | MMacro, MCode ->
  389. (* this was just a dependency to check : do not add to the context *)
  390. PMap.iter (Hashtbl.replace com.resources) m.m_extra.m_binded_res;
  391. | _ ->
  392. ServerMessage.reusing com tabs m;
  393. m.m_extra.m_added <- sctx.compilation_step;
  394. List.iter (fun t ->
  395. match t with
  396. | TClassDecl c -> c.cl_restore()
  397. | TEnumDecl e ->
  398. let rec loop acc = function
  399. | [] -> ()
  400. | (Meta.RealPath,[Ast.EConst (Ast.String(path,_)),_],_) :: l ->
  401. e.e_path <- Ast.parse_path path;
  402. e.e_meta <- (List.rev acc) @ l;
  403. | x :: l -> loop (x::acc) l
  404. in
  405. loop [] e.e_meta
  406. | TAbstractDecl a ->
  407. a.a_meta <- List.filter (fun (m,_,_) -> m <> Meta.ValueUsed) a.a_meta
  408. | _ -> ()
  409. ) m.m_types;
  410. TypeloadModule.add_module ctx m p;
  411. PMap.iter (Hashtbl.replace com.resources) m.m_extra.m_binded_res;
  412. PMap.iter (fun _ m2 -> add_modules (tabs ^ " ") m0 m2) m.m_extra.m_deps
  413. )
  414. end
  415. in
  416. add_modules "" m m
  417. (* Looks up the module referred to by [mpath] in the cache. If it exists, a check is made to
  418. determine if it's still valid. If this function returns None, the module is re-typed. *)
  419. let type_module sctx (ctx:Typecore.typer) mpath p =
  420. let t = Timer.timer ["server";"module cache"] in
  421. let com = ctx.Typecore.com in
  422. let cc = CommonCache.get_cache sctx.cs com in
  423. sctx.mark_loop <- sctx.mark_loop + 1;
  424. try
  425. let m = cc#find_module mpath in
  426. let tcheck = Timer.timer ["server";"module cache";"check"] in
  427. begin match check_module sctx ctx m p with
  428. | None -> ()
  429. | Some path ->
  430. ServerMessage.skipping_dep com "" (m,path);
  431. tcheck();
  432. raise Not_found;
  433. end;
  434. tcheck();
  435. let tadd = Timer.timer ["server";"module cache";"add modules"] in
  436. add_modules sctx ctx m p;
  437. tadd();
  438. t();
  439. Some m
  440. with Not_found ->
  441. t();
  442. None
  443. (* Sets up the per-compilation context. *)
  444. let create sctx write params =
  445. let cs = sctx.cs in
  446. let maybe_cache_context com =
  447. if com.display.dms_full_typing then begin
  448. CommonCache.cache_context sctx.cs com;
  449. ServerMessage.cached_modules com "" (List.length com.modules);
  450. end
  451. in
  452. let ctx = create_context params in
  453. ctx.flush <- (fun() ->
  454. sctx.compilation_step <- sctx.compilation_step + 1;
  455. sctx.compilation_mark <- sctx.mark_loop;
  456. check_display_flush ctx (fun () ->
  457. List.iter
  458. (fun msg ->
  459. let s = compiler_message_string msg in
  460. write (s ^ "\n");
  461. ServerMessage.message s;
  462. )
  463. (List.rev ctx.messages);
  464. sctx.was_compilation <- ctx.com.display.dms_full_typing;
  465. if ctx.has_error then begin
  466. measure_times := false;
  467. write "\x02\n"
  468. end else maybe_cache_context ctx.com;
  469. )
  470. );
  471. ctx.setup <- (fun() ->
  472. let sign = Define.get_signature ctx.com.defines in
  473. ServerMessage.defines ctx.com "";
  474. ServerMessage.signature ctx.com "" sign;
  475. ServerMessage.display_position ctx.com "" (DisplayPosition.display_position#get);
  476. try
  477. if (Hashtbl.find sctx.class_paths sign) <> ctx.com.class_path then begin
  478. ServerMessage.class_paths_changed ctx.com "";
  479. Hashtbl.replace sctx.class_paths sign ctx.com.class_path;
  480. cs#clear_directories sign;
  481. (cs#get_context sign)#set_initialized false;
  482. end;
  483. with Not_found ->
  484. Hashtbl.add sctx.class_paths sign ctx.com.class_path;
  485. ()
  486. );
  487. ctx.com.print <- (fun str -> write ("\x01" ^ String.concat "\x01" (ExtString.String.nsplit str "\n") ^ "\n"));
  488. ctx
  489. (* Resets the state for a new compilation *)
  490. let init_new_compilation sctx =
  491. ServerCompilationContext.reset sctx;
  492. Parser.reset_state();
  493. return_partial_type := false;
  494. measure_times := false;
  495. Hashtbl.clear DeprecationCheck.warned_positions;
  496. close_times();
  497. stats.s_files_parsed := 0;
  498. stats.s_classes_built := 0;
  499. stats.s_methods_typed := 0;
  500. stats.s_macros_called := 0;
  501. Hashtbl.clear Timer.htimers;
  502. sctx.compilation_step <- sctx.compilation_step + 1;
  503. sctx.compilation_mark <- sctx.mark_loop;
  504. start_time := get_time()
  505. let cleanup () =
  506. begin match !MacroContext.macro_interp_cache with
  507. | Some interp -> EvalContext.GlobalState.cleanup interp
  508. | None -> ()
  509. end
  510. let gc_heap_stats () =
  511. let stats = Gc.quick_stat() in
  512. stats.major_words,stats.heap_words
  513. let fmt_percent f =
  514. int_of_float (f *. 100.)
  515. module Tasks = struct
  516. class gc_task (max_working_memory : float) (heap_size : float) = object(self)
  517. inherit server_task ["gc"] 100
  518. method private execute =
  519. let t0 = get_time() in
  520. let stats = Gc.stat() in
  521. let live_words = float_of_int stats.live_words in
  522. (* Maximum heap size needed for the last X compilations = sum of what's live + max working memory. *)
  523. let needed_max = live_words +. max_working_memory in
  524. (* Additional heap percentage needed = what's live / max of what was live. *)
  525. let percent_needed = (1. -. live_words /. needed_max) in
  526. (* Effective cache size percentage = what's live / heap size. *)
  527. let percent_used = live_words /. heap_size in
  528. (* Set allowed space_overhead to the maximum of what we needed during the last X compilations. *)
  529. let new_space_overhead = int_of_float ((percent_needed +. 0.05) *. 100.) in
  530. let old_gc = Gc.get() in
  531. Gc.set { old_gc with Gc.space_overhead = new_space_overhead; };
  532. (* Compact if less than 80% of our heap words consist of the cache and there's less than 50% overhead. *)
  533. let do_compact = percent_used < 0.8 && percent_needed < 0.5 in
  534. begin if do_compact then
  535. Gc.compact()
  536. else
  537. Gc.full_major();
  538. end;
  539. Gc.set old_gc;
  540. ServerMessage.gc_stats (get_time() -. t0) stats do_compact new_space_overhead
  541. end
  542. class class_maintenance_task (cs : CompilationServer.t) (c : tclass) = object(self)
  543. inherit server_task ["module maintenance"] 70
  544. method private execute =
  545. let rec field cf =
  546. (* Unset cf_expr. This holds the optimized version for generators, which we don't need to persist. If
  547. we compile again, the semi-optimized expression will be restored by calling cl_restore(). *)
  548. cf.cf_expr <- None;
  549. List.iter field cf.cf_overloads
  550. in
  551. (* What we're doing here at the moment is free, so we can just do it in one task. If this ever gets more expensive,
  552. we should spawn a task per-field. *)
  553. List.iter field c.cl_ordered_fields;
  554. List.iter field c.cl_ordered_statics;
  555. Option.may field c.cl_constructor;
  556. end
  557. class module_maintenance_task (cs : CompilationServer.t) (m : module_def) = object(self)
  558. inherit server_task ["module maintenance"] 80
  559. method private execute =
  560. List.iter (fun mt -> match mt with
  561. | TClassDecl c ->
  562. cs#add_task (new class_maintenance_task cs c)
  563. | _ ->
  564. ()
  565. ) m.m_types
  566. end
  567. class server_exploration_task (cs : CompilationServer.t) = object(self)
  568. inherit server_task ["server explore"] 90
  569. method private execute =
  570. cs#iter_modules (fun m -> cs#add_task (new module_maintenance_task cs m))
  571. end
  572. end
  573. (* The server main loop. Waits for the [accept] call to then process the sent compilation
  574. parameters through [process_params]. *)
  575. let wait_loop process_params verbose accept =
  576. if verbose then ServerMessage.enable_all ();
  577. Sys.catch_break false; (* Sys can never catch a break *)
  578. (* Create server context and set up hooks for parsing and typing *)
  579. let cs = CompilationServer.create () in
  580. let sctx = ServerCompilationContext.create verbose cs in
  581. TypeloadModule.type_module_hook := type_module sctx;
  582. MacroContext.macro_enable_cache := true;
  583. TypeloadParse.parse_hook := parse_file cs;
  584. let ring = Ring.create 10 0. in
  585. let heap_stats_start = ref (gc_heap_stats()) in
  586. let update_heap () =
  587. (* On every compilation: Track how many words were allocated for this compilation (working memory). *)
  588. let heap_stats_now = gc_heap_stats() in
  589. let words_allocated = (fst heap_stats_now) -. (fst !heap_stats_start) in
  590. let heap_size = float_of_int (snd heap_stats_now) in
  591. Ring.push ring words_allocated;
  592. if Ring.is_filled ring then begin
  593. Ring.reset_filled ring;
  594. (* Maximum working memory for the last X compilations. *)
  595. let max = Ring.fold ring 0. (fun m i -> if i > m then i else m) in
  596. cs#add_task (new Tasks.gc_task max heap_size)
  597. end;
  598. heap_stats_start := heap_stats_now;
  599. in
  600. (* Main loop: accept connections and process arguments *)
  601. while true do
  602. let support_nonblock, read, write, close = accept() in
  603. let process s =
  604. let t0 = get_time() in
  605. let hxml =
  606. try
  607. let idx = String.index s '\001' in
  608. current_stdin := Some (String.sub s (idx + 1) ((String.length s) - idx - 1));
  609. (String.sub s 0 idx)
  610. with Not_found ->
  611. s
  612. in
  613. let data = parse_hxml_data hxml in
  614. ServerMessage.arguments data;
  615. init_new_compilation sctx;
  616. begin try
  617. let create = create sctx write in
  618. (* Pass arguments to normal handling in main.ml *)
  619. process_params create data;
  620. close_times();
  621. if !measure_times then report_times (fun s -> write (s ^ "\n"))
  622. with
  623. | Completion str ->
  624. ServerMessage.completion str;
  625. write str
  626. | Arg.Bad msg ->
  627. print_endline ("Error: " ^ msg);
  628. end;
  629. run_delays sctx;
  630. ServerMessage.stats stats (get_time() -. t0)
  631. in
  632. begin try
  633. (* Read arguments *)
  634. let rec loop block =
  635. match read block with
  636. | Some data ->
  637. process data
  638. | None ->
  639. if not cs#has_task then
  640. (* If there is no pending task, turn into blocking mode. *)
  641. loop true
  642. else begin
  643. (* Otherwise run the task and loop to check if there are more or if there's a request now. *)
  644. cs#get_task#run;
  645. loop false
  646. end;
  647. in
  648. loop (not support_nonblock)
  649. with Unix.Unix_error _ ->
  650. ServerMessage.socket_message "Connection Aborted"
  651. | e ->
  652. let estr = Printexc.to_string e in
  653. ServerMessage.uncaught_error estr;
  654. (try write ("\x02\n" ^ estr); with _ -> ());
  655. if is_debug_run then print_endline (estr ^ "\n" ^ Printexc.get_backtrace());
  656. if e = Out_of_memory then begin
  657. close();
  658. exit (-1);
  659. end;
  660. end;
  661. (* Close connection and perform some cleanup *)
  662. close();
  663. current_stdin := None;
  664. cleanup();
  665. update_heap();
  666. (* If our connection always blocks, we have to execute all pending tasks now. *)
  667. if not support_nonblock then
  668. while cs#has_task do cs#get_task#run done
  669. else if sctx.was_compilation then
  670. cs#add_task (new Tasks.server_exploration_task cs)
  671. done
  672. let mk_length_prefixed_communication allow_nonblock chin chout =
  673. let sin = Unix.descr_of_in_channel chin in
  674. let chin = IO.input_channel chin in
  675. let chout = IO.output_channel chout in
  676. let bout = Buffer.create 0 in
  677. let block () = Unix.clear_nonblock sin in
  678. let unblock () = Unix.set_nonblock sin in
  679. let read_nonblock _ =
  680. let len = IO.read_i32 chin in
  681. Some (IO.really_nread_string chin len)
  682. in
  683. let read = if allow_nonblock then fun do_block ->
  684. if do_block then begin
  685. block();
  686. read_nonblock true;
  687. end else begin
  688. let c0 =
  689. unblock();
  690. try
  691. Some (IO.read_byte chin)
  692. with
  693. | Sys_blocked_io
  694. (* TODO: We're supposed to catch Sys_blocked_io only, but that doesn't work on my PC... *)
  695. | Sys_error _ ->
  696. None
  697. in
  698. begin match c0 with
  699. | Some c0 ->
  700. block(); (* We got something, make sure we block until we're done. *)
  701. let c1 = IO.read_byte chin in
  702. let c2 = IO.read_byte chin in
  703. let c3 = IO.read_byte chin in
  704. let len = c3 lsl 24 + c2 lsl 16 + c1 lsl 8 + c0 in
  705. Some (IO.really_nread_string chin len)
  706. | None ->
  707. None
  708. end
  709. end
  710. else read_nonblock in
  711. let write = Buffer.add_string bout in
  712. let close = fun() ->
  713. IO.write_i32 chout (Buffer.length bout);
  714. IO.nwrite_string chout (Buffer.contents bout);
  715. IO.flush chout
  716. in
  717. fun () ->
  718. Buffer.clear bout;
  719. allow_nonblock, read, write, close
  720. (* The accept-function to wait for a stdio connection. *)
  721. let init_wait_stdio() =
  722. set_binary_mode_in stdin true;
  723. set_binary_mode_out stderr true;
  724. mk_length_prefixed_communication false stdin stderr
  725. (* Connect to given host/port and return accept function for communication *)
  726. let init_wait_connect host port =
  727. let host = Unix.inet_addr_of_string host in
  728. let chin, chout = Unix.open_connection (Unix.ADDR_INET (host,port)) in
  729. mk_length_prefixed_communication true chin chout
  730. (* The accept-function to wait for a socket connection. *)
  731. let init_wait_socket host port =
  732. let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  733. (try Unix.setsockopt sock Unix.SO_REUSEADDR true with _ -> ());
  734. (try Unix.bind sock (Unix.ADDR_INET (Unix.inet_addr_of_string host,port)) with _ -> failwith ("Couldn't wait on " ^ host ^ ":" ^ string_of_int port));
  735. ServerMessage.socket_message ("Waiting on " ^ host ^ ":" ^ string_of_int port);
  736. Unix.listen sock 10;
  737. let bufsize = 1024 in
  738. let tmp = Bytes.create bufsize in
  739. let accept() = (
  740. let sin, _ = Unix.accept sock in
  741. Unix.set_nonblock sin;
  742. ServerMessage.socket_message "Client connected";
  743. let b = Buffer.create 0 in
  744. let rec read_loop count =
  745. try
  746. let r = Unix.recv sin tmp 0 bufsize [] in
  747. if r = 0 then
  748. failwith "Incomplete request"
  749. else begin
  750. ServerMessage.socket_message (Printf.sprintf "Reading %d bytes\n" r);
  751. Buffer.add_subbytes b tmp 0 r;
  752. if Bytes.get tmp (r-1) = '\000' then
  753. Buffer.sub b 0 (Buffer.length b - 1)
  754. else
  755. read_loop 0
  756. end
  757. with Unix.Unix_error((Unix.EWOULDBLOCK|Unix.EAGAIN),_,_) ->
  758. if count = 100 then
  759. failwith "Aborting inactive connection"
  760. else begin
  761. ServerMessage.socket_message "Waiting for data...";
  762. ignore(Unix.select [] [] [] 0.05); (* wait a bit *)
  763. read_loop (count + 1);
  764. end
  765. in
  766. let read = fun _ -> (let s = read_loop 0 in Unix.clear_nonblock sin; Some s) in
  767. let write s = ssend sin (Bytes.unsafe_of_string s) in
  768. let close() = Unix.close sin in
  769. false, read, write, close
  770. ) in
  771. accept
  772. (* The connect function to connect to [host] at [port] and send arguments [args]. *)
  773. let do_connect host port args =
  774. let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  775. (try Unix.connect sock (Unix.ADDR_INET (Unix.inet_addr_of_string host,port)) with _ -> failwith ("Couldn't connect on " ^ host ^ ":" ^ string_of_int port));
  776. let args = ("--cwd " ^ Unix.getcwd()) :: args in
  777. ssend sock (Bytes.of_string (String.concat "" (List.map (fun a -> a ^ "\n") args) ^ "\000"));
  778. let has_error = ref false in
  779. let rec print line =
  780. match (if line = "" then '\x00' else line.[0]) with
  781. | '\x01' ->
  782. print_string (String.concat "\n" (List.tl (ExtString.String.nsplit line "\x01")));
  783. flush stdout
  784. | '\x02' ->
  785. has_error := true;
  786. | _ ->
  787. prerr_endline line;
  788. in
  789. let buf = Buffer.create 0 in
  790. let process() =
  791. let lines = ExtString.String.nsplit (Buffer.contents buf) "\n" in
  792. (* the last line ends with \n *)
  793. let lines = (match List.rev lines with "" :: l -> List.rev l | _ -> lines) in
  794. List.iter print lines;
  795. in
  796. let tmp = Bytes.create 1024 in
  797. let rec loop() =
  798. let b = Unix.recv sock tmp 0 1024 [] in
  799. Buffer.add_subbytes buf tmp 0 b;
  800. if b > 0 then begin
  801. if Bytes.get tmp (b - 1) = '\n' then begin
  802. process();
  803. Buffer.reset buf;
  804. end;
  805. loop();
  806. end
  807. in
  808. loop();
  809. process();
  810. if !has_error then exit 1