Timeout Reading From Stdin With Promises using lwt

Task

Concurrency & Parallelism / Single-Threaded Concurrency / Timeout Reading From Stdin With Promises

Prompt the user to input some text on stdin - if the user does not finish before the timeout, abort.

Opam Packages Used

  • lwt Tested with version: 5.7.0 — Used libraries: lwt, lwt.unix

Code

We use monadic binding (let*) to chain promises together in a readable way.

let (let*) = Lwt.bind

Attempt to read a line of input from stdin using Lwt_io.read_line.

  • We use Lwt.finalize to ensure cleanup (flushing stdin) happens regardless of success/failure
  • Lwt.catch handles any exceptions during reading
let read_name () =
  Lwt.finalize
    (fun () ->
      Lwt.catch
        (fun () ->
          let* name = Lwt_io.(read_line stdin) in
          Lwt.return (Some name))
        (fun _ -> Lwt.return None))

Flush ensures no pending input is left in the buffer

    (fun () ->
      Lwt_io.flush Lwt_io.stdin)

We create a promise that resolves to None after waiting for the specified duration using Lwt_unix.sleep.

The Lwt_unix library provides the operating-system (not just "unix") and scheduling modules for Lwt.

let timeout ?(duration = 6.0) () =
  let* () = Lwt_unix.sleep duration in
  Lwt.return None

Lwt.pick starts both the read_name and timeout promises simultaneously, keeps the first promise to complete (user input or timeout), and automatically cancels the other promise.

All the promises given to a Lwt.pick need to have the same type, in this case string option Lwt.t.

let main () =
  print_endline "Enter your name (don't take too long)";
  let* result = Lwt.pick [ read_name (); timeout () ] in
  (match result with
  | Some name -> Printf.printf "Hello %s\n" name
  | None -> print_endline "Too slow!");
  Lwt.return_unit

To run the event-loop we use a single Lwt_main.run which processes a single top-level promise.

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

Discussion

ppx_let can be used to enable the syntax let%lwt instead of defining let* as a shorthand for `Lwt.bind. This makes it obvious on first glance which monad we're operating in.

Recipe not working? Comments not clear or out of date?

Open an issue or contribute to this recipe!

Other Recipes for this Task