PageRenderTime 28ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/hack/src/dfind/dfindServer.ml

https://github.com/tstarling/hiphop-php
OCaml | 407 lines | 281 code | 37 blank | 89 comment | 14 complexity | 43f48f648b4f6f99d8f79ca7acb4f7be MD5 | raw file
  1. (**
  2. * Copyright (c) 2014, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the "hack" directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. *)
  10. (*****************************************************************************)
  11. (* Code relative to the client/server communication *)
  12. (*****************************************************************************)
  13. open DfindEnv
  14. type message =
  15. | Find_handle_follow of DfindEnv.dir * DfindEnv.handle
  16. | Find_handle of DfindEnv.dir * DfindEnv.handle
  17. | Check
  18. | Kill
  19. | Ping
  20. (*****************************************************************************)
  21. (* Some helpers *)
  22. (*****************************************************************************)
  23. let is_prefix dir file =
  24. String.length file > String.length dir &&
  25. String.sub file 0 (String.length dir) = dir &&
  26. file.[String.length dir] = '/'
  27. (*****************************************************************************)
  28. (* Processing an inotify event *)
  29. (*****************************************************************************)
  30. (* Can be useful to see what the event actually is, for debugging *)
  31. let string_of ev =
  32. let wd,mask,cookie,s = ev in
  33. let mask = String.concat ":" (List.map Inotify.string_of_event mask) in
  34. let s = match s with Some s -> s | None -> "\"\"" in
  35. Printf.sprintf "wd [%u] mask[%s] cookie[%ld] %s" (Inotify.int_of_wd wd)
  36. mask cookie s
  37. (* Die if something unexpected happened *)
  38. let check_event env fname = function
  39. | Inotify.Access
  40. | Inotify.Attrib
  41. | Inotify.Close_write
  42. | Inotify.Close_nowrite
  43. | Inotify.Create
  44. | Inotify.Delete
  45. | Inotify.Delete_self
  46. | Inotify.Move_self
  47. | Inotify.Moved_from
  48. | Inotify.Moved_to
  49. | Inotify.Open
  50. | Inotify.Ignored
  51. | Inotify.Modify
  52. | Inotify.Isdir -> ()
  53. | Inotify.Q_overflow ->
  54. Printf.fprintf env.log "INOTIFY OVERFLOW!!!\n";
  55. flush env.log;
  56. exit 5
  57. | Inotify.Unmount ->
  58. Printf.fprintf env.log "UNMOUNT EVENT!!!\n";
  59. flush env.log;
  60. exit 5
  61. let (process_inotify_event:
  62. DfindEnv.t -> SSet.t -> Inotify.event
  63. -> SSet.t) = fun env dirty event ->
  64. let wd, mask, _cookie, fname = event in
  65. List.iter (check_event env fname) mask;
  66. match fname
  67. with None -> dirty
  68. | Some fname ->
  69. let wname = try WMap.find wd env.DfindEnv.wnames with _ -> assert false in
  70. (* Let's rebuild the full name of the file *)
  71. let path = wname ^ "/" ^ fname in
  72. (* Tell everybody that this file has changed *)
  73. let dirty = SSet.add path dirty in
  74. (* Is it a directory? Be conservative, everything we know about this
  75. * directory is now "dirty"
  76. *)
  77. let dirty =
  78. if SMap.mem path env.dirs
  79. then SSet.union dirty (SMap.find path env.dirs)
  80. else begin
  81. let dir_content =
  82. try SMap.find wname env.dirs
  83. with Not_found -> SSet.empty
  84. in
  85. env.dirs <- SMap.add wname (SSet.add path dir_content) env.dirs;
  86. dirty
  87. end
  88. in
  89. env.new_files <- SSet.empty;
  90. (* Add the file, plus all of the sub elements if it is a directory *)
  91. DfindAddFile.path env path;
  92. (* Add everything new we found in this directory
  93. * (empty when it's a regular file)
  94. *)
  95. let dirty = SSet.union env.new_files dirty in
  96. (* Printf.fprintf env.log "Event: %s\n" (string_of event); flush env.log; *)
  97. dirty
  98. (*****************************************************************************)
  99. (* Processing a user handle
  100. * dfind my_dir/ my_handle
  101. *)
  102. (*****************************************************************************)
  103. let set_handle_time env handle =
  104. (* Replace the current time of the handle to now. *)
  105. let new_time = Time.get() in
  106. set_handle env handle new_time
  107. let get_all_files env (dir, h as handle) =
  108. Printf.fprintf env.log "Dumping handle: %s[%s]\n" dir h; flush env.log;
  109. (* Find the time when the handle was created *)
  110. let time =
  111. match get_handle env handle with
  112. | None -> Time.bot
  113. | Some x -> x
  114. in
  115. let acc = ref SSet.empty in
  116. (* Now walk the tree, but cut the branches that are too old *)
  117. TimeFiles.walk
  118. (fun x -> x < time) (* Cut everything that is too old *)
  119. (fun (file_time, file) ->
  120. if file_time >= time then begin
  121. acc := SSet.add file !acc;
  122. end
  123. )
  124. env.files;
  125. set_handle_time env handle;
  126. let acc = SSet.filter (is_prefix dir) !acc in
  127. acc
  128. let print_handle ~close env handle oc =
  129. let acc = get_all_files env handle in
  130. add_output env ~close oc acc;
  131. ()
  132. (*****************************************************************************)
  133. (* Section defining the functions called by the server
  134. * Whenever an inotify event is received, process_event is called
  135. * Whenever a new message is received, process_message is called
  136. *)
  137. (*****************************************************************************)
  138. (* Send to client sends a message to a specific client
  139. * the client is a triplet (directory_of_interest, handle, output_channel).
  140. *)
  141. let (send_to_client: DfindEnv.t -> SSet.t -> DfindEnv.client -> unit) =
  142. fun env dirty (dir, handle, oc) ->
  143. add_output env ~close:false oc dirty
  144. let (process_inotify_events: DfindEnv.t -> Inotify.event list -> unit) =
  145. fun env evs ->
  146. (* What's new? *)
  147. let dirty = List.fold_left (process_inotify_event env) SSet.empty evs in
  148. let time = Time.get() in
  149. (* Insert the files with the current timestamp *)
  150. SSet.iter begin fun file ->
  151. (*
  152. Printf.fprintf env.log "Adding %s[%s]\n" file (Time.to_string time);
  153. flush env.log;
  154. *)
  155. env.files <- TimeFiles.add (time, file) env.files
  156. end dirty;
  157. let clients = get_clients env in
  158. (* Reset the list of clients, they will re-insert themselves *)
  159. (* Notify every listener that something changed *)
  160. List.iter (send_to_client env dirty) clients
  161. let process_handle ~close env dir handle oc =
  162. if not (SMap.mem dir env.dirs)
  163. then DfindAddFile.path env dir;
  164. let dir_handle = dir, handle in
  165. print_handle ~close env dir_handle oc
  166. let process_client_msg env oc = function
  167. | Find_handle_follow (dir, handle) ->
  168. process_handle ~close:false env dir handle oc;
  169. add_client env (dir, handle, oc)
  170. | Find_handle (dir, handle) ->
  171. process_handle ~close:true env dir handle oc
  172. | Check -> () (* TODO check *)
  173. | Kill ->
  174. exit 0
  175. | Ping ->
  176. let oc = Unix.out_channel_of_descr oc in
  177. output_string oc "OK\n";
  178. close_out oc
  179. (*****************************************************************************)
  180. (* Generic code to create a socket in Ocaml *)
  181. (*****************************************************************************)
  182. let server_socket, client_socket =
  183. let tmp = Filename.temp_dir_name in
  184. let user = Sys.getenv "USER" in
  185. let sock_name = tmp ^ "/dfind_"^user^".sock" in
  186. begin fun () -> (* Server side *)
  187. try
  188. if Sys.file_exists sock_name then Sys.remove sock_name;
  189. let sockaddr = Unix.ADDR_UNIX sock_name in
  190. let domain = Unix.PF_UNIX in
  191. let sock = Unix.socket domain Unix.SOCK_STREAM 0 in
  192. let _ = Unix.bind sock sockaddr in
  193. let _ = Unix.listen sock 10 in
  194. sock
  195. with Unix.Unix_error (err, _, _) ->
  196. exit 1
  197. end,
  198. begin fun () -> (* Client side *)
  199. try
  200. let sockaddr = Unix.ADDR_UNIX sock_name in
  201. let domain = Unix.PF_UNIX in
  202. let sock = Unix.socket domain Unix.SOCK_STREAM 0 in
  203. Unix.connect sock sockaddr ;
  204. let ic = Unix.in_channel_of_descr sock in
  205. let oc = Unix.out_channel_of_descr sock in
  206. ic, oc
  207. with _ ->
  208. Printf.fprintf stderr "Error: could not connect to the server\n";
  209. exit 3
  210. end
  211. (*****************************************************************************)
  212. (* We use a pid file for 2 purposes:
  213. * - Know what the pid of the server is quickly
  214. * - Be able to tell quickly if the server is running
  215. *
  216. * The server, when it starts, creates and then locks the pid file (without
  217. * unlocking).
  218. * So we know a server is already runnning by testing the lock on the pid
  219. * file
  220. *)
  221. (*****************************************************************************)
  222. let get_pid_file () =
  223. let tmp = Filename.temp_dir_name in
  224. let user = Sys.getenv "USER" in
  225. let fn = tmp ^ "/dfind_"^user^".pid" in
  226. fn
  227. let is_running () =
  228. let fn = get_pid_file() in
  229. Sys.file_exists fn &&
  230. try
  231. let fd = Unix.openfile fn [Unix.O_RDONLY] 0o640 in
  232. Unix.lockf fd Unix.F_TEST 1;
  233. false
  234. with _ ->
  235. true
  236. (* create and the lock the pid file (lock forever) *)
  237. let lock_pid_file () =
  238. let fn = get_pid_file() in
  239. let fd = Unix.openfile fn [Unix.O_RDWR; Unix.O_CREAT] 0o640 in
  240. Unix.lockf fd Unix.F_LOCK 1;
  241. let oc = Unix.out_channel_of_descr fd in
  242. output_string oc (string_of_int (Unix.getpid()));
  243. output_string oc "\n";
  244. flush oc
  245. (*****************************************************************************)
  246. (* Functions used to notify that the server is ready *)
  247. (*****************************************************************************)
  248. (* Function used by the client to make sure the server is ready to listen *)
  249. let (wait_for_server: Unix.file_descr -> unit) = fun ready_in ->
  250. assert (Unix.read ready_in " " 0 1 = 1);
  251. Unix.close ready_in
  252. (* Used by the server to wake up the client waiting *)
  253. let (notify_client: Unix.file_descr -> unit) = fun ready_out ->
  254. assert (Unix.write ready_out "X" 0 1 = 1);
  255. Unix.close ready_out
  256. (*****************************************************************************)
  257. (* Fork and work *)
  258. (*****************************************************************************)
  259. let half_hour = 1800.0
  260. let one_day = 86400.0
  261. let exit_if_unused env =
  262. let time_since_start: float = Unix.time() -. env.start_time in
  263. let time_since_query: float = Unix.time() -. env.last_query in
  264. (* quit the server after a day at an opportune time *)
  265. if time_since_start > one_day && time_since_query >= half_hour
  266. then begin
  267. Printf.fprintf env.log "Exiting server after 24 hours\n";
  268. flush env.log;
  269. exit 5;
  270. end
  271. let daemon env socket =
  272. Printf.fprintf env.log "Status: Starting daemon\n"; flush env.log;
  273. while true do
  274. let fdl = [ env.inotify; socket ] in
  275. let output_descrl = DfindEnv.get_output_descrl env in
  276. exit_if_unused env;
  277. let readyl, out_readyl, _ = Unix.select fdl output_descrl [] half_hour in
  278. if out_readyl <> []
  279. then output env;
  280. if List.exists (fun x -> x = env.inotify) readyl then begin
  281. let evs = Unix.handle_unix_error Inotify.read env.inotify in
  282. process_inotify_events env evs
  283. end;
  284. if List.exists (fun x -> x = socket) readyl then begin
  285. try
  286. Printf.fprintf env.log "STATUS: message received\n"; flush env.log;
  287. env.last_query <- Unix.time();
  288. let cli, _ = Unix.accept socket in
  289. let ic = Unix.in_channel_of_descr cli in
  290. let msg = Marshal.from_channel ic in
  291. process_client_msg env cli msg;
  292. Printf.fprintf env.log "STATUS: done\n"; flush env.log;
  293. with e ->
  294. Printf.fprintf env.log "Exception: %s\n" (Printexc.to_string e);
  295. flush env.log;
  296. ()
  297. end;
  298. done
  299. let daemon_from_pipe env message_in result_out =
  300. let env = { env with log = stdout; } in
  301. let acc = ref SSet.empty in
  302. while true do
  303. let fdl = [ message_in; env.inotify ] in
  304. let readyl, _, _ = Unix.select fdl [] [] (-1.0) in
  305. if List.exists (fun x -> x = env.inotify) readyl then begin
  306. let evs = Unix.handle_unix_error Inotify.read env.inotify in
  307. acc := List.fold_left (process_inotify_event env) !acc evs;
  308. end;
  309. if List.exists (fun x -> x = message_in) readyl then begin
  310. let ic = Unix.in_channel_of_descr message_in in
  311. flush env.log;
  312. let msg = Marshal.from_channel ic in
  313. assert (msg = "Go");
  314. let result_out = Unix.out_channel_of_descr result_out in
  315. Marshal.to_channel result_out !acc [];
  316. flush result_out;
  317. acc := SSet.empty;
  318. end;
  319. done
  320. let fork_in_pipe root =
  321. let msg_in, msg_out = Unix.pipe() in
  322. let result_in, result_out = Unix.pipe() in
  323. match Unix.fork() with
  324. | -1 -> failwith "Go get yourself a real computer"
  325. | 0 ->
  326. Unix.close msg_out;
  327. Unix.close result_in;
  328. let env = DfindEnv.make() in
  329. DfindAddFile.path env root;
  330. Printf.printf "Added %s\n" root; flush stdout;
  331. daemon_from_pipe env msg_in result_out;
  332. assert false
  333. | pid ->
  334. Unix.close msg_in;
  335. Unix.close result_out;
  336. msg_out, result_in, pid
  337. let fork () =
  338. let ready_in, ready_out = Unix.pipe() in
  339. match Unix.fork() with
  340. | -1 -> failwith "Go get yourself a real computer"
  341. | 0 ->
  342. (* The server must not die when a client dies,
  343. * if a client is killed via sig interrupt,
  344. * we will get the SIGPIPE signal server-side,
  345. * OCaml doesn't catch this signal (one of the very few).
  346. * All the other signals that we care about are turned
  347. * into exceptions ... So we are good.
  348. *)
  349. close_in stdin;
  350. close_out stdout;
  351. close_out stderr;
  352. Sys.set_signal Sys.sigpipe Sys.Signal_ignore;
  353. Sys.set_signal Sys.sigint Sys.Signal_ignore;
  354. Unix.close ready_in;
  355. let env = DfindEnv.make() in
  356. DfindMaybe.set_log env.log;
  357. let socket = server_socket () in
  358. lock_pid_file();
  359. (* This tells the client that originated the fork that
  360. * the server is ready.
  361. *)
  362. notify_client ready_out;
  363. (try
  364. daemon env socket;
  365. with e ->
  366. Printf.fprintf env.log "Daemon died: %s\n" (Printexc.to_string e));
  367. assert false (* daemon doesn't finish *)
  368. | pid ->
  369. Unix.close ready_out;
  370. ready_in