Skip to content

Commit 80ef9f4

Browse files
authored
ocaml opium process forking (#6163)
* opium forks * start date refreshes * add missing changes
1 parent 2afedf4 commit 80ef9f4

File tree

13 files changed

+272
-37
lines changed

13 files changed

+272
-37
lines changed

frameworks/OCaml/opium/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424
It doesn't have a dependency on any web server to make it more portable and allow easier addition to new webservers.
2525
Feel free to copy paste when adding additional servers.
2626

27+
## Local development
28+
29+
Either use the docker images or `make pin && make install` and then `make run` or `make run-forks`
30+
31+
## Variants
32+
33+
- opium - base implementation, uses a single process
34+
- opium-haproxy - starts multiple processes on different ports and uses haproxy to distribute the load
35+
- opium-fedora-forks - starts multiple processes listening on the same socket
36+
- opium-alpine-forks - same as the above, but uses `alpine` instead of `fedora`
37+
2738
## Test URLs
2839

2940
### PLAINTEXT

frameworks/OCaml/opium/benchmark_config.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,52 @@
2525
"notes": "",
2626
"versus": "httpaf"
2727
},
28+
"fedora-forks": {
29+
"json_url": "/json",
30+
"plaintext_url": "/plaintext",
31+
"db_url": "/db",
32+
"query_url": "/queries/",
33+
"update_url": "/updates/",
34+
"fortune_url": "/fortunes",
35+
"port": 8080,
36+
"approach": "Realistic",
37+
"classification": "Micro",
38+
"database": "postgres",
39+
"framework": "opium",
40+
"language": "OCaml",
41+
"flavor": "None",
42+
"orm": "Micro",
43+
"platform": "httpaf",
44+
"webserver": "None",
45+
"os": "Linux",
46+
"database_os": "Linux",
47+
"display_name": "opium-fedora-forks",
48+
"notes": "",
49+
"versus": "httpaf"
50+
},
51+
"alpine-forks": {
52+
"json_url": "/json",
53+
"plaintext_url": "/plaintext",
54+
"db_url": "/db",
55+
"query_url": "/queries/",
56+
"update_url": "/updates/",
57+
"fortune_url": "/fortunes",
58+
"port": 8080,
59+
"approach": "Realistic",
60+
"classification": "Micro",
61+
"database": "postgres",
62+
"framework": "opium",
63+
"language": "OCaml",
64+
"flavor": "None",
65+
"orm": "Micro",
66+
"platform": "httpaf",
67+
"webserver": "None",
68+
"os": "Linux",
69+
"database_os": "Linux",
70+
"display_name": "opium-alpine-forks",
71+
"notes": "",
72+
"versus": "httpaf"
73+
},
2874
"haproxy": {
2975
"json_url": "/json",
3076
"plaintext_url": "/plaintext",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM ocurrent/opam:alpine-3.12-ocaml-4.11
2+
3+
ENV DIR web
4+
# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html
5+
# https://linux.die.net/man/1/ocamlrun
6+
# https://blog.janestreet.com/memory-allocator-showdown/
7+
ENV OCAMLRUNPARAM a=2,o=240
8+
9+
RUN sudo apk add --no-cache make m4 postgresql-dev libev-dev libffi-dev linux-headers
10+
11+
WORKDIR /${DIR}
12+
13+
COPY src/opi.opam src/Makefile ./
14+
15+
RUN make install-ci
16+
17+
COPY ./src ./
18+
19+
RUN sudo chown -R opam: . && make build
20+
21+
ENTRYPOINT _build/default/bin/main_forks.exe
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM ocurrent/opam:fedora-32-ocaml-4.11
2+
3+
ENV DIR web
4+
# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html
5+
# https://linux.die.net/man/1/ocamlrun
6+
# https://blog.janestreet.com/memory-allocator-showdown/
7+
ENV OCAMLRUNPARAM a=2,o=240
8+
9+
RUN sudo dnf install --assumeyes postgresql-devel libev-devel libffi-devel
10+
11+
WORKDIR /${DIR}
12+
13+
COPY src/opi.opam src/Makefile ./
14+
15+
RUN make install-ci
16+
17+
COPY ./src ./
18+
19+
RUN sudo chown -R opam: . && make build
20+
21+
ENTRYPOINT _build/default/bin/main_forks.exe

frameworks/OCaml/opium/opium-haproxy.dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ COPY src/opi.opam src/Makefile ./
1414

1515
RUN make install-ci
1616

17+
ENV APP_INSTANCES 1
18+
1719
COPY ./src ./
1820

1921
RUN sudo chown -R opam: . && make build

frameworks/OCaml/opium/src/Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ gen-opam:
1919
dune build @install
2020

2121
build:
22-
opam exec -- dune build --profile release bin/$(BINARY).exe
22+
opam exec -- dune build --profile release
2323

2424
run:
25-
./_build/default/bin/$(BINARY).exe
26-
27-
run-debug:
2825
dune exec $(project_name) -- --debug
26+
27+
run-forks:
28+
dune exec $(project_name)_forks -- --debug
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
(executable
22
(public_name opi)
33
(name main)
4-
(libraries opi opium caqti caqti-driver-postgresql caqti-lwt tyxml lwt.unix))
4+
(modules main)
5+
(libraries server lwt.unix))
6+
7+
(executable
8+
(public_name opi_forks)
9+
(name main_forks)
10+
(modules main_forks)
11+
(libraries server lwt.unix))

frameworks/OCaml/opium/src/bin/main.ml

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,17 @@
11
open Opium.Std
22

3-
module Tfb_headers = struct
4-
let middleware =
5-
let open Lwt.Syntax in
6-
let filter handler req =
7-
let+ res = handler req in
8-
let headers = ["Server", "opium"; "Date", Opi.Time.now ()] in
9-
Response.add_headers_or_replace headers res
10-
in
11-
Rock.Middleware.create ~name:"TFB Headers" ~filter
12-
end
13-
143
let main () =
154
let port =
165
match Sys.getenv_opt "PORT" with
176
| Some x -> int_of_string x
187
| None -> 8080
198
in
20-
let routes =
21-
[
22-
"/plaintext", Routes.plaintext;
23-
"/json", Routes.json;
24-
"/db", Routes.single_query;
25-
"/fortunes", Routes.fortunes;
26-
"/queries/", Routes.multiple_queries;
27-
"/queries/:count", Routes.multiple_queries;
28-
"/updates/", Routes.updates;
29-
"/updates/:count", Routes.updates
30-
]
31-
in
32-
let add_routes app = List.fold_left (fun app (route,handler) -> (get route handler) app) app routes in
33-
let app : Opium.App.t =
34-
App.empty
35-
|> App.cmd_name "Opium"
36-
|> App.port port
37-
|> middleware Middleware.content_length
38-
|> middleware Tfb_headers.middleware
39-
|> add_routes in
9+
10+
Server.dump_lwt ();
11+
12+
let app = Server.create_app ~port in
13+
14+
Server.start_refreshing_date ();
4015

4116
match App.run_command' app with
4217
| `Ok (app : unit Lwt.t ) ->
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
open Opium.Std
2+
open Lwt.Syntax
3+
4+
let run_app app ~instances ~port =
5+
let listen_address =
6+
let inet_addr = Unix.inet_addr_any in
7+
Unix.ADDR_INET (inet_addr, port)
8+
in
9+
let socket =
10+
Lwt_unix.socket (Unix.domain_of_sockaddr listen_address) Unix.SOCK_STREAM 0
11+
in
12+
Lwt_unix.setsockopt socket Unix.SO_REUSEADDR true;
13+
14+
Lwt_main.run (
15+
let+ () = Lwt_unix.bind socket listen_address in
16+
Lwt_unix.listen socket (Lwt_unix.somaxconn () [@ocaml.warning "-3"])
17+
);
18+
19+
let rec accept_loop socket handler instance =
20+
let* (socket', sockaddr') = Lwt_unix.accept socket in
21+
Lwt.async (fun () -> handler sockaddr' socket');
22+
accept_loop socket handler instance
23+
in
24+
25+
let rock = App.to_rock app in
26+
27+
for i = 1 to instances do
28+
flush_all ();
29+
if Lwt_unix.fork () = 0 then (
30+
(* child *)
31+
Server.start_refreshing_date ();
32+
Lwt.async (fun () ->
33+
let* () = Lwt_io.eprintf "Listening on %d (child %d)\n" port i in
34+
let handler = Server.create_connection_handler rock in
35+
accept_loop socket handler i
36+
);
37+
let forever, _ = Lwt.wait () in
38+
Lwt_main.run forever;
39+
exit 0)
40+
done;
41+
42+
while true do
43+
Unix.pause ()
44+
done
45+
46+
47+
let main () =
48+
let port =
49+
match Sys.getenv_opt "PORT" with
50+
| Some x -> int_of_string x
51+
| None -> 8080
52+
in
53+
let instances =
54+
match Sys.getenv_opt "APP_INSTANCES" with
55+
| Some x -> int_of_string x
56+
| None ->
57+
let ic = Unix.open_process_in "getconf _NPROCESSORS_ONLN" in
58+
let cores = int_of_string (input_line ic) in
59+
ignore (Unix.close_process_in ic);
60+
cores
61+
in
62+
63+
Server.dump_lwt ();
64+
Printf.eprintf "Starting %d instances\n" instances;
65+
66+
let app = Server.create_app ~port in
67+
run_app app ~instances ~port
68+
69+
let () = main ()

frameworks/OCaml/opium/src/lib/routes.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ let updates count =
4141
let+ result = Db.Update.update_world updated_world_req in
4242
match result with
4343
| Error _ -> failwith "failed queries"
44-
| Ok w -> {world with randomNumber=updated_random_number}
44+
| Ok () -> {world with randomNumber=updated_random_number}
4545
) in
4646
let+ updated_worlds = Lwt.all results in
4747
Models.World.list_response_to_yojson updated_worlds

0 commit comments

Comments
 (0)