2 * Copyright (c) 2015, Facebook, Inc.
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.
11 type 'a in_channel
= Timeout.in_channel
12 type 'a out_channel
= Pervasives.out_channel
14 type ('in_
, 'out
) channel_pair
= 'in_ in_channel
* 'out out_channel
16 type ('in_
, 'out
) handle
= {
17 channels
: ('in_
, 'out
) channel_pair
;
21 (* Windows: ensure that the serialize/deserialize functions
22 for the custom block of "Unix.file_descr" are registred. *)
23 let () = Lazy.force
Handle.init
26 'a out_channel
-> ?flags
:Marshal.extern_flags list
-> ?flush
:bool ->
28 fun oc ?
(flags
= []) ?flush
:(should_flush
=true) v
->
29 Marshal.to_channel oc v flags
;
30 if should_flush
then flush oc
32 let from_channel : ?timeout
:Timeout.t
-> 'a in_channel
-> 'a
= fun ?timeout ic
->
33 Timeout.input_value ?timeout ic
35 let flush : 'a out_channel
-> unit = Pervasives.flush
37 let descr_of_in_channel : 'a in_channel
-> Unix.file_descr
=
38 Timeout.descr_of_in_channel
40 let descr_of_out_channel : 'a out_channel
-> Unix.file_descr
=
41 Unix.descr_of_out_channel
46 (* We cannot fork() on Windows, so in order to emulate this in a
47 * cross-platform way, we use create_process() and set the HH_SERVER_DAEMON
48 * environment variable to indicate which function the child should
49 * execute. On Unix, create_process() does fork + exec, so global state is
50 * not copied; in particular, if you have set a mutable reference the
51 * daemon will not see it. All state must be explicitly passed via
52 * environment variables; see set/get_context() below.
54 * With some factoring we could make the daemons into separate binaries
55 * altogether and dispense with this emulation. *)
59 (* All the 'untyped' operations---that are required for the
60 entry-points hashtable and the parameters stored in env
61 variable---are hidden in this sub-module, behind a 'type-safe'
64 type ('param
, 'input
, 'output
) t
65 val name_of_entry
: ('param
, 'input
, 'output
) t
-> string
67 string -> ('param
-> ('input
, 'output
) channel_pair
-> unit) ->
68 ('param
, 'input
, 'output
) t
70 ('param
, 'input
, 'output
) t
->
72 ('input
, 'output
) channel_pair
-> unit
74 ('param
, 'input
, 'output
) t
-> 'param
->
75 Unix.file_descr
* Unix.file_descr
->
79 (('param
, 'input
, 'output
) t
* 'param
* ('input
, 'output
) channel_pair
)
85 type ('param
, 'input
, 'output
) t
= string
87 let name_of_entry name
= name
89 (* Store functions as 'Obj.t' *)
90 let entry_points : (string, Obj.t
) Hashtbl.t
= Hashtbl.create
23
92 if Hashtbl.mem
entry_points name
then
93 Printf.ksprintf failwith
94 "Daemon.register_entry_point: duplicate entry point %S."
96 Hashtbl.add
entry_points name
(Obj.repr f
);
100 try Obj.obj
(Hashtbl.find entry_points name
)
102 Printf.ksprintf failwith
103 "Unknown entry point %S" name
105 let set_context entry param
(ic
, oc
) =
106 let data = (ic
, oc
, param
) in
107 Unix.putenv
"HH_SERVER_DAEMON" entry
;
109 Filename.open_temp_file
111 ~temp_dir
:Sys_utils.temp_dir_name
112 "daemon_param" ".bin" in
113 output_value oc
data;
115 Unix.putenv
"HH_SERVER_DAEMON_PARAM" file
117 (* How this works on Unix: It may appear like we are passing file descriptors
118 * from one process to another here, but in_handle / out_handle are actually
119 * file descriptors that are already open in the current process -- they were
120 * created by the parent process before it did fork + exec. However, since
121 * exec causes the child to "forget" everything, we have to pass the numbers
122 * of these file descriptors as arguments.
124 * I'm not entirely sure what this does on Windows. *)
126 let entry = Unix.getenv
"HH_SERVER_DAEMON" in
127 if entry = "" then raise Not_found
;
128 let (in_handle
, out_handle
, param
) =
130 let file = Sys.getenv
"HH_SERVER_DAEMON_PARAM" in
131 if file = "" then raise Not_found
;
132 let ic = Sys_utils.open_in_bin_no_fail
file in
133 let res = Marshal.from_channel ic in
134 Sys_utils.close_in_no_fail
"Daemon.get_context" ic;
138 failwith
"Can't find daemon parameters." in
140 (Timeout.in_channel_of_descr in_handle
,
141 Unix.out_channel_of_descr out_handle
))
143 let clear_context () =
144 Unix.putenv
"HH_SERVER_DAEMON" "";
145 Unix.putenv
"HH_SERVER_DAEMON_PARAM" "";
149 type ('param
, 'input
, 'output
) entry = ('param
, 'input
, 'output
) Entry.t
151 let exec entry param
ic oc
=
152 let f = Entry.find entry in
153 try f param
(ic, oc
); exit
0
155 prerr_endline
(Printexc.to_string e
);
156 Printexc.print_backtrace stderr
;
159 let register_entry_point = Entry.register
161 let fd_of_path path
=
162 Sys_utils.with_umask
0o111
begin fun () ->
163 Sys_utils.mkdir_no_fail
(Filename.dirname path
);
164 Unix.openfile path
[Unix.O_RDWR
; Unix.O_CREAT
; Unix.O_TRUNC
] 0o666
167 let null_fd () = fd_of_path Sys_utils.null_path
169 let setup_channels channel_mode
=
170 match channel_mode
with
172 let parent_in, child_out
= Unix.pipe
() in
173 let child_in, parent_out
= Unix.pipe
() in
174 (* Close descriptors on exec so they are not leaked. *)
175 Unix.set_close_on_exec
parent_in;
176 Unix.set_close_on_exec parent_out
;
177 (parent_in, child_out
), (child_in, parent_out
)
179 let parent_fd, child_fd
= Unix.socketpair
Unix.PF_UNIX
Unix.SOCK_STREAM
0 in
180 (** FD's on sockets are bi-directional. *)
181 (parent_fd, child_fd
), (child_fd
, parent_fd)
183 let make_pipe (descr_in
, descr_out
) =
184 let ic = Timeout.in_channel_of_descr descr_in
in
185 let oc = Unix.out_channel_of_descr descr_out
in
188 let close_pipe channel_mode
(ch_in
, ch_out
) =
189 match channel_mode
with
191 Timeout.close_in ch_in
;
194 (** the in and out FD's are the same. Close only once. *)
195 Timeout.close_in ch_in
197 (* This only works on Unix, and should be avoided as far as possible. Use
198 * Daemon.spawn instead. *)
200 ?
(channel_mode
= `pipe
)
202 (log_stdout
, log_stderr
) (f : param
-> ('a
, 'b
) channel_pair
-> unit)
203 (param
: param
) : ('b
, 'a
) handle
=
204 let (parent_in, child_out
), (child_in, parent_out
)
205 = setup_channels channel_mode
in
206 let (parent_in, child_out
) = make_pipe (parent_in, child_out
) in
207 let (child_in, parent_out
) = make_pipe (child_in, parent_out
) in
208 match Fork.fork () with
209 | -1 -> failwith
"Go get yourself a real computer"
212 ignore
(Unix.setsid
());
213 close_pipe channel_mode
(parent_in, parent_out
);
214 Sys_utils.with_umask
0o111
begin fun () ->
215 let fd = null_fd () in
216 Unix.dup2
fd Unix.stdin
;
219 Unix.dup2 log_stdout
Unix.stdout
;
220 Unix.dup2 log_stderr
Unix.stderr
;
221 if log_stdout
<> Unix.stdout
then Unix.close log_stdout
;
222 if log_stderr
<> Unix.stderr
&& log_stderr
<> log_stdout
then
223 Unix.close log_stderr
;
224 f param
(child_in, child_out
);
227 prerr_endline
(Printexc.to_string e
);
228 Printexc.print_backtrace stderr
;
230 | pid
-> (* parent *)
231 close_pipe channel_mode
(child_in, child_out
);
232 { channels
= parent_in, parent_out
; pid
}
235 (type param
) (type input
) (type output
)
236 ?
(channel_mode
= `pipe
)
238 (stdin
, stdout
, stderr
)
239 (entry: (param
, input
, output
) entry)
240 (param
: param
) : (output
, input
) handle
=
241 let (parent_in, child_out
), (child_in, parent_out
) =
242 setup_channels channel_mode
in
243 Entry.set_context entry param
(child_in, child_out
);
244 let exe = Sys_utils.executable_path
() in
245 let name = Option.value ~default
:(Entry.name_of_entry entry) name in
246 let pid = Unix.create_process
exe [|exe; name|] stdin stdout stderr
in
247 Entry.clear_context ();
248 (match channel_mode
with
251 Unix.close child_out
;
253 (** the in and out FD's are the same. Close only once. *)
254 Unix.close
child_in);
256 let close_if_open fd =
258 with Unix.Unix_error
(Unix.EBADF
, _
, _
) -> ()
260 if stdin
<> Unix.stdin
then close_if_open stdin
;
261 if stdout
<> Unix.stdout
then close_if_open stdout
;
262 if stderr
<> Unix.stderr
&& stderr
<> stdout
then close_if_open stderr
;
265 ~reason
:(Entry.name_of_entry entry)
268 { channels
= Timeout.in_channel_of_descr
parent_in,
269 Unix.out_channel_of_descr parent_out
;
272 (* for testing code *)
274 let ic = Timeout.open_in
"/dev/null" in
275 let oc = open_out
"/dev/null" in
276 {channels
= ic, oc; pid = 0}
279 * In order for the Daemon infrastructure to work, the beginning of your
280 * program (or very close to the beginning) must start with a call to
283 * Details: Daemon.spawn essentially does a fork then exec of the currently
284 * running program. Thus, the child process will just end up running the exact
285 * same program as the parent if you forgot to start with a check_entry_point.
286 * The parent process sees this as a NOOP when its program starts, but a
287 * child process (from Daemon.spawn) will use this as a GOTO to its entry
290 let check_entry_point () =
292 let entry, param
, (ic, oc) = Entry.get_context () in
293 Entry.clear_context ();
294 exec entry param
ic oc
297 let close { channels
= (ic, oc); _
} =
303 Sys_utils.terminate_process h
.pid
305 let close_out = close_out
306 let output_string = output_string
309 let close_in = Timeout.close_in
310 let input_char ic = Timeout.input_char ic
311 let input_value ic = Timeout.input_value ic