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.
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.
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:
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.
(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
)