1

I'm writing some networking code currently and I need to send out a large number of messages and then wait for a single response.

Given that I have a function that returns the input and output channels for a socket I have:

let resps = List.map uris ~f:(fun uri -> 
  let%lwt (ic,oc) = connect uri in
  let%lwt () = Lwt_io.write_value oc msg in
  Lwt_io.read_value ic
) in
Lwt.pick resps

My understanding of this is that pick should cancel any ongoing requests after it has a fulfilled promise in resps. The issue is that if any of those connections fails/is refused, an exception is raised Unix.ECONNREFUSED.

My question is what is the correct semantics to force Lwt.pick to ignore the exceptions?

Options I've thought of so far are to catch the exception explicity in the requests:

let resps = List.map uris ~f:(fun uri -> 
  try
  let%lwt (ic,oc) = connect uri in
  let%lwt () = Lwt_io.write_value oc msg in
  Lwt_io.read_value ic
  with Unix_error (e,_,_) -> ...
) in
Lwt.pick resps

But I'm not sure under what conditions the Lwt.pick will view those promises are rejected?

Update: I'm now handling the errors with cancellable, unfulfillable promises:

fst @@ Lwt.task ()

This feels hacky but seems to work so far.

ivg
  • 34,431
  • 2
  • 35
  • 63
Cjen1
  • 1,826
  • 3
  • 17
  • 47

1 Answers1

2

Handling the exception explicitly is right. Lwt promises are rejected when you either reject them explicitly (using Lwt.fail), or when an exception is caught by Lwt, in a callback that should have returned a promise (like the one you would pass to Lwt.bind).

However, for handling exceptions in code that calls into Lwt, you have to use try%lwt instead of the plain try.

antron
  • 3,749
  • 2
  • 17
  • 23
  • So in terms of the semantics of sending a message out to many hosts and only waiting on the one response, I should handle any exceptions raised by the communication (`ECONNREFUSED` etc) and then what should the return of those handlers be? Just raise `Lwt.Cancelled`, or return a promise which never resolves? – Cjen1 Oct 08 '19 at 13:40
  • If you are tempted to return a promise that never resolves, you probably shouldn't be passing any of these promises to `Lwt.pick` in the first place. Are you waiting for *one specific* response, or for the *first* response? – antron Oct 08 '19 at 17:10
  • If for one specific response (as suggested by your comment), then bind only on that response. If the first response (as suggested by the initial question), but you want to ignore exceptions, then probably the cleanest way is to create the final promise using `Lwt.wait` ahead of time, and have the first successful response resolve that promise. `Lwt.pick` isn't quite right for this use case, because you need a slightly different behavior than it provides. – antron Oct 08 '19 at 17:12
  • I am looking for the first one to resolve, which I thought was the semantics of `Lwt.pick` unless I am mistaken? – Cjen1 Oct 09 '19 at 13:14
  • You are correct that those are the semantics of `Lwt.pick`, however, `Lwt.pick` will report the first resolution whether it is a fulfillment or a rejection. It seems that you are looking for the first fulfillment, and if the first promise to resolve is rejected, you would like that rejection disregarded, and to keep waiting. These semantics are not quite the semantics of `Lwt.pick`, so you may need your own helper function, or just a one-time use of `Lwt.wait` :) – antron Oct 09 '19 at 18:16