|
| 1 | +(* Js_of_ocaml library |
| 2 | + * http://www.ocsigen.org/js_of_ocaml/ |
| 3 | + * Copyright (C) 2016 OCamlPro |
| 4 | + * |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU Library General Public License as published by |
| 7 | + * the Free Software Foundation, with linking exception; |
| 8 | + * either version 2.1 of the License, or (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU Library General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU Library General Public License |
| 16 | + * along with this program; if not, write to the Free Software |
| 17 | + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 18 | + *) |
| 19 | +open Js_of_ocaml |
| 20 | +open JsooTopWorkerIntf |
| 21 | + |
| 22 | +type 'a result = 'a JsooTopWrapped.result Lwt.t |
| 23 | + |
| 24 | +let ( >>= ) = Lwt.bind |
| 25 | + |
| 26 | +let ( >>? ) o f = |
| 27 | + let open! JsooTopWrapped in |
| 28 | + o |
| 29 | + >>= function |
| 30 | + | Error (err, w) -> Lwt.return (Error (err, w)) |
| 31 | + | Success (x, w) -> ( |
| 32 | + f x |
| 33 | + >>= function |
| 34 | + | Error (err, w') -> Lwt.return (Error (err, w @ w')) |
| 35 | + | Success (x, w') -> Lwt.return (Success (x, w @ w'))) |
| 36 | + |
| 37 | +let return_success e = Lwt.return (JsooTopWrapped.Success (e, [])) |
| 38 | + |
| 39 | +let return_unit_success = return_success () |
| 40 | + |
| 41 | +(* let return_error e = Lwt.return (JsooTopWrapped.Error (e, [])) *) |
| 42 | +(* let return_exn exn = return_error (JsooTopWrapped.error_of_exn exn) *) |
| 43 | + |
| 44 | +(* let wrap pp = *) |
| 45 | +(* let buf = Buffer.create 503 in *) |
| 46 | +(* let flush () = *) |
| 47 | +(* let s = Buffer.contents buf in *) |
| 48 | +(* if s <> "" then begin *) |
| 49 | +(* Buffer.reset buf; *) |
| 50 | +(* pp s *) |
| 51 | +(* end in *) |
| 52 | +(* Format.make_formatter (Buffer.add_substring buf) flush *) |
| 53 | + |
| 54 | +(* let () = *) |
| 55 | +(* Location.register_error_of_exn *) |
| 56 | +(* (function *) |
| 57 | +(* | Js.Error e -> *) |
| 58 | +(* Firebug.console##log(e##stack); *) |
| 59 | +(* let msg = Js.to_string e##message in *) |
| 60 | +(* Some { Location.msg; if_highlight = msg; sub = []; loc = Location.none } *) |
| 61 | +(* | _ -> None) *) |
| 62 | + |
| 63 | +module IntMap = Map.Make (Int) |
| 64 | + |
| 65 | +let map_option f o = |
| 66 | + match o with |
| 67 | + | None -> None |
| 68 | + | Some o -> Some (f o) |
| 69 | + |
| 70 | +let iter_option f o = |
| 71 | + match o with |
| 72 | + | None -> () |
| 73 | + | Some o -> f o |
| 74 | + |
| 75 | +type u = |
| 76 | + | U : 'a msg_ty * 'a JsooTopWrapped.result Lwt.u * 'a JsooTopWrapped.result Lwt.t -> u |
| 77 | + |
| 78 | +type output = string -> unit |
| 79 | + |
| 80 | +type toplevel = |
| 81 | + { cmis_prefix : string |
| 82 | + ; js_file : string |
| 83 | + ; mutable imported : string list |
| 84 | + ; mutable worker : (Js.js_string Js.t, Js.js_string Js.t) Worker.worker Js.t |
| 85 | + ; mutable wakeners : u IntMap.t |
| 86 | + ; mutable counter : int |
| 87 | + ; mutable fds : output IntMap.t |
| 88 | + ; mutable fd_counter : int |
| 89 | + ; mutable reset_worker : toplevel -> unit Lwt.t |
| 90 | + ; mutable after_init : toplevel -> unit Lwt.t |
| 91 | + ; pp_stdout : output |
| 92 | + ; pp_stderr : output } |
| 93 | + |
| 94 | +exception Not_equal |
| 95 | + |
| 96 | +let check_equal : type t1 t2. t1 msg_ty -> t2 msg_ty -> (t1, t2) eq = |
| 97 | + fun ty1 ty2 -> |
| 98 | + match ty1, ty2 with |
| 99 | + | Unit, Unit -> Eq |
| 100 | + | Bool, Bool -> Eq |
| 101 | + | Int, Int -> Eq |
| 102 | + | String, String -> Eq |
| 103 | + | Unit, _ -> raise Not_equal |
| 104 | + | Bool, _ -> raise Not_equal |
| 105 | + | Int, _ -> raise Not_equal |
| 106 | + | String, _ -> raise Not_equal |
| 107 | + |
| 108 | +let onmessage worker (ev : _ Worker.messageEvent Js.t) = |
| 109 | + match Json.unsafe_input ev##.data with |
| 110 | + | Write (fd, s) -> ( |
| 111 | + try |
| 112 | + IntMap.find fd worker.fds s; |
| 113 | + Js._false |
| 114 | + with Not_found -> |
| 115 | + Firebug.console##warn (Js.string (Printf.sprintf "Missing channels (%d)" fd)); |
| 116 | + Js._false) |
| 117 | + | ReturnSuccess (id, ty_v, v, w) -> ( |
| 118 | + try |
| 119 | + let (U (ty_u, u, _)) = IntMap.find id worker.wakeners in |
| 120 | + let Eq = check_equal ty_u ty_v in |
| 121 | + worker.wakeners <- IntMap.remove id worker.wakeners; |
| 122 | + Lwt.wakeup u (JsooTopWrapped.Success (v, w)); |
| 123 | + Js._false |
| 124 | + with |
| 125 | + | Not_found -> |
| 126 | + Firebug.console##warn (Js.string (Printf.sprintf "Missing wakeners (%d)" id)); |
| 127 | + Js._false |
| 128 | + | Not_equal -> |
| 129 | + Firebug.console##warn (Js.string (Printf.sprintf "Unexpected wakeners (%d)" id)); |
| 130 | + Js._false) |
| 131 | + | ReturnError (id, e, w) -> ( |
| 132 | + try |
| 133 | + let (U (_, u, _)) = IntMap.find id worker.wakeners in |
| 134 | + worker.wakeners <- IntMap.remove id worker.wakeners; |
| 135 | + Lwt.wakeup u (JsooTopWrapped.Error (e, w)); |
| 136 | + Js._false |
| 137 | + with Not_found -> |
| 138 | + Firebug.console##warn (Js.string (Printf.sprintf "Missing wakeners (%d)" id)); |
| 139 | + Js._false) |
| 140 | + |
| 141 | +let terminate worker = |
| 142 | + (worker.worker)##terminate; |
| 143 | + IntMap.iter |
| 144 | + (fun id (U (_, _, t)) -> |
| 145 | + worker.wakeners <- IntMap.remove id worker.wakeners; |
| 146 | + Lwt.cancel t) |
| 147 | + worker.wakeners |
| 148 | + |
| 149 | +let never_ending = |
| 150 | + (* and not cancellable. *) |
| 151 | + fst (Lwt.wait ()) |
| 152 | + |
| 153 | +let ty_of_host_msg : type t. t host_msg -> t msg_ty = function |
| 154 | + | Init _ -> Unit |
| 155 | + | Reset -> Unit |
| 156 | + | Check _ -> Unit |
| 157 | + | Execute _ -> Bool |
| 158 | + | Use_string _ -> Bool |
| 159 | + | Use_mod_string _ -> Bool |
| 160 | + | Import_scripts _ -> Unit |
| 161 | + |
| 162 | +(** Threads created with [post] will always be wake-uped by |
| 163 | + [onmessage] by calling [Lwt.wakeup]. They should never end with |
| 164 | + an exception, unless canceled. When canceled, the worker is |
| 165 | + killed and a new one is spawned. *) |
| 166 | +let rec post : type a. toplevel -> a host_msg -> a JsooTopWrapped.result Lwt.t = |
| 167 | + fun worker msg -> |
| 168 | + let msg_id = worker.counter in |
| 169 | + let msg_ty = ty_of_host_msg msg in |
| 170 | + let t, u = Lwt.task () in |
| 171 | + Lwt.on_cancel t (fun () -> Lwt.async (fun () -> worker.reset_worker worker)); |
| 172 | + worker.wakeners <- IntMap.add msg_id (U (msg_ty, u, t)) worker.wakeners; |
| 173 | + worker.counter <- msg_id + 1; |
| 174 | + (worker.worker)##postMessage (Json.output (msg_id, msg)); |
| 175 | + t |
| 176 | + |
| 177 | +and do_reset_worker () = |
| 178 | + let running = ref true in |
| 179 | + fun worker -> |
| 180 | + if !running |
| 181 | + then ( |
| 182 | + running := false; |
| 183 | + terminate worker; |
| 184 | + IntMap.iter |
| 185 | + (* GRGR: Peut-on 'cancel' directement le Lwt.u ? *) |
| 186 | + (fun _ (U (_, _, t)) -> Lwt.cancel t) |
| 187 | + worker.wakeners; |
| 188 | + worker.worker <- Worker.create worker.js_file; |
| 189 | + worker.fds <- |
| 190 | + IntMap.empty |
| 191 | + |> IntMap.add 0 (IntMap.find 0 worker.fds) |
| 192 | + |> IntMap.add 1 (IntMap.find 1 worker.fds); |
| 193 | + worker.fd_counter <- 2; |
| 194 | + let imported = worker.imported in |
| 195 | + worker.imported <- []; |
| 196 | + worker.wakeners <- IntMap.empty; |
| 197 | + worker.counter <- 0; |
| 198 | + worker.reset_worker <- do_reset_worker (); |
| 199 | + (Obj.magic worker.worker)##.onmessage := Js.wrap_callback (onmessage worker); |
| 200 | + Lwt_list.iter_p |
| 201 | + (fun name -> import_cmis_js worker name >>= fun _ -> Lwt.return_unit) |
| 202 | + imported |
| 203 | + >>= fun () -> |
| 204 | + post worker @@ Init worker.cmis_prefix |
| 205 | + >>= fun _ -> worker.after_init worker >>= fun _ -> Lwt.return_unit) |
| 206 | + else Lwt.return_unit |
| 207 | + |
| 208 | +and import_cmis_js worker name = |
| 209 | + if List.mem name worker.imported |
| 210 | + then return_unit_success |
| 211 | + else |
| 212 | + let url = worker.cmis_prefix ^ name ^ ".cmis.js" in |
| 213 | + post worker @@ Import_scripts [url] |
| 214 | + >>? fun () -> |
| 215 | + worker.imported <- name :: worker.imported; |
| 216 | + return_unit_success |
| 217 | + |
| 218 | +let create |
| 219 | + ?(cmis_prefix = "") |
| 220 | + ?(after_init = fun _ -> Lwt.return_unit) |
| 221 | + ~pp_stdout |
| 222 | + ~pp_stderr |
| 223 | + ~js_file |
| 224 | + () = |
| 225 | + let worker = Worker.create js_file in |
| 226 | + let fds = IntMap.empty |> IntMap.add 0 pp_stdout |> IntMap.add 1 pp_stderr in |
| 227 | + let worker = |
| 228 | + { cmis_prefix |
| 229 | + ; imported = [] |
| 230 | + ; worker |
| 231 | + ; js_file |
| 232 | + ; wakeners = IntMap.empty |
| 233 | + ; counter = 0 |
| 234 | + ; fds |
| 235 | + ; fd_counter = 2 |
| 236 | + ; reset_worker = do_reset_worker () |
| 237 | + ; after_init |
| 238 | + ; pp_stdout |
| 239 | + ; pp_stderr } |
| 240 | + in |
| 241 | + (Obj.magic worker.worker)##.onmessage := Js.wrap_callback (onmessage worker); |
| 242 | + post worker @@ Init cmis_prefix |
| 243 | + >>= fun _ -> worker.after_init worker >>= fun () -> Lwt.return worker |
| 244 | +
|
| 245 | +let create_fd worker pp = |
| 246 | + worker.fds <- IntMap.add worker.fd_counter pp worker.fds; |
| 247 | + let fd = worker.fd_counter in |
| 248 | + worker.fd_counter <- fd + 1; |
| 249 | + fd |
| 250 | +
|
| 251 | +let close_fd worker fd = worker.fds <- IntMap.remove fd worker.fds |
| 252 | +
|
| 253 | +let reset worker ?(timeout = fun () -> never_ending) () = |
| 254 | + let timeout = timeout () in |
| 255 | + Lwt.choose |
| 256 | + [ (post worker Reset >>= fun res -> Lwt.return (`Reset res)) |
| 257 | + ; (timeout >>= fun () -> Lwt.return `Timeout) ] |
| 258 | + >>= function |
| 259 | + | `Reset (JsooTopWrapped.Success ((), _)) -> |
| 260 | + Lwt.cancel timeout; |
| 261 | + worker.after_init worker |
| 262 | + | `Reset (JsooTopWrapped.Error (err, _)) -> |
| 263 | + Lwt.cancel timeout; |
| 264 | + worker.pp_stderr err.JsooTopWrapped.msg; |
| 265 | + worker.reset_worker worker |
| 266 | + | `Timeout -> |
| 267 | + (* Not canceling the Reset thread, but manually resetting. *) |
| 268 | + worker.reset_worker worker |
| 269 | +
|
| 270 | +let check worker ?(setenv = false) code = post worker @@ Check (setenv, code) |
| 271 | +
|
| 272 | +let execute worker ?ppf_code ?(print_outcome = false) ~ppf_answer code = |
| 273 | + let ppf_code = map_option (create_fd worker) ppf_code in |
| 274 | + let ppf_answer = create_fd worker ppf_answer in |
| 275 | + post worker @@ Execute (ppf_code, print_outcome, ppf_answer, code) |
| 276 | + >>= fun result -> |
| 277 | + iter_option (close_fd worker) ppf_code; |
| 278 | + close_fd worker ppf_answer; |
| 279 | + Lwt.return result |
| 280 | +
|
| 281 | +let use_string worker ?filename ?(print_outcome = false) ~ppf_answer code = |
| 282 | + let ppf_answer = create_fd worker ppf_answer in |
| 283 | + post worker @@ Use_string (filename, print_outcome, ppf_answer, code) |
| 284 | + >>= fun result -> |
| 285 | + close_fd worker ppf_answer; |
| 286 | + Lwt.return result |
| 287 | +
|
| 288 | +let use_mod_string |
| 289 | + worker |
| 290 | + ?(print_outcome = false) |
| 291 | + ~ppf_answer |
| 292 | + ~modname |
| 293 | + ?sig_code |
| 294 | + impl_code = |
| 295 | + let ppf_answer = create_fd worker ppf_answer in |
| 296 | + post worker @@ Use_mod_string (ppf_answer, print_outcome, modname, sig_code, impl_code) |
| 297 | + >>= fun result -> |
| 298 | + close_fd worker ppf_answer; |
| 299 | + Lwt.return result |
| 300 | +
|
| 301 | +let set_after_init w after_init = w.after_init <- after_init |
0 commit comments