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.read
always 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_blocking
does 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 the Lwt_unix.read
.
Concerning the specific code in the question, the underlying read
system call fails with EAGAIN
or EWOULDBLOCK
(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.read
converts this into an exception Unix.Unix_error Unix.EAGAIN
(respectively, Unix.Unix_error Unix.EWOULDBLOCK
). Lwt_unix.read
retries Unix.read
in this case. So, you cannot (currently) directly respond to non-blocking reads that fail in this way if using Lwt_unix.read
.
If you do want/need this level of control on a socket created with Lwt_unix
, you can do this:
Lwt_unix.set_blocking sock false;
try
Unix.read (Lwt_unix.unix_file_descr sock) str 0 maxlen
with Unix.Unix_error (Unix.EAGAIN | Unix.EWOULDBLOCK) ->
(* Handle no data available. *)
EDIT: Also, as mentioned by @ThomasLeonard, some uses of ignore_result
in your code should probably be e >>= fun () -> e'
instead. This forces Lwt to wait for e
to complete before running e'
. In particular, you should do this for Lwt_unix.connect
.