package process_limits

  1. Overview
  2. Docs

Source file Time_limit.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
(* Yoann Padioleau, Martin Jambon
 *
 * Copyright (C) 1998-2023 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation, with the
 * special exception on linking described in file license.txt.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the file
 * license.txt for more details.
 *)
open Common

let logger = Logging.get_logger [ __MODULE__ ]

(*****************************************************************************)
(* Prelude *)
(*****************************************************************************)

(*****************************************************************************)
(* Types *)
(*****************************************************************************)

(* A timeout exception with accompanying debug information:
   - a descriptive name
   - the time limit
     The mli interface makes this type private to help prevent unsafe uses of
     the exception.
*)
type timeout_info = { name : string; max_duration : float }

exception Timeout of timeout_info

(*****************************************************************************)
(* Helpers *)
(*****************************************************************************)

(*****************************************************************************)
(* Entry points *)
(*****************************************************************************)
let string_of_timeout_info { name; max_duration } =
  spf "%s:%g" name max_duration

let current_timer = ref None

(* it seems that the toplevel block such signals, even with this explicit
 *  command :(
 *  let _ = Unix.sigprocmask Unix.SIG_UNBLOCK [Sys.sigalrm]
 *)

(* could be in Control section *)

(*
   This is tricky stuff.

   We have to make sure that timeout is not intercepted before here, so
   avoid exn handle such as try (...) with _ -> cos timeout will not bubble up
   enough. In such case, add a case before such as
   with Timeout -> raise Timeout | _ -> ...

  question: can we have a signal and so exn when in a exn handler ?
*)
let set_timeout ~name max_duration f =
  (match !current_timer with
  | None -> ()
  | Some { name = running_name; max_duration = running_val } ->
      invalid_arg
        (spf
           "Common.set_timeout: cannot set a timeout %S of %g seconds. A timer \
            for %S of %g seconds is still running."
           name max_duration running_name running_val));
  let info (* private *) = { name; max_duration } in
  let raise_timeout () = raise (Timeout info) in
  let clear_timer () =
    current_timer := None;
    Unix.setitimer Unix.ITIMER_REAL { Unix.it_value = 0.; it_interval = 0. }
    |> ignore
  in
  let set_timer () =
    current_timer := Some info;
    Unix.setitimer Unix.ITIMER_REAL
      { Unix.it_value = max_duration; it_interval = 0. }
    |> ignore
  in
  try
    Sys.set_signal Sys.sigalrm (Sys.Signal_handle (fun _ -> raise_timeout ()));
    set_timer ();
    let x = f () in
    clear_timer ();
    Some x
  with
  | Timeout { name; max_duration } ->
      clear_timer ();
      logger#info "%S timeout at %g s (we abort)" name max_duration;
      None
  | exn ->
      let e = Exception.catch exn in
      (* It's important to disable the alarm before relaunching the exn,
         otherwise the alarm is still running.

         robust?: and if alarm launched after the log (...) ?
         Maybe signals are disabled when process an exception handler ?
      *)
      clear_timer ();
      logger#info "exn while in set_timeout";
      Exception.reraise e

let set_timeout_opt ~name time_limit f =
  match time_limit with
  | None -> Some (f ())
  | Some x -> set_timeout ~name x f
OCaml

Innovation. Community. Security.