-->

Interrupt a call in OCaml

2020-07-22 19:09发布

问题:

I'd like to interrupt a call if it takes too long to compute, like this

try
   do_something ()
with Too_long -> something_else ()

Is it possible to do something like that in OCaml? The function do_something may not be modified.

回答1:

In general the only way to interrupt a function is to use a signal, as Basile suggested. Unfortunately the control flow will be transferred to a signal handler, so that you will be unable to return a value that you like. To get a more fine-grained control, you can run you do_something in separate thread. A first approximation would be the following function:

exception Timeout

let with_timeout timeout f =
  let result = ref None in
  let finished = Condition.create () in
  let guard = Mutex.create () in
  let set x =
    Mutex.lock guard;
    result := Some x;
    Mutex.unlock guard in
  Mutex.lock guard;
  let work () =
    let x = f () in
    set x;
    Condition.signal finished in
  let delay () =
    Thread.delay timeout;
    Condition.signal finished in
  let task = Thread.create work () in
  let wait = Thread.create delay () in
  Condition.wait finished guard;
  match !result with
  | None ->
    Thread.kill task;
    raise Timeout
  | Some x ->
    Thread.kill wait;
    x

The solution with threads as well as with signal function has some drawbacks. For example, threads are switched in OCaml in specific iterruption points, in general this is any allocations. So if your code doesn't perform any allocations or external calls, then it may never yield to other thread and will run forever. A good example of such function is let rec f () = f (). In this is your case, then you should run your function in another process instead of thread. There're many libraries for multiprocessing in OCaml, to name a few:

  1. parmap
  2. forkwork
  3. async-parallel
  4. lwt-parallel


回答2:

There is no built-in facility to perform this precise operation in the standard library, but it is rather straightforward to implement. Using the Thread module, run one thread to perform your main program and a monitoring thread that will kill the program if it lasts too long. Here is a starting implementation:

type 'a state =
    | Running
    | Finished of 'a
    | Failed of exn
    | Cancelled of 'a

let bounded_run d f g x =
    let state = ref Running in
    let p = ref None in
    let m = ref None in
    let cancel t' = match !t' with
      | Some(t) -> Thread.kill t
      | None -> ()
    in
    let program () =
      (try state := Finished(f x)
       with exn -> state := Failed (exn));
      cancel m;
    in
    let monitor () =
      Thread.delay d;
      match !state with
        | Running -> cancel p; state := Cancelled(g x)
        | _ -> ()
    in
    p := Some(Thread.create program ());
    m := Some(Thread.create monitor p);
    (match !m with
      | None -> ()
      | Some(t) -> Thread.join t);
    !state

The call bounded_run d f g x runs f x for at most d seconds and returns Finished(f x) if the computation runs in the given time. It might return Failed(exn) if the computation throws an exception. When the computation lasts too long, the returned value is Cancelled(g x).

This implementation has many defaults, for instance, the state and the returned values should have different types (the value Running should not be possible in the returned type), it does not use mutexes to prevent concurrent accesses to the p and m variables holding references to the threads we use. While it is rough at the edges, this should get you started, but for more advanced usage, you should also learn Event or 3rd party libraries such as Lwt or Async – the former will require you to change your function.



回答3:

(I guess that you are on Linux)

Read more about signal(7)-s. You could use Ocaml's Sys.signal for Sys.sigalarm and Unix module (notably Unix.setitimer)



标签: ocaml