concurrencyocamlocaml-lwt

How to make a loop break when using Lwt in OCaml


I'm writing code to monitor the content of a file. When the program reaches the end of the the file I want it to terminate cleanly.

let log () : input_channel Lwt.t = 
  openfile "log" [O_RDONLY] 0 >>= fun fd -> 
  Lwt.return (of_fd input fd);;

let rec loop (ic: input_channel) = Lwt_io.read_line ic >>= fun text -> 
    Lwt_io.printl text >>= fun _ -> loop ic;;

let monitor () : unit Lwt.t = log () >>= loop;;

let handler : exn -> unit Lwt.t = fun e -> match e with
    | End_of_file -> let (p: unit Lwt.t), r = Lwt.wait() in p
    | x -> Lwt.fail x;;

let main () : unit Lwt.t = Lwt.catch monitor handler;;

let _ = Lwt_main.run (main ());;

However, when reading a file and reaching the end, the program does not terminate, it just hangs and I have to escape with Ctrl+c. I am not sure what is going on under the hood with bind but I figured whatever it's doing, eventually Lwt_io.readline ic should eventually hit the end of the file and return an End_of_file exception, which presumably would get passed over to the handler, etc.

If I had to guess at a resolution, I would think maybe in the last bind of the definition of >>= I would include some if check. But I'd be checking, I think, whether Lwt_io.read_line returned End_of_file, which I though should be handled by the handler.


Solution

  • The Lwt.wait function creates a promise which could only be resolved using the second element of the returned pair, basically, this function will never terminate:

    let never_ready () = 
      let (p,_) = Lwt.wait in
      p
    

    and this is exactly what you've written.

    Concerning a graceful termination, ideally, you should do this in the loop function so that you can close the channel and prevent leaking of the valuable resources, e.g.,

    let rec loop (ic: input_channel) = 
      Lwt_io.read_line ic >>= function
      | exception End_of_file -> 
        Lwt.close ic
      | text->
        Lwt_io.printl text >>= fun () -> 
        loop ic
    

    The minimum change to your code would be, however, to use Lwt.return () instead of Lwt.wait in the body of your handler.