quit a little more gracefully when interrupted (SIGINT, SIGTERM)

This commit is contained in:
tali 2024-02-01 15:17:23 -05:00
parent e355bac41c
commit e160156b78
1 changed files with 65 additions and 44 deletions

View File

@ -14,25 +14,21 @@ type config = {
notify : [`ready | `stopping] -> unit; notify : [`ready | `stopping] -> unit;
} }
type ping_wheel = Connection.t Wheel.t let bind_server
let listener
~(port : int) ~(port : int)
~(listen_backlog : int) ~(listen_backlog : int)
~(on_ready : unit -> unit) : fd Lwt.t =
: (fd * sockaddr) Lwt_stream.t = let fd = Lwt_unix.socket PF_INET SOCK_STREAM 0 in
let sock : fd Lwt.t = Lwt_unix.setsockopt fd SO_KEEPALIVE false;
let fd = Lwt_unix.socket PF_INET SOCK_STREAM 0 in Lwt_unix.setsockopt fd SO_REUSEPORT false;
Lwt_unix.setsockopt fd SO_KEEPALIVE false; let srv_adr = Unix.ADDR_INET (Unix.inet_addr_any, port) in
Lwt_unix.setsockopt fd SO_REUSEPORT false; let* () = Lwt_unix.bind fd srv_adr in
let srv_adr = Unix.ADDR_INET (Unix.inet_addr_any, port) in Lwt_unix.listen fd listen_backlog;
let* () = Lwt_unix.bind fd srv_adr in info (fun m -> m "listening on %a" pp_sockaddr srv_adr);
Lwt_unix.listen fd listen_backlog; Lwt.return fd
on_ready ();
info (fun m -> m "listening on %a" pp_sockaddr srv_adr); let accepts (fd : fd) : (fd * sockaddr) Lwt_stream.t =
Lwt.return fd let accept () = Lwt_unix.accept fd >>= Lwt.return_some in
in
let accept () = sock >>= Lwt_unix.accept >|= Option.some in
Lwt_stream.from accept Lwt_stream.from accept
let reader (fd : fd) : Msg.t Lwt_stream.t = let reader (fd : fd) : Msg.t Lwt_stream.t =
@ -81,7 +77,7 @@ let handle_client
(conn_addr : sockaddr) (conn_addr : sockaddr)
~(server_info : Server_info.t) ~(server_info : Server_info.t)
~(router : Router.t) ~(router : Router.t)
~(ping_wheel : ping_wheel) ~(ping_wheel : Connection.t Wheel.t)
= =
info (fun m -> m "new connection %a" pp_sockaddr conn_addr); info (fun m -> m "new connection %a" pp_sockaddr conn_addr);
let conn : Connection.t = let conn : Connection.t =
@ -107,6 +103,24 @@ let handle_client
(fun e -> error (fun m -> m "%a:@ %a" pp_sockaddr conn_addr Fmt.exn e)); (fun e -> error (fun m -> m "%a:@ %a" pp_sockaddr conn_addr Fmt.exn e));
end end
let interval dt =
let tick () =
let* () = Lwt_unix.sleep dt in
Lwt.return_some ()
in
Lwt_stream.from tick
let interrupt () =
let signal, signal_waiter = Lwt.wait () in
let on_signal num =
trace (fun m -> m "caught signal %d" num);
try Lwt.wakeup signal_waiter () with
Invalid_argument _ -> failwith "unceremoniously exiting"
in
Lwt_unix.on_signal (2 (* SIGINT *)) on_signal |> ignore;
Lwt_unix.on_signal (15 (* SIGTERM *)) on_signal |> ignore;
signal
let run { let run {
port; port;
listen_backlog; listen_backlog;
@ -137,6 +151,13 @@ let run {
info (fun m -> m "version:@ %s" server_info.version); info (fun m -> m "version:@ %s" server_info.version);
info (fun m -> m "created:@ %s" server_info.created); info (fun m -> m "created:@ %s" server_info.created);
let* server : fd =
bind_server
~port
~listen_backlog
in
notify `ready;
let router : Router.t = let router : Router.t =
Router.make Router.make
~whowas_history_len ~whowas_history_len
@ -147,40 +168,40 @@ let run {
ping_interval ping_interval
in in
let on_tick () = let ping conn =
(* trace (fun m -> m "tick"); *) match Connection.on_ping conn with
List.iter | Ok () -> Wheel.add ping_wheel conn
(fun conn -> | Error _ -> Connection.close conn ~reason:"Connection timeout"
match Connection.on_ping conn with
| Ok () -> Wheel.add ping_wheel conn
| Error () -> Connection.close conn ~reason:"Connection timed out")
(Wheel.tick ping_wheel)
in in
let pinger_promise = let pinger_promise =
Lwt_stream.iter Lwt_stream.iter
on_tick (fun () ->
(Lwt_stream.from @@ fun () -> List.iter ping
let* () = Lwt_unix.sleep 1.0 in (Wheel.tick ping_wheel))
Lwt.return_some ()) (interval 1.0)
in
let on_con (fd, adr) =
handle_client fd adr
~server_info
~router
~ping_wheel
in in
let listener_promise = let listener_promise =
Lwt_stream.iter Lwt_stream.iter
on_con (fun (fd, addr) ->
(listener handle_client fd addr
~port ~server_info
~listen_backlog ~router
~on_ready:(fun () -> notify `ready)) ~ping_wheel)
(accepts server)
in in
(* TODO: graceful cleanup on ctrl-c *) let* () =
Lwt.pick [
listener_promise <?> pinger_promise;
interrupt ()
]
in
notify `stopping;
(* TODO: send QUIT to all connections *)
info (fun m -> m "shutting down");
Lwt_unix.close server
listener_promise <&> pinger_promise