I wanted to try the Lwt_unix module for a simple client that reads data in a socket until there is nothing to read. Some told me that Lwt create non blocking sockets but with my code, it is still blocking:
open Lwt
open Unix
(* ocamlfind ocamlc -o lwt_socket_client -package lwt,lwt.unix,unix -linkpkg -g lwt_socket_client.ml *)
let host = Unix.inet_addr_loopback
let port = 6600
let create_socket () =
let sock = Lwt_unix.socket PF_INET SOCK_STREAM 0 in
Lwt_unix.set_blocking sock false;
sock
let s_read sock maxlen =
let str = Bytes.create maxlen in
let rec _read sock acc =
Lwt.ignore_result(Lwt_io.write_line Lwt_io.stdout "_read");
Lwt_unix.read sock str 0 maxlen >>= fun recvlen ->
Lwt.ignore_result(Lwt_io.write_line Lwt_io.stdout (string_of_int recvlen));
if recvlen = 0 then Lwt.return (acc)
else _read sock (acc ^ (String.sub str 0 recvlen))
in _read sock ""
let socket_read sock =
Lwt.ignore_result(Lwt_unix.connect sock @@ ADDR_INET(host, port));
s_read sock 1024 >>= fun answer ->
Lwt_io.write_line Lwt_io.stdout answer
let () =
let sock = create_socket () in
Lwt_main.run (socket_read sock)
If I try this example with in a term:
echo "totoche" | netcat -l 127.0.0.1 -p 6600
then the result is :
./lwt_socket_client
_read
8
_read
Which block until I hit Ctrl+c.
I have tried both with :
Lwt_unix.set_blocking sock false;
and
Lwt_unix.set_blocking sock true;
and of course without this line, but it is still blocking. What am I doing wrong?
For more informations, one of my previous question : OCaml non-blocking client socket
Conceptually,
Lwt_unix.readalways blocks the Lwt thread, but never blocks the whole process – unless the process is waiting for that Lwt thread, and there are no other Lwt threads to run.Lwt_unix.set_blockingdoes not affect this behavior. It just changes the settings on the underlying socket, and therefore the strategy used internally by Lwt to avoid blocking the process.So, as mentioned by @ThomasLeonard, the "idiomatic Lwt" way to do a non-blocking
read(from the perspective of the process) is simply to run additional Lwt threads concurrently with theLwt_unix.read.Concerning the specific code in the question, the underlying
readsystem call fails withEAGAINorEWOULDBLOCK(depending on the system) if the underlying socket is non-blocking, but no data is available – rather than succeeding with zero bytes read, which indicates the socket has been closed.Unix.readconverts this into an exceptionUnix.Unix_error Unix.EAGAIN(respectively,Unix.Unix_error Unix.EWOULDBLOCK).Lwt_unix.readretriesUnix.readin this case. So, you cannot (currently) directly respond to non-blocking reads that fail in this way if usingLwt_unix.read.If you do want/need this level of control on a socket created with
Lwt_unix, you can do this:EDIT: Also, as mentioned by @ThomasLeonard, some uses of
ignore_resultin your code should probably bee >>= fun () -> e'instead. This forces Lwt to wait foreto complete before runninge'. In particular, you should do this forLwt_unix.connect.