io_splice RubyGem user+dev discussion/patches/pulls/bugs/help
 help / color / mirror / code / Atom feed
From: Eric Wong <e@80x24.org>
To: sleepy-penguin@bogomips.org
Cc: ruby-io-splice@bogomips.org, Eric Wong <e@80x24.org>
Subject: [PATCH 2/2] implement copy_file_range support for Linux 4.5+
Date: Wed, 16 Mar 2016 03:13:40 +0000	[thread overview]
Message-ID: <20160316031340.7164-3-e@80x24.org> (raw)
In-Reply-To: <20160316031340.7164-1-e@80x24.org>

Under Linux 4.5, this allows for efficient copies and
is similar to the splice and sendfile system calls.
---
 ext/sleepy_penguin/cfr.c      | 68 +++++++++++++++++++++++++++++++++++++++++++
 ext/sleepy_penguin/extconf.rb |  1 +
 ext/sleepy_penguin/init.c     |  2 ++
 ext/sleepy_penguin/sp_copy.h  | 50 +++++++++++++++++++++++++++++++
 ext/sleepy_penguin/splice.c   | 54 ++--------------------------------
 test/test_cfr.rb              | 29 ++++++++++++++++++
 6 files changed, 153 insertions(+), 51 deletions(-)
 create mode 100644 ext/sleepy_penguin/cfr.c
 create mode 100644 ext/sleepy_penguin/sp_copy.h
 create mode 100644 test/test_cfr.rb

diff --git a/ext/sleepy_penguin/cfr.c b/ext/sleepy_penguin/cfr.c
new file mode 100644
index 0000000..ea17f82
--- /dev/null
+++ b/ext/sleepy_penguin/cfr.c
@@ -0,0 +1,68 @@
+#include "sleepy_penguin.h"
+#include "sp_copy.h"
+#include <unistd.h>
+
+#ifndef HAVE_COPY_FILE_RANGE
+#  include <sys/syscall.h>
+#  if !defined(__NR_copy_file_range) && \
+	(defined(__x86_64__) || defined(__i386__))
+#    define __NR_copy_file_range 285
+#  endif /* __NR_copy_file_range */
+#endif
+
+#ifdef __NR_copy_file_range
+static ssize_t my_cfr(int fd_in, off_t *off_in, int fd_out, off_t *off_out,
+		       size_t len, unsigned int flags)
+{
+	long n = syscall(__NR_copy_file_range,
+			fd_in, off_in, fd_out, off_out, len, flags);
+
+	return (ssize_t)n;
+}
+#  define copy_file_range(fd_in,off_in,fd_out,off_out,len,flags) \
+		my_cfr((fd_in),(off_in),(fd_out),(off_out),(len),(flags))
+#endif
+
+static void *nogvl_cfr(void *ptr)
+{
+	struct copy_args *a = ptr;
+
+	return (void *)copy_file_range(a->fd_in, a->off_in,
+				a->fd_out, a->off_out, a->len, a->flags);
+}
+
+static VALUE rb_cfr(int argc, VALUE *argv, VALUE mod)
+{
+	off_t i, o;
+	VALUE io_in, off_in, io_out, off_out, len, flags;
+	ssize_t bytes;
+	struct copy_args a;
+
+	rb_scan_args(argc, argv, "51",
+	             &io_in, &off_in, &io_out, &off_out, &len, &flags);
+
+	a.off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i);
+	a.off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o);
+	a.len = NUM2SIZET(len);
+	a.flags = NIL_P(flags) ? 0 : NUM2UINT(flags);
+
+again:
+	a.fd_in = rb_sp_fileno(io_in);
+	a.fd_out = rb_sp_fileno(io_out);
+	bytes = (ssize_t)IO_RUN(nogvl_cfr, &a);
+	if (bytes < 0) {
+		if (errno == EINTR)
+			goto again;
+		rb_sys_fail("copy_file_range");
+	} else if (bytes == 0) {
+		rb_eof_error();
+	}
+	return SSIZET2NUM(bytes);
+}
+
+void sleepy_penguin_init_cfr(void)
+{
+	VALUE mod = rb_define_module("SleepyPenguin");
+
+	rb_define_singleton_method(mod, "copy_file_range", rb_cfr, -1);
+}
diff --git a/ext/sleepy_penguin/extconf.rb b/ext/sleepy_penguin/extconf.rb
index 46d1059..53a2810 100644
--- a/ext/sleepy_penguin/extconf.rb
+++ b/ext/sleepy_penguin/extconf.rb
@@ -19,6 +19,7 @@
 end
 have_type('clockid_t', 'time.h')
 have_func('clock_gettime', 'time.h')
+have_func('copy_file_range', 'unistd.h')
 have_func('epoll_create1', %w(sys/epoll.h))
 have_func('inotify_init1', %w(sys/inotify.h))
 have_func('splice', %w(fcntl.h))
diff --git a/ext/sleepy_penguin/init.c b/ext/sleepy_penguin/init.c
index 93e8092..0a1458b 100644
--- a/ext/sleepy_penguin/init.c
+++ b/ext/sleepy_penguin/init.c
@@ -53,6 +53,7 @@ void sleepy_penguin_init_signalfd(void);
 #endif
 
 void sleepy_penguin_init_splice(void);
+void sleepy_penguin_init_cfr(void);
 
 static size_t l1_cache_line_size_detect(void)
 {
@@ -130,4 +131,5 @@ void Init_sleepy_penguin_ext(void)
 	sleepy_penguin_init_inotify();
 	sleepy_penguin_init_signalfd();
 	sleepy_penguin_init_splice();
+	sleepy_penguin_init_cfr();
 }
diff --git a/ext/sleepy_penguin/sp_copy.h b/ext/sleepy_penguin/sp_copy.h
new file mode 100644
index 0000000..83b9554
--- /dev/null
+++ b/ext/sleepy_penguin/sp_copy.h
@@ -0,0 +1,50 @@
+/* common splice and copy_file_range-related definitions */
+
+#ifndef SSIZET2NUM
+#  define SSIZET2NUM(x) LONG2NUM(x)
+#endif
+#ifndef NUM2SIZET
+#  define NUM2SIZET(x) NUM2ULONG(x)
+#endif
+
+#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && defined(HAVE_RUBY_THREAD_H)
+/* Ruby 2.0+ */
+#  include <ruby/thread.h>
+#  define WITHOUT_GVL(fn,a,ubf,b) \
+        rb_thread_call_without_gvl((fn),(a),(ubf),(b))
+#elif defined(HAVE_RB_THREAD_BLOCKING_REGION)
+typedef VALUE (*my_blocking_fn_t)(void*);
+#  define WITHOUT_GVL(fn,a,ubf,b) \
+	rb_thread_blocking_region((my_blocking_fn_t)(fn),(a),(ubf),(b))
+
+#else /* Ruby 1.8 */
+/* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
+#  include <rubysig.h>
+#  define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
+typedef void rb_unblock_function_t(void *);
+typedef void * rb_blocking_function_t(void *);
+static void * WITHOUT_GVL(rb_blocking_function_t *func, void *data1,
+			rb_unblock_function_t *ubf, void *data2)
+{
+	void *rv;
+
+	assert(RUBY_UBF_IO == ubf && "RUBY_UBF_IO required for emulation");
+
+	TRAP_BEG;
+	rv = func(data1);
+	TRAP_END;
+
+	return rv;
+}
+#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
+
+#define IO_RUN(fn,data) WITHOUT_GVL((fn),(data),RUBY_UBF_IO,0)
+
+struct copy_args {
+	int fd_in;
+	int fd_out;
+	off_t *off_in;
+	off_t *off_out;
+	size_t len;
+	unsigned flags;
+};
diff --git a/ext/sleepy_penguin/splice.c b/ext/sleepy_penguin/splice.c
index d2f9206..2f901a8 100644
--- a/ext/sleepy_penguin/splice.c
+++ b/ext/sleepy_penguin/splice.c
@@ -1,4 +1,5 @@
 #include "sleepy_penguin.h"
+#include "sp_copy.h"
 #ifdef HAVE_SPLICE
 #include <errno.h>
 #include <fcntl.h>
@@ -18,13 +19,6 @@ static VALUE sym_EAGAIN;
 #  define F_GETPIPE_SZ    (F_LINUX_SPECIFIC_BASE + 8)
 #endif
 
-#ifndef SSIZET2NUM
-#  define SSIZET2NUM(x) LONG2NUM(x)
-#endif
-#ifndef NUM2SIZET
-#  define NUM2SIZET(x) NUM2ULONG(x)
-#endif
-
 static int check_fileno(VALUE io)
 {
 	int saved_errno = errno;
@@ -33,51 +27,9 @@ static int check_fileno(VALUE io)
 	return fd;
 }
 
-#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && defined(HAVE_RUBY_THREAD_H)
-/* Ruby 2.0+ */
-#  include <ruby/thread.h>
-#  define WITHOUT_GVL(fn,a,ubf,b) \
-        rb_thread_call_without_gvl((fn),(a),(ubf),(b))
-#elif defined(HAVE_RB_THREAD_BLOCKING_REGION)
-typedef VALUE (*my_blocking_fn_t)(void*);
-#  define WITHOUT_GVL(fn,a,ubf,b) \
-	rb_thread_blocking_region((my_blocking_fn_t)(fn),(a),(ubf),(b))
-
-#else /* Ruby 1.8 */
-/* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
-#  include <rubysig.h>
-#  define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
-typedef void rb_unblock_function_t(void *);
-typedef void * rb_blocking_function_t(void *);
-static void * WITHOUT_GVL(rb_blocking_function_t *func, void *data1,
-			rb_unblock_function_t *ubf, void *data2)
-{
-	void *rv;
-
-	assert(RUBY_UBF_IO == ubf && "RUBY_UBF_IO required for emulation");
-
-	TRAP_BEG;
-	rv = func(data1);
-	TRAP_END;
-
-	return rv;
-}
-#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
-
-#define IO_RUN(fn,data) WITHOUT_GVL((fn),(data),RUBY_UBF_IO,0)
-
-struct splice_args {
-	int fd_in;
-	int fd_out;
-	off_t *off_in;
-	off_t *off_out;
-	size_t len;
-	unsigned flags;
-};
-
 static void *nogvl_splice(void *ptr)
 {
-	struct splice_args *a = ptr;
+	struct copy_args *a = ptr;
 
 	return (void *)splice(a->fd_in, a->off_in, a->fd_out, a->off_out,
 	                     a->len, a->flags);
@@ -87,7 +39,7 @@ static ssize_t do_splice(int argc, VALUE *argv, unsigned dflags)
 {
 	off_t i = 0, o = 0;
 	VALUE io_in, off_in, io_out, off_out, len, flags;
-	struct splice_args a;
+	struct copy_args a;
 	ssize_t bytes;
 	ssize_t total = 0;
 
diff --git a/test/test_cfr.rb b/test/test_cfr.rb
new file mode 100644
index 0000000..3483c5a
--- /dev/null
+++ b/test/test_cfr.rb
@@ -0,0 +1,29 @@
+# -*- encoding: binary -*-
+require 'test/unit'
+require 'tempfile'
+$-w = true
+require 'sleepy_penguin'
+
+class TestCfr < Test::Unit::TestCase
+  def test_copy_file_range
+    str = 'abcde'
+    size = 5
+    src = Tempfile.new('ruby_cfr_src')
+    dst = Tempfile.new('ruby_cfr_dst')
+    assert_equal 5, src.syswrite(str)
+    src.sysseek(0)
+    begin
+      nr = SleepyPenguin.copy_file_range(src, nil, dst, nil, size, 0)
+    rescue Errno::EINVAL
+      warn 'copy_file_range not supported (requires Linux 4.5+)'
+      warn "We have: #{`uname -a`}"
+      return
+    end
+    assert_equal nr, 5
+    dst.sysseek(0)
+    assert_equal str, dst.sysread(5)
+  ensure
+    dst.close!
+    src.close!
+  end
+end if SleepyPenguin.respond_to?(:copy_file_range)
-- 
EW


      parent reply	other threads:[~2016-03-16  3:13 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-03-16  3:13 [sleepy_penguin PATCH 0/2] splice/tee/copy_file_range support Eric Wong
2016-03-16  3:13 ` [PATCH 1/2] support the splice(2) and tee(2) syscalls Eric Wong
2017-01-02  2:31   ` Eric Wong
2016-03-16  3:13 ` Eric Wong [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://yhbt.net/ruby_io_splice/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20160316031340.7164-3-e@80x24.org \
    --to=e@80x24.org \
    --cc=ruby-io-splice@bogomips.org \
    --cc=sleepy-penguin@bogomips.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://yhbt.net/ruby_io_splice.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).