package devkit

  1. Overview
  2. Docs
Development kit - general purpose library

Install

dune-project
 Dependency

Authors

Maintainers

Sources

1.20261006.tar.gz
md5=a39dbbce04bd5562a9b4443daf39c582
sha512=b10104b61384b2151521b785e7f3c3250134c75eae09b42cad69bc4a22a9a5e429beb891ee87cedf376c23d9c40f09d3f71c3fd48aa35df490c0f69dc17f3242

doc/src/devkit.core/daemon.ml.html

Source file daemon.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
(** daemon utilities *)

module U = ExtUnix.Specific

let log = Log.from "daemon"

let logfile = ref None
let pidfile = ref None
let runas = ref None
let foreground = ref false

let managed = ref false

(** global flag indicating that process should exit,
    [manage] will automatically set this flag on SIGTERM unless default signal handling is overriden
*)
let should_exit_ = ref false

(** [should_exit_lwt] usage is discouraged.
    Use [wait_exit] instead, which makes it harder to ignore "should exit" state and loop infinitely
*)
let (should_exit_lwt,signal_exit_lwt) = Lwt.wait ()
let should_exit () = !should_exit_
let should_run () = not !should_exit_

(** exception to be raised by functions that wish to signal premature termination due to [!should_exit = true] *)
exception ShouldExit

let signal_exit =
  let do_lwt = lazy (
    (* we can't use Lwt's wakeup_later because it doesn't always "later", it
       soemtimes behaves the same as plain wakeup *)
    Lwt.dont_wait
      (fun () ->
        Lwt.bind
          (Lwt.pause ())
          (fun () -> Lwt.wakeup signal_exit_lwt (); Lwt.return_unit))
      (fun exc -> log#error "signal exit: error at wakeup: %s" (Printexc.to_string exc))
    )
  in
  (* nearly-invariant: should_exit_ = (Lwt.state should_exit_lwt = Lwt.Return) *)
  fun () -> should_exit_ := true; Lazy.force do_lwt

(** @raise ShouldExit if [should_exit] condition is set, otherwise do nothing *)
let break () = if !should_exit_ then raise ShouldExit

(** wait until [should_exit] is set and raise [ShouldExit] *)
let wait_exit =
  (* NOTE
    Bind to should_exit_lwt only once, because every bind will create an immutable waiter on
    should_exit_lwt's sleeper, that is only removed after should_exit_lwt thread terminates.
  *)
  let thread = lazy (Lwt.bind should_exit_lwt (fun () -> raise ShouldExit)) in
  fun () -> Lazy.force thread

(** [break_lwt = Lwt.wrap break] *)
let break_lwt () = Lwt.wrap break

(** [unless_exit x] resolves promise [x] or raises [ShouldExit] *)
let unless_exit x = Lwt.pick [wait_exit (); x]

let get_args () =
  [
    ("-loglevel", Arg.String Log.set_loglevels, " ([<facil|prefix*>=]debug|info|warn|error[,])+");
    ("-logformat",
      Arg.Symbol (["plain"; "default"; "logfmt"], (function
        | "plain" | "default" -> Log.State.set_plaintext ()
        | "logfmt" -> Log.State.set_logfmt ()
        | s -> failwith (Printf.sprintf "unknown log format %S" s))),
      " Log output format (default: plain)");
    ExtArg.may_str "logfile" logfile "<file> Log file";
    ExtArg.may_str "pidfile" pidfile "<file> PID file";
    "-runas",
      Arg.String (fun name -> try runas := Some (Unix.getpwnam name) with exn -> Exn.fail ~exn "runas: unknown user %s" name),
      "<user> run as specified user";
    "-fg", Arg.Set foreground, " Stay in foreground";
  ]

let args = get_args ()

let install_signal_handlers () =
  let unix_stderr s =
    let s = Log.State.format_simple `Info log#facility s in
    try
      let (_:int) = Unix.write_substring Unix.stderr s 0 (String.length s) in ()
    with _ ->
      () (* do not fail, can be ENOSPC *)
  in
  Signal.set [Sys.sigpipe] ignore;
  Signal.set_verbose [Sys.sigusr1] "reopen log" (fun () -> Log.reopen !logfile);
  Signal.set_verbose [Sys.sigusr2] "memory reclaim and stats" begin fun () ->
    match Signal.is_safe_output () with
    | true -> Memory.log_stats (); Memory.reclaim ()
    | false ->
      (* output directly to fd to prevent deadlock, but breaks buffering *)
      Memory.get_stats () |> List.iter unix_stderr;
      Memory.reclaim_s () |> unix_stderr
  end;
  Signal.set_exit signal_exit

let manage () =
  match !managed with
  | true -> () (* be smart *)
  | false ->
(*
  this will fail if files don't exists :(
  (* fail before fork if something is wrong *)
  Option.may (fun path -> Unix.(access path [R_OK;W_OK])) !logfile;
  Option.may (fun path -> Unix.(access path [R_OK;W_OK])) !pidfile;
*)
  Option.may Nix.check_pidfile !pidfile; (* check pidfile before fork to fail early *)
  if not !foreground then Nix.daemonize ();
  begin match !runas with
  | None -> ()
  | Some pw ->
    let uid = pw.Unix.pw_uid and gid = pw.Unix.pw_gid in
    U.setreuid uid uid;
    U.setregid gid gid;
  end;
  Log.reopen !logfile; (* immediately after fork *)
  Log.read_env_config ();
  Option.may Nix.manage_pidfile !pidfile; (* write pidfile after fork! *)
  if Option.is_some !logfile then
  begin
    log #info "run: %s" Nix.cmdline;
    log #info "GC settings: %s" (Action.gc_settings ());
  end;
  install_signal_handlers ();
  Nix.raise_limits ();
  managed := true;
  ()