From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS16276 79.137.64.0/18 X-Spam-Status: No, score=0.7 required=3.0 tests=BAYES_00,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_ZBI,RCVD_IN_XBL,RDNS_DYNAMIC,SPF_FAIL,SPF_HELO_FAIL shortcircuit=no autolearn=no autolearn_force=no version=3.4.0 Received: from 80x24.org (168.ip-79-137-81.eu [79.137.81.168]) by dcvr.yhbt.net (Postfix) with ESMTP id 741C31FC43 for ; Sat, 11 Mar 2017 01:00:19 +0000 (UTC) From: Eric Wong To: sleepy-penguin@bogomips.org Subject: [PATCH] implement linux_sendfile support Date: Sat, 11 Mar 2017 01:00:16 +0000 Message-Id: <20170311010016.13003-1-e@80x24.org> List-Id: This will also allow non-Linux users to use sendfile if it is not available. --- ext/sleepy_penguin/extconf.rb | 1 + ext/sleepy_penguin/init.c | 2 + ext/sleepy_penguin/sendfile.c | 120 ++++++++++++++++++++++++++++++++++++++++++ lib/sleepy_penguin.rb | 19 +++++++ test/test_sendfile.rb | 28 ++++++++++ 5 files changed, 170 insertions(+) create mode 100644 ext/sleepy_penguin/sendfile.c create mode 100644 test/test_sendfile.rb diff --git a/ext/sleepy_penguin/extconf.rb b/ext/sleepy_penguin/extconf.rb index b1a95c4..e07f5d2 100644 --- a/ext/sleepy_penguin/extconf.rb +++ b/ext/sleepy_penguin/extconf.rb @@ -6,6 +6,7 @@ if have_header('sys/event.h') end have_header('sys/mount.h') have_header('sys/eventfd.h') +have_header('sys/sendfile.h') # it's impossible to use signalfd reliably with Ruby since Ruby currently # manages # (and overrides) all signal handling diff --git a/ext/sleepy_penguin/init.c b/ext/sleepy_penguin/init.c index 0a1458b..8295073 100644 --- a/ext/sleepy_penguin/init.c +++ b/ext/sleepy_penguin/init.c @@ -54,6 +54,7 @@ void sleepy_penguin_init_signalfd(void); void sleepy_penguin_init_splice(void); void sleepy_penguin_init_cfr(void); +void sleepy_penguin_init_sendfile(void); static size_t l1_cache_line_size_detect(void) { @@ -132,4 +133,5 @@ void Init_sleepy_penguin_ext(void) sleepy_penguin_init_signalfd(); sleepy_penguin_init_splice(); sleepy_penguin_init_cfr(); + sleepy_penguin_init_sendfile(); } diff --git a/ext/sleepy_penguin/sendfile.c b/ext/sleepy_penguin/sendfile.c new file mode 100644 index 0000000..887dfb6 --- /dev/null +++ b/ext/sleepy_penguin/sendfile.c @@ -0,0 +1,120 @@ +#include "sleepy_penguin.h" +#include +#include +#include + +#if defined(HAVE_SYS_SENDFILE_H) && !defined(HAVE_BSD_SENDFILE) +# include +#endif + +#if defined(__linux__) && defined(HAVE_SENDFILE) +# define linux_sendfile(in_fd, out_fd, offset, count) \ + sendfile((in_fd),(out_fd),(offset),(count)) + +/* all good */ +#elif defined(HAVE_SENDFILE) && \ + (defined(__FreeBSD__) || defined(__DragonFly__)) +/* + * make BSD sendfile look like Linux for now... + * we can support SF_NODISKIO later + */ +static ssize_t linux_sendfile(int sockfd, int filefd, off_t *off, size_t count) +{ + off_t sbytes = 0; + off_t offset = off ? *off : lseek(filefd, 0, SEEK_CUR); + + int rc = sendfile(filefd, sockfd, offset, count, NULL, &sbytes, 0); + if (sbytes > 0) { + if (off) + *off += sbytes; + else + lseek(filefd, sbytes, SEEK_CUR); + return (ssize_t)sbytes; + } + + return (ssize_t)rc; +} +#else /* emulate sendfile using (read|pread) + write */ +static ssize_t pread_sendfile(int sockfd, int filefd, off_t *off, size_t count) +{ + size_t max_read = 16384; + void *buf; + ssize_t r; + ssize_t w; + + max_read = count > max_read ? max_read : count; + buf = xmalloc(max_read); + + do { + r = off ? pread(filefd, buf, max_read, *off) : + read(filefd, buf, max_read); + } while (r < 0 && errno == EINTR); + + if (r <= 0) { + int err = errno; + xfree(buf); + errno = err; + return r; + } + w = write(sockfd, buf, r); + if (w > 0 && off) + *off += w; + xfree(buf); + return w; +} +# define linux_sendfile(out_fd, in_fd, offset, count) \ + pread_sendfile((out_fd),(in_fd),(offset),(count)) +#endif + +struct sf_args { + int dst_fd; + int src_fd; + off_t *off; + size_t count; +}; + +static VALUE sym_wait_writable; + +static VALUE nogvl_sf(void *ptr) +{ + struct sf_args *a = ptr; + + return (VALUE)linux_sendfile(a->dst_fd, a->src_fd, a->off, a->count); +} + +static VALUE lsf(VALUE mod, VALUE dst, VALUE src, VALUE src_off, VALUE count) +{ + off_t off = 0; + struct sf_args a; + ssize_t bytes; + int retried = 0; + + a.off = NIL_P(src_off) ? NULL : (off = NUM2OFFT(src_off), &off); + a.count = NUM2SIZET(count); +again: + a.src_fd = rb_sp_fileno(src); + a.dst_fd = rb_sp_fileno(dst); + bytes = (ssize_t)rb_sp_fd_region(nogvl_sf, &a, a.dst_fd); + if (bytes < 0) { + switch (errno) { + case EAGAIN: + return sym_wait_writable; + case ENOMEM: + case ENOBUFS: + if (!retried) { + rb_gc(); + retried = 1; + goto again; + } + } + rb_sys_fail("sendfile"); + } + return SSIZET2NUM(bytes); +} + +void sleepy_penguin_init_sendfile(void) +{ + VALUE m = rb_define_module("SleepyPenguin"); + rb_define_singleton_method(m, "__lsf", lsf, 4); + sym_wait_writable = ID2SYM(rb_intern("wait_writable")); +} diff --git a/lib/sleepy_penguin.rb b/lib/sleepy_penguin.rb index 07c431b..5722f2a 100644 --- a/lib/sleepy_penguin.rb +++ b/lib/sleepy_penguin.rb @@ -19,4 +19,23 @@ end module SleepyPenguin require_relative 'sleepy_penguin/splice' if respond_to?(:__splice) require_relative 'sleepy_penguin/cfr' if respond_to?(:__cfr) + + # Copies +len+ bytes from +src+ to +dst+, where +src+ refers to + # an open, mmap(2)-able File and +dst+ refers to a Socket. + # An optional +offset+ keyword may be specified for the +src+ File. + # Using +offset+ will not adjust the offset of the underlying file + # handle itself; in other words: this allows concurrent threads to + # use linux_sendfile to write data from one open file to multiple + # sockets. + # + # Returns the number of bytes written on success, or :wait_writable + # if the +dst+ Socket is non-blocking and the operation would block. + # A return value of zero bytes indicates EOF is reached on the +src+ + # file. + # + # Newer OSes may be more flexible in whether or not +dst+ or +src+ + # is a regular file or socket, respectively. + def self.linux_sendfile(dst, src, len, offset: nil) + __lsf(dst, src, offset, len) + end end diff --git a/test/test_sendfile.rb b/test/test_sendfile.rb new file mode 100644 index 0000000..a25b711 --- /dev/null +++ b/test/test_sendfile.rb @@ -0,0 +1,28 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'tempfile' +require 'socket' +$-w = true +require 'sleepy_penguin' + +class TestSendfile < Test::Unit::TestCase + def test_linux_sendfile + rd, wr = UNIXSocket.pair + size = 5 + src = Tempfile.new('ruby_sf_src') + assert_equal 0, SleepyPenguin.linux_sendfile(wr, src, size) + str = 'abcde'.freeze + assert_equal str.bytesize, src.syswrite(str) + assert_equal 0, SleepyPenguin.linux_sendfile(wr, src, size) + src.sysseek(0, IO::SEEK_SET) + assert_equal str.bytesize, + SleepyPenguin.linux_sendfile(wr, src, size, offset: 0) + assert_equal str, rd.read(size) + assert_equal 0, src.sysseek(0, IO::SEEK_CUR), 'handle offset not changed' + assert_equal 3, SleepyPenguin.linux_sendfile(wr, src, 3) + assert_equal 3, src.sysseek(0, IO::SEEK_CUR), 'handle offset changed' + ensure + [ rd, wr ].compact.each(&:close) + src.close! if src + end +end -- EW