guile-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] Bindings for ‘sendfile’


From: Ludovic Courtès
Subject: Re: [PATCH] Bindings for ‘sendfile’
Date: Sun, 07 Apr 2013 21:53:26 +0200
User-agent: Gnus/5.130005 (Ma Gnus v0.5) Emacs/24.3 (gnu/linux)

There were sporadic failures of the sendfile/pipe tests on Hydra.  I
managed to reproduce them and noticed that sometimes sendfile(2) would
return 64KiB, which is much less than what we asked for.

Although the man page doesn’t mention it, this can happen when writing
to a slow or limited device, like a pipe (pretty much like write(2)).

I was hesitant whether to stick to libc behavior (return the number of
bytes sent and let the user handle it), or to handle it directly in our
‘sendfile’ procedure.  After discussion with Mark on IRC, I became
convinced that the latter is preferable, so here’s the patch.

I’ll commit it shortly if there are no objections.

Ludo’.

>From 42459c382ca512c927b6228c1557afe8e10aedae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= <address@hidden>
Date: Sun, 7 Apr 2013 21:47:48 +0200
Subject: [PATCH] Change `sendfile' to loop until everything has been sent.

* libguile/filesys.c (scm_sendfile)[HAVE_SYS_SENDFILE_H &&
  HAVE_SENDFILE]: Compare RESULT with C_COUNT.  Loop until C_COUNT bytes
  have been sent.  Return SCM_UNSPECIFIED.
* doc/ref/posix.texi (File System): Update the description.  Explain the
  new semantics.
---
 doc/ref/posix.texi |   11 +++++++++--
 libguile/filesys.c |   36 +++++++++++++++++++++++++++++-------
 2 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/doc/ref/posix.texi b/doc/ref/posix.texi
index 45f320f..13f3a48 100644
--- a/doc/ref/posix.texi
+++ b/doc/ref/posix.texi
@@ -806,9 +806,10 @@ The return value is unspecified.
 @deffn {Scheme Procedure} sendfile out in count [offset]
 @deffnx {C Function} scm_sendfile (out, in, count, offset)
 Send @var{count} bytes from @var{in} to @var{out}, both of which
-are either open file ports or file descriptors.  When
+must be either open file ports or file descriptors.  When
 @var{offset} is omitted, start reading from @var{in}'s current
-position; otherwise, start reading at @var{offset}.
+position; otherwise, start reading at @var{offset}.  The return
+value is unspecified.
 
 When @var{in} is a port, it is often preferable to specify @var{offset},
 because @var{in}'s offset as a port may be different from the offset of
@@ -824,6 +825,12 @@ In some cases, the @code{sendfile} libc function may return
 @code{EINVAL} or @code{ENOSYS}.  In that case, Guile's @code{sendfile}
 procedure automatically falls back to doing a series of @code{read} and
 @code{write} calls.
+
+In other cases, the libc function may send fewer bytes than
address@hidden instance because @var{out} is a slow or limited
+device, such as a pipe.  When that happens, Guile's @code{sendfile}
+automatically retries until exactly @var{count} bytes were sent or an
+error occurs.
 @end deffn
 
 @findex rename
diff --git a/libguile/filesys.c b/libguile/filesys.c
index d318ae7..069c7ad 100644
--- a/libguile/filesys.c
+++ b/libguile/filesys.c
@@ -1111,9 +1111,10 @@ SCM_DEFINE (scm_copy_file, "copy-file", 2, 0, 0,
 SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
            (SCM out, SCM in, SCM count, SCM offset),
            "Send @var{count} bytes from @var{in} to @var{out}, both of which "
-           "are either open file ports or file descriptors.  When "
+           "must be either open file ports or file descriptors.  When "
            "@var{offset} is omitted, start reading from @var{in}'s current "
-           "position; otherwise, start reading at @var{offset}.")
+           "position; otherwise, start reading at @var{offset}.  The return "
+           "value is unspecified.")
 #define FUNC_NAME s_scm_sendfile
 {
 #define VALIDATE_FD_OR_PORT(cvar, svar, pos)   \
@@ -1139,9 +1140,31 @@ SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
 #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE
   /* The Linux-style sendfile(2), which is different from the BSD-style.  */
 
-  result = sendfile_or_sendfile64 (out_fd, in_fd,
-                                  SCM_UNBNDP (offset) ? NULL : &c_offset,
-                                  c_count);
+  {
+    size_t total;
+    off_t *offset_ptr;
+
+    offset_ptr = SCM_UNBNDP (offset) ? NULL : &c_offset;
+
+    /* On Linux, when OUT_FD is a file, everything is transferred at once and
+       RESULT == C_COUNT.  However, when OUT_FD is a pipe or other "slow"
+       device, fewer bytes may be transferred, hence the loop.  */
+    for (total = 0, result = 0; total < c_count && result >= 0; )
+      {
+       result = sendfile_or_sendfile64 (out_fd, in_fd, offset_ptr,
+                                        c_count - total);
+       if (result > 0)
+         {
+           total += result;
+           if (offset_ptr == NULL)
+             offset_ptr = &c_offset;
+           *offset_ptr = total;
+         }
+       else if (result < 0 && errno == EINTR)
+         /* Keep going.  */
+         result = 0;
+      }
+  }
 
   /* Quoting the Linux man page: "In Linux kernels before 2.6.33, out_fd
      must refer to a socket.  Since Linux 2.6.33 it can be any file."
@@ -1183,10 +1206,9 @@ SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
        result += obtained;
       }
 
-    return scm_from_size_t (result);
   }
 
-  return scm_from_ssize_t (result);
+  return SCM_UNSPECIFIED;
 
 #undef VALIDATE_FD_OR_PORT
 }
-- 
1.7.10.4


reply via email to

[Prev in Thread] Current Thread [Next in Thread]