Skip to content

Commit

Permalink
fix(sendfile): handle EAGAIN on macOS non-blocking sockets (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
anmonteiro authored Sep 17, 2023
1 parent 3c44768 commit c2509e8
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 35 deletions.
2 changes: 1 addition & 1 deletion lib/posix.ml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ let sendfile
* `Gluten.Server.report_write_result` here given that we put
* bytes in the file descriptor off-band. `report_write_result`
* just tracks the internal Faraday buffers. *)
Logs.debug (fun m -> m "`sendfile` wrote %d bytes successfully" sent);
Logs.debug (fun m -> m "`sendfile` wrote %d bytes" sent);
Ok ()
| Error e ->
Writer.close raw_write_body;
Expand Down
133 changes: 107 additions & 26 deletions sendfile/sendfile.ml
Original file line number Diff line number Diff line change
@@ -1,3 +1,90 @@
exception Darwin_specific_unix_error of (Unix.error * int)

let () =
Callback.register_exception
"sendfile_exn_unix_error"
(Darwin_specific_unix_error (Unix.E2BIG, 0));
Printexc.register_printer (function
| Darwin_specific_unix_error (unix_error, len) ->
let msg =
(* Copied from
https://github.com/ocaml/ocaml/blob/trunk/otherlibs/unix/unix_unix.ml *)
match unix_error with
| E2BIG -> "E2BIG"
| EACCES -> "EACCES"
| EAGAIN -> "EAGAIN"
| EBADF -> "EBADF"
| EBUSY -> "EBUSY"
| ECHILD -> "ECHILD"
| EDEADLK -> "EDEADLK"
| EDOM -> "EDOM"
| EEXIST -> "EEXIST"
| EFAULT -> "EFAULT"
| EFBIG -> "EFBIG"
| EINTR -> "EINTR"
| EINVAL -> "EINVAL"
| EIO -> "EIO"
| EISDIR -> "EISDIR"
| EMFILE -> "EMFILE"
| EMLINK -> "EMLINK"
| ENAMETOOLONG -> "ENAMETOOLONG"
| ENFILE -> "ENFILE"
| ENODEV -> "ENODEV"
| ENOENT -> "ENOENT"
| ENOEXEC -> "ENOEXEC"
| ENOLCK -> "ENOLCK"
| ENOMEM -> "ENOMEM"
| ENOSPC -> "ENOSPC"
| ENOSYS -> "ENOSYS"
| ENOTDIR -> "ENOTDIR"
| ENOTEMPTY -> "ENOTEMPTY"
| ENOTTY -> "ENOTTY"
| ENXIO -> "ENXIO"
| EPERM -> "EPERM"
| EPIPE -> "EPIPE"
| ERANGE -> "ERANGE"
| EROFS -> "EROFS"
| ESPIPE -> "ESPIPE"
| ESRCH -> "ESRCH"
| EXDEV -> "EXDEV"
| EWOULDBLOCK -> "EWOULDBLOCK"
| EINPROGRESS -> "EINPROGRESS"
| EALREADY -> "EALREADY"
| ENOTSOCK -> "ENOTSOCK"
| EDESTADDRREQ -> "EDESTADDRREQ"
| EMSGSIZE -> "EMSGSIZE"
| EPROTOTYPE -> "EPROTOTYPE"
| ENOPROTOOPT -> "ENOPROTOOPT"
| EPROTONOSUPPORT -> "EPROTONOSUPPORT"
| ESOCKTNOSUPPORT -> "ESOCKTNOSUPPORT"
| EOPNOTSUPP -> "EOPNOTSUPP"
| EPFNOSUPPORT -> "EPFNOSUPPORT"
| EAFNOSUPPORT -> "EAFNOSUPPORT"
| EADDRINUSE -> "EADDRINUSE"
| EADDRNOTAVAIL -> "EADDRNOTAVAIL"
| ENETDOWN -> "ENETDOWN"
| ENETUNREACH -> "ENETUNREACH"
| ENETRESET -> "ENETRESET"
| ECONNABORTED -> "ECONNABORTED"
| ECONNRESET -> "ECONNRESET"
| ENOBUFS -> "ENOBUFS"
| EISCONN -> "EISCONN"
| ENOTCONN -> "ENOTCONN"
| ESHUTDOWN -> "ESHUTDOWN"
| ETOOMANYREFS -> "ETOOMANYREFS"
| ETIMEDOUT -> "ETIMEDOUT"
| ECONNREFUSED -> "ECONNREFUSED"
| EHOSTDOWN -> "EHOSTDOWN"
| EHOSTUNREACH -> "EHOSTUNREACH"
| ELOOP -> "ELOOP"
| EOVERFLOW -> "EOVERFLOW"
| EUNKNOWNERR x -> Printf.sprintf "EUNKNOWNERR %d" x
in

Some
(Format.asprintf "Sendfile(Unix_error): %s; remaining len: %d" msg len)
| _ -> None)

external sendfile :
src:Unix.file_descr
-> dst:Unix.file_descr
Expand All @@ -6,38 +93,32 @@ external sendfile :
-> int
= "ocaml_sendfile_sendfile_stub"

let sendfile_once_exn ?(off = 0) ?len ~src dst =
let len =
match len with Some len -> len | None -> (Unix.fstat src).st_size - off
in
sendfile ~src ~dst ~off ~len
let sendfile_once_exn ?(off = 0) ~len ~src dst = sendfile ~src ~dst ~off ~len

let sendfile_once ?(off = 0) ?len ~src dst =
try
let ret = sendfile_once_exn ~src ~off ?len dst in
Ok ret
with
let sendfile_once ?(off = 0) ~len ~src dst =
try Ok (sendfile_once_exn ~src ~off ~len dst) with
| Unix.Unix_error (unix_err, _, _msg) -> Error unix_err

let rec sendfile_exn ?(off = 0) ?len ~src dst =
let sendfile_exn ?(off = 0) ?len ~src dst =
let rec sendfile_exn ~off ~len ~src dst =
match sendfile_once_exn ~src ~off ~len dst with
| c when c = len -> len
| c -> sendfile_exn ~src ~off:(off + c) ~len:(len - c) dst
| exception Unix.Unix_error ((EINTR | EAGAIN), _, _) ->
sendfile_exn ~src ~off ~len dst
| exception Darwin_specific_unix_error ((EINTR | EAGAIN), sent) ->
(* Darwin systems signal the number of bytes partially sent on EINTR /
EAGAIN. *)
sendfile_exn ~src ~off:(off + sent) ~len:(len - sent) dst
in
let len =
match len with Some len -> len | None -> (Unix.fstat src).st_size - off
in
match sendfile_once_exn ~src ~off ~len dst with
| c when c = len -> len
| c -> sendfile_exn ~src ~off:(off + c) ~len:(len - c) dst
| exception Unix.Unix_error (Unix.EINTR, _, _) ->
(* From:
https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendfile.2.html
A signal interrupted sendfile() before it could be completed. If
specified, the number of bytes success-fully successfully fully sent will
be returned in *len. *)
sendfile_exn ~src ~off ~len dst
let _sent = sendfile_exn ~off ~len ~src dst in
(* If we're here, we sent the whole file correctly, so we return the total
length sent. *)
len

let sendfile ?off ?len ~src dst =
try
let ret = sendfile_exn ~src ?off ?len dst in
Ok ret
with
try Ok (sendfile_exn ~src ?off ?len dst) with
| Unix.Unix_error (unix_err, _, _msg) -> Error unix_err
10 changes: 4 additions & 6 deletions sendfile/sendfile.mli
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
val sendfile_once_exn :
?off:int (** Defaults to 0. *)
-> ?len:int
(** Defaults to length of data (file) associated with descriptor [fd]. *)
-> len:int
-> src:Unix.file_descr
-> Unix.file_descr
-> int
Expand All @@ -11,8 +10,7 @@ val sendfile_once_exn :

val sendfile_once :
?off:int (** Defaults to 0. *)
-> ?len:int
(** Defaults to length of data (file) associated with descriptor [fd]. *)
-> len:int
-> src:Unix.file_descr
-> Unix.file_descr
-> (int, Unix.error) result
Expand All @@ -27,8 +25,8 @@ val sendfile_exn :
-> src:Unix.file_descr
-> Unix.file_descr
-> int
(** Calls `sendfile(2)`, possibly repeatedly (if EINTR is returned) until the
entire file has been sent.
(** Calls `sendfile(2)`, possibly repeatedly (if EINTR / EAGAIN is returned)
until the entire file has been sent.
@raise Sys_error if `sendfile` is not supported by the platform. *)

Expand Down
37 changes: 35 additions & 2 deletions sendfile/sendfile_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,28 @@
#include <sys/sendfile.h>
#endif

#include <errno.h>

#define CAML_NAME_SPACE
#include <caml/alloc.h>
#include <caml/callback.h>
#include <caml/custom.h>
#include <caml/fail.h>
#include <caml/memory.h>
#include <caml/mlvalues.h>
#include <caml/threads.h>
#include <caml/unixsupport.h>

value sendfile_unix_error(int errcode, off_t len) {
CAMLparam0();
value err, res;
err = caml_unix_error_of_code(errcode);
res = caml_alloc_small(2, 0);
Field(res, 0) = err;
Field(res, 1) = Val_long(len);
CAMLreturn(res);
}

/*
* Sendfile has different signatures on macOS and Linux.
*/
Expand All @@ -42,8 +57,26 @@ CAMLprim value ocaml_sendfile_sendfile_stub(value v_fd, value v_sock,
ret = sendfile(Int_val(v_fd), Int_val(v_sock), offset, &len, NULL, 0);
caml_acquire_runtime_system();

if (ret == -1)
uerror("sendfile", Nothing);
/*
* For the macOS case we raise a custom exception with the remaining length
* to be sent, due to `sendfile` differences in the Apple platform.
*
* From
* https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendfile.2.html:
*
* EINTR: A signal interrupted sendfile() before it could be completed. If
* specified, the number of bytes successfully fully sent will
* be returned in *len.
*
* Similarly for EAGAIN:
* The socket is marked for non-blocking I/O and not all data was sent due to
* the socket buffer being full. If specified, the number of bytes
* successfully sent will be returned in *len.
*/
if (ret == -1) {
caml_raise_with_arg(*caml_named_value("sendfile_exn_unix_error"),
sendfile_unix_error(errno, len));
}

CAMLreturn(Val_long(len));
}
Expand Down

0 comments on commit c2509e8

Please sign in to comment.