Browse Source

ocaml opium process forking (#6163)

* opium forks

* start date refreshes

* add missing changes
mudrz 4 years ago
parent
commit
80ef9f43de

+ 11 - 0
frameworks/OCaml/opium/README.md

@@ -24,6 +24,17 @@
 It doesn't have a dependency on any web server to make it more portable and allow easier addition to new webservers.
 It doesn't have a dependency on any web server to make it more portable and allow easier addition to new webservers.
 Feel free to copy paste when adding additional servers.
 Feel free to copy paste when adding additional servers.
 
 
+## Local development
+
+Either use the docker images or `make pin && make install` and then `make run` or `make run-forks` 
+
+## Variants
+
+- opium - base implementation, uses a single process
+- opium-haproxy - starts multiple processes on different ports and uses haproxy to distribute the load
+- opium-fedora-forks - starts multiple processes listening on the same socket
+- opium-alpine-forks - same as the above, but uses `alpine` instead of `fedora`
+
 ## Test URLs
 ## Test URLs
 
 
 ### PLAINTEXT
 ### PLAINTEXT

+ 46 - 0
frameworks/OCaml/opium/benchmark_config.json

@@ -25,6 +25,52 @@
         "notes": "",
         "notes": "",
         "versus": "httpaf"
         "versus": "httpaf"
       },
       },
+      "fedora-forks": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "update_url": "/updates/",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "opium",
+        "language": "OCaml",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "httpaf",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "opium-fedora-forks",
+        "notes": "",
+        "versus": "httpaf"
+      },
+      "alpine-forks": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "update_url": "/updates/",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "opium",
+        "language": "OCaml",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "httpaf",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "opium-alpine-forks",
+        "notes": "",
+        "versus": "httpaf"
+      },
       "haproxy": {
       "haproxy": {
         "json_url": "/json",
         "json_url": "/json",
         "plaintext_url": "/plaintext",
         "plaintext_url": "/plaintext",

+ 21 - 0
frameworks/OCaml/opium/opium-alpine-forks.dockerfile

@@ -0,0 +1,21 @@
+FROM ocurrent/opam:alpine-3.12-ocaml-4.11
+
+ENV DIR web
+# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html
+# https://linux.die.net/man/1/ocamlrun
+# https://blog.janestreet.com/memory-allocator-showdown/
+ENV OCAMLRUNPARAM a=2,o=240
+
+RUN sudo apk add --no-cache make m4 postgresql-dev libev-dev libffi-dev linux-headers
+
+WORKDIR /${DIR}
+
+COPY src/opi.opam src/Makefile ./
+
+RUN make install-ci
+
+COPY ./src ./
+
+RUN sudo chown -R opam: . && make build
+
+ENTRYPOINT _build/default/bin/main_forks.exe

+ 21 - 0
frameworks/OCaml/opium/opium-fedora-forks.dockerfile

@@ -0,0 +1,21 @@
+FROM ocurrent/opam:fedora-32-ocaml-4.11
+
+ENV DIR web
+# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html
+# https://linux.die.net/man/1/ocamlrun
+# https://blog.janestreet.com/memory-allocator-showdown/
+ENV OCAMLRUNPARAM a=2,o=240
+
+RUN sudo dnf install --assumeyes postgresql-devel libev-devel libffi-devel
+
+WORKDIR /${DIR}
+
+COPY src/opi.opam src/Makefile ./
+
+RUN make install-ci
+
+COPY ./src ./
+
+RUN sudo chown -R opam: . && make build
+
+ENTRYPOINT _build/default/bin/main_forks.exe

+ 2 - 0
frameworks/OCaml/opium/opium-haproxy.dockerfile

@@ -14,6 +14,8 @@ COPY src/opi.opam src/Makefile ./
 
 
 RUN make install-ci
 RUN make install-ci
 
 
+ENV APP_INSTANCES 1
+
 COPY ./src ./
 COPY ./src ./
 
 
 RUN sudo chown -R opam: . && make build
 RUN sudo chown -R opam: . && make build

+ 4 - 4
frameworks/OCaml/opium/src/Makefile

@@ -19,10 +19,10 @@ gen-opam:
 	dune build @install
 	dune build @install
 
 
 build:
 build:
-	opam exec -- dune build --profile release bin/$(BINARY).exe
+	opam exec -- dune build --profile release
 
 
 run:
 run:
-	./_build/default/bin/$(BINARY).exe
-
-run-debug:
 	dune exec $(project_name) -- --debug
 	dune exec $(project_name) -- --debug
+
+run-forks:
+	dune exec $(project_name)_forks -- --debug

+ 8 - 1
frameworks/OCaml/opium/src/bin/dune

@@ -1,4 +1,11 @@
 (executable
 (executable
  (public_name opi)
  (public_name opi)
  (name main)
  (name main)
- (libraries opi opium caqti caqti-driver-postgresql caqti-lwt tyxml lwt.unix))
+ (modules main)
+ (libraries server lwt.unix))
+
+(executable
+ (public_name opi_forks)
+ (name main_forks)
+ (modules main_forks)
+ (libraries server lwt.unix))

+ 6 - 31
frameworks/OCaml/opium/src/bin/main.ml

@@ -1,42 +1,17 @@
 open Opium.Std
 open Opium.Std
 
 
-module Tfb_headers = struct
-  let middleware =
-    let open Lwt.Syntax in
-    let filter handler req =
-      let+ res = handler req in
-      let headers = ["Server", "opium"; "Date", Opi.Time.now ()] in
-      Response.add_headers_or_replace headers res
-    in
-    Rock.Middleware.create ~name:"TFB Headers" ~filter
-end
-
 let main () =
 let main () =
   let port =
   let port =
     match Sys.getenv_opt "PORT" with
     match Sys.getenv_opt "PORT" with
     | Some x -> int_of_string x
     | Some x -> int_of_string x
     | None -> 8080
     | None -> 8080
   in
   in
-  let routes =
-    [
-      "/plaintext", Routes.plaintext;
-      "/json", Routes.json;
-      "/db", Routes.single_query;
-      "/fortunes", Routes.fortunes;
-      "/queries/", Routes.multiple_queries;
-      "/queries/:count", Routes.multiple_queries;
-      "/updates/", Routes.updates;
-      "/updates/:count", Routes.updates
-    ]
-  in
-  let add_routes app = List.fold_left (fun app (route,handler) -> (get route handler) app) app routes in
-  let app : Opium.App.t =
-    App.empty
-    |> App.cmd_name "Opium"
-    |> App.port port
-    |> middleware Middleware.content_length
-    |> middleware Tfb_headers.middleware
-    |> add_routes in
+
+  Server.dump_lwt ();
+
+  let app = Server.create_app ~port in
+
+  Server.start_refreshing_date ();
 
 
   match App.run_command' app with
   match App.run_command' app with
   | `Ok (app : unit Lwt.t ) ->
   | `Ok (app : unit Lwt.t ) ->

+ 69 - 0
frameworks/OCaml/opium/src/bin/main_forks.ml

@@ -0,0 +1,69 @@
+open Opium.Std
+open Lwt.Syntax
+
+let run_app app ~instances ~port =
+  let listen_address =
+    let inet_addr = Unix.inet_addr_any in
+    Unix.ADDR_INET (inet_addr, port)
+  in
+  let socket =
+    Lwt_unix.socket (Unix.domain_of_sockaddr listen_address) Unix.SOCK_STREAM 0
+  in
+  Lwt_unix.setsockopt socket Unix.SO_REUSEADDR true;
+
+  Lwt_main.run (
+    let+ () = Lwt_unix.bind socket listen_address in
+    Lwt_unix.listen socket (Lwt_unix.somaxconn () [@ocaml.warning "-3"])
+  );
+
+  let rec accept_loop socket handler instance =
+    let* (socket', sockaddr') = Lwt_unix.accept socket in
+    Lwt.async (fun () -> handler sockaddr' socket');
+    accept_loop socket handler instance
+  in
+
+  let rock = App.to_rock app in
+
+  for i = 1 to instances do
+    flush_all ();
+    if Lwt_unix.fork () = 0 then (
+      (* child *)
+      Server.start_refreshing_date ();
+      Lwt.async (fun () ->
+          let* () = Lwt_io.eprintf "Listening on %d (child %d)\n" port i in
+          let handler = Server.create_connection_handler rock in
+          accept_loop socket handler i
+        );
+      let forever, _ = Lwt.wait () in
+      Lwt_main.run forever;
+      exit 0)
+  done;
+
+  while true do
+    Unix.pause ()
+  done
+
+
+let main () =
+  let port =
+    match Sys.getenv_opt "PORT" with
+    | Some x -> int_of_string x
+    | None -> 8080
+  in
+  let instances =
+    match Sys.getenv_opt "APP_INSTANCES" with
+    | Some x -> int_of_string x
+    | None ->
+      let ic = Unix.open_process_in "getconf _NPROCESSORS_ONLN" in
+      let cores = int_of_string (input_line ic) in
+      ignore (Unix.close_process_in ic);
+      cores
+  in
+
+  Server.dump_lwt ();
+  Printf.eprintf "Starting %d instances\n" instances;
+
+  let app = Server.create_app ~port in
+  run_app app ~instances ~port
+
+let () = main ()

+ 1 - 1
frameworks/OCaml/opium/src/lib/routes.ml

@@ -41,7 +41,7 @@ let updates count =
       let+ result = Db.Update.update_world updated_world_req in
       let+ result = Db.Update.update_world updated_world_req in
       match result with
       match result with
       | Error _ -> failwith "failed queries"
       | Error _ -> failwith "failed queries"
-      | Ok w -> {world with randomNumber=updated_random_number}
+      | Ok () -> {world with randomNumber=updated_random_number}
     ) in
     ) in
   let+ updated_worlds = Lwt.all results in
   let+ updated_worlds = Lwt.all results in
   Models.World.list_response_to_yojson updated_worlds
   Models.World.list_response_to_yojson updated_worlds

+ 3 - 0
frameworks/OCaml/opium/src/server/dune

@@ -0,0 +1,3 @@
+(library
+ (name server)
+ (libraries opi opium caqti caqti-driver-postgresql caqti-lwt tyxml lwt.unix))

+ 0 - 0
frameworks/OCaml/opium/src/bin/routes.ml → frameworks/OCaml/opium/src/server/routes.ml


+ 80 - 0
frameworks/OCaml/opium/src/server/server.ml

@@ -0,0 +1,80 @@
+open Opium.Std
+open Lwt.Syntax
+
+module Tfb_headers = struct
+  let memo_date = ref (Opi.Time.now ())
+
+  let start_refreshing_date () = 
+    let refresh_date _sig =
+      memo_date := Opi.Time.now ();
+      ignore (Unix.alarm 1)
+    in
+    ignore (Sys.(signal sigalrm (Signal_handle refresh_date)));
+    refresh_date ()
+
+  let middleware =
+    let filter handler req =
+      let+ res = handler req in
+      let headers = ["Server", "opium"; "Date", !memo_date] in
+      Response.add_headers_or_replace headers res
+    in
+
+    Rock.Middleware.create ~name:"TFB Headers" ~filter
+end
+
+let start_refreshing_date = Tfb_headers.start_refreshing_date
+
+(* lwt debugging information *)
+let dump_lwt () =
+  let options =
+    [
+      ("fd_passing", `fd_passing);
+      ("fdatasync", `fdatasync);
+      ("get_affinity", `get_affinity);
+      ("get_cpu", `get_cpu);
+      ("get_credentials", `get_credentials);
+      ("libev", `libev);
+      ("madvise", `madvise);
+      ("mincore", `mincore);
+      ("recv_msg", `recv_msg);
+      ("send_msg", `send_msg);
+      ("set_affinity", `set_affinity);
+      ("wait4", `wait4);
+    ]
+  in
+  Printf.eprintf "Lwt:\n";
+  List.iter
+    (fun (str, opt) -> Printf.eprintf "  %s = %b\n" str (Lwt_sys.have opt))
+    options
+
+let create_connection_handler t addr fd=
+  let f ~request_handler ~error_handler =
+    Httpaf_lwt_unix.Server.create_connection_handler
+      ~request_handler:(fun _ -> request_handler)
+      ~error_handler:(fun _ -> error_handler)
+      addr
+      fd
+  in
+  Opium_kernel.Server_connection.run f t
+
+let create_app ~port =
+  let routes =
+    [
+      "/plaintext", Routes.plaintext;
+      "/json", Routes.json;
+      "/db", Routes.single_query;
+      "/fortunes", Routes.fortunes;
+      "/queries/", Routes.multiple_queries;
+      "/queries/:count", Routes.multiple_queries;
+      "/updates/", Routes.updates;
+      "/updates/:count", Routes.updates
+    ]
+  in
+  let add_routes app = List.fold_left (fun app (route,handler) -> (get route handler) app) app routes in
+
+  App.empty
+  |> App.cmd_name "Opium"
+  |> App.port port
+  |> middleware Middleware.content_length
+  |> middleware Tfb_headers.middleware
+  |> add_routes