Disclaimer: I haven’t read your code. I skimmed it and I don’t have time immediately to parse and understand it. So I offer answers to some of your questions hoping that it can help you anyway.
You do not need to use async to do “parallel” Lwt “threads”. (See below about why I’m using quotes.)
The main ways to have two tasks executed concurrently is to use
Lwt.both or the variants Lwt.all and Lwt.join. These functions take multiple promises as argument and return a new promise which resolves once all the provided promises have.
Lwt.choose or Lwt.pick. These functions take multiple promises as argument and return a new promise which resolves once either of the provided promises have.
The promise starts making progress towards resolution as soon as it exists. You can pass the promise to async to ignore the value it resolves to as well as the resolution entirely. That is, async allows you to ignore promises you don’t care about. You can pass a promise to both/all/join/choose/pick to synchronise on the resolution.
About the earlier quotes:
There are no threads in Lwt, only promises.
There is no parallelism with Lwt, only concurrency.
Thank you for this resource! Part 2 OS Interactions clued me to the deceptively simple problem. I was using the stdlib read_line when I should have been using Lwt_io.(read_line stdin).
For posterity, here is the (refactored) working solution:
open Lwt.Syntax
open Lwt.Infix
let addr_from_string of_string addr_string =
match of_string addr_string with
| Ok addr -> addr
| Error `Msg e -> prerr_endline ("Failed to resolve " ^ addr_string ^ ": " ^ e); exit 2
let client () =
if Array.length Sys.argv < 3 then begin
prerr_endline "Usage: client <host> <src port> <dst port>";
exit 2;
end;
let addr_string = Sys.argv.(1) and
src_port = int_of_string Sys.argv.(2) and
dst_port = int_of_string Sys.argv.(3) in
let* sock = addr_from_string Ipaddr.V4.Prefix.of_string "127.0.0.1/32" |> Udpv4_socket.connect in
let callback ~src:_ ~dst:_ ~src_port:_ buf =
Cstruct.to_string buf |> Lwt_io.print in
Udpv4_socket.listen sock ~port:src_port callback;
let send () =
let dst = addr_from_string Ipaddr.V4.of_string addr_string in
let* line = Lwt_io.(read_line stdin) in
let buf = Cstruct.of_string (line ^ "\n") in
Udpv4_socket.write ~src_port ~dst ~dst_port sock buf
>>= function
| Ok () -> Lwt.pause ()
| Error `Sendto_failed -> prerr_endline "sendto failed"; exit 1
in
let rec loop () = send () >>= loop in
loop ()
let () = Lwt_main.run (client ())
One suggestion, I would recommend sticking with Lwt.Syntax throughout the entire codebase instead of using the older Lwt.Infix. You can also avoid global open which pollutes the entire scope, and use local open:
let client () =
if Array.length Sys.argv < 3 then begin
prerr_endline "Usage: client <host> <src port> <dst port>";
exit 2
end;
let addr_string = Sys.argv.(1) in
let src_port = int_of_string Sys.argv.(2) in
let dst_port = int_of_string Sys.argv.(3) in
let open Lwt.Syntax in (* opening only from the point we need it from *)
let* sock = addr_from_string Ipaddr.V4.Prefix.of_string "127.0.0.1/32" |> Udpv4_socket.connect in
let callback ~src:_ ~dst:_ ~src_port:_ buf = Cstruct.to_string buf |> Lwt_io.print in
Udpv4_socket.listen sock ~port:src_port callback;
let send () =
let dst = addr_from_string Ipaddr.V4.of_string addr_string in
let* line = Lwt_io.(read_line stdin) in
let buf = Cstruct.of_string (line ^ "\n") in
let* res = Udpv4_socket.write ~src_port ~dst ~dst_port sock buf in
match res with
| Ok () ->
Lwt.pause ()
| Error `Sendto_failed ->
prerr_endline "sendto failed";
exit 1
in
let rec loop () =
let* () = send () in
loop ()
in
loop ()