ocamllwt

OCaml: Lwt and non blocking socket


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


Solution

  • 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.