Code smell? Lwt.async @@ fun () ->

Is there a more idiomatic way to write:

Lwt.async @@ fun () -> ...

The reason this pops up is that I have a unit Lwt.t but I need a unit, so following the type system, I get the above, but I’m wondering if there is a more elegant way to do this.

There are several ways to change a unit Lwt.t into a unit.

  • Lwt.dont_wait is like async but you also provide an exception handler
  • Lwt.async the exception handler is set in a global reference and so depending on your application it might or might not be ok
  • ignore doesn’t provide any helper for exception management
  • let _ : unit Lwt.t = … in … is similar to ignore but doesn’t change the type it just doesn’t bind it

I’m not sure what you mean by elegant or smell, but you can have your pick of the existing solutions above, or you can even roll something more specific to your needs (e.g., maintain a global list of ignored promises that you occasionally poll for errors or something of this sort). Generally though, “the type system tells me to convert this promise into an immediate unit” is not often a good reason to do so; so I’d recommending thinking about why you don’t need to wait for the promise to resolve, what happens to the work carried out by that promise, and why is it you need a unit in the first place.

1 Like

Let’s look at this piece of code here:

let read_full response_body : String.t Lwt.t =
  let out_mvar: String.t  Lwt_mvar.t = Lwt_mvar.create_empty () in
  let collector : string List.t ref = ref [] in
  let on_eof () : unit =  Lwt.async @@ fun () -> Lwt_mvar.put out_mvar @@ String.concat "" (List.rev !collector) in
  let rec on_read bs ~off ~len : unit =
    collector  := (Bigstringaf.substring ~off ~len bs) :: !collector;
    Httpaf.Body.schedule_read response_body ~on_read ~on_eof
  in 
  Httpaf.Body.schedule_read response_body ~on_read ~on_eof;
  Lwt_mvar.take out_mvar ;;

Well, I need to get it to compile. :slight_smile:

Httpaf.Body.schedule_read expects the on_eof handler to be unit → unit.

Eventually it writes to the lwt_mvar

My understanding ,and I may be 100% wrong here as I started with async ocaml two days ago, is that the Lwt.take out_mvar is going to block until we put something into the out_mvar.

===

Thanks for the suggestions. This is really |'fine grained" compared to the coarse level I’m currently operating at. Given what I wrote above, what would you recommend ?

In this case, you can use a simpler synchronisation primitive than an mvar. The mvar mechanism is a good way to synchronise different promises, some waiting for input and other producing that input. But the mvar mechanism is a bit richer than that and it also allows the producer to wait for an input having been taken by a consumer. That’s why put returns a promise.

A simpler synchronisation primitive is the one-shot waiter-wakener: Lwt.wait. This primitive is simpler (it’s one-shot!) and as a consequence, the writer doesn’t need to wait ever: the return type of Lwt.wakeup is unit.

let read_full response_body : String.t Lwt.t =
  let is_done, notify_is_done: unit Lwt.t = Lwt.wait () in
  let collector : string List.t ref = ref [] in
  let on_eof () : unit = Lwt.wakeup notify_is_done in
  let rec on_read bs ~off ~len : unit =
    collector  := (Bigstringaf.substring ~off ~len bs) :: !collector;
    Httpaf.Body.schedule_read response_body ~on_read ~on_eof
  in 
  Httpaf.Body.schedule_read response_body ~on_read ~on_eof;
  let* () = is_done in
  let collected = String.concat "" (List.rev !collector) in
  Lwt.return collected ;;

In addition to changing an mvar into a waiter-wakener pair, there is also a small difference where the synchronisation channel just sends unit instead of sending the result. The result is computed afterwards. This is optional, you could switch it back the way it was. I wrote is this way to separate the synchronisation concern from the computing of the result concern. But merging the two is ok too.

1 Like

Appreciate the rewrite, finally understand motivation behind Lwt.wait.

Thanks!