sleepy_penguin 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.document +1 -0
  3. data/.olddoc.yml +3 -4
  4. data/GIT-VERSION-GEN +1 -1
  5. data/LICENSE +3 -3
  6. data/README +7 -4
  7. data/TODO +1 -0
  8. data/ext/sleepy_penguin/cfr.c +62 -0
  9. data/ext/sleepy_penguin/epoll.c +34 -24
  10. data/ext/sleepy_penguin/eventfd.c +6 -5
  11. data/ext/sleepy_penguin/extconf.rb +6 -0
  12. data/ext/sleepy_penguin/init.c +83 -12
  13. data/ext/sleepy_penguin/inotify.c +48 -36
  14. data/ext/sleepy_penguin/kqueue.c +22 -21
  15. data/ext/sleepy_penguin/sendfile.c +120 -0
  16. data/ext/sleepy_penguin/sleepy_penguin.h +15 -28
  17. data/ext/sleepy_penguin/sp_copy.h +33 -0
  18. data/ext/sleepy_penguin/splice.c +174 -0
  19. data/ext/sleepy_penguin/timerfd.c +1 -5
  20. data/ext/sleepy_penguin/util.c +12 -0
  21. data/lib/sleepy_penguin.rb +28 -0
  22. data/lib/sleepy_penguin/cfr.rb +29 -0
  23. data/lib/sleepy_penguin/epoll.rb +13 -10
  24. data/lib/sleepy_penguin/kqueue.rb +6 -6
  25. data/lib/sleepy_penguin/sp.rb +1 -1
  26. data/lib/sleepy_penguin/splice.rb +125 -0
  27. data/pkg.mk +5 -12
  28. data/sleepy_penguin.gemspec +13 -15
  29. data/test/helper.rb +2 -7
  30. data/test/test_cfr.rb +35 -0
  31. data/test/test_constants.rb +2 -4
  32. data/test/test_epoll.rb +35 -6
  33. data/test/test_epoll_gc.rb +2 -5
  34. data/test/test_epoll_io.rb +3 -6
  35. data/test/test_epoll_optimizations.rb +2 -2
  36. data/test/test_eventfd.rb +2 -5
  37. data/test/test_inotify.rb +2 -4
  38. data/test/test_kqueue.rb +35 -7
  39. data/test/test_kqueue_io.rb +2 -5
  40. data/test/test_pipesize.rb +22 -0
  41. data/test/test_sendfile.rb +26 -0
  42. data/test/test_splice.rb +250 -0
  43. data/test/test_splice_eintr.rb +31 -0
  44. data/test/test_timerfd.rb +2 -5
  45. metadata +27 -34
  46. data/lib/sleepy_penguin/epoll/io.rb +0 -28
  47. data/lib/sleepy_penguin/kqueue/io.rb +0 -30
@@ -0,0 +1,174 @@
1
+ #include "sleepy_penguin.h"
2
+ #include "sp_copy.h"
3
+ #ifdef HAVE_SPLICE
4
+ #include <errno.h>
5
+ #include <fcntl.h>
6
+ #include <assert.h>
7
+ #include <sys/uio.h>
8
+ #include <unistd.h>
9
+
10
+ static VALUE sym_EAGAIN;
11
+
12
+ #ifndef F_LINUX_SPECIFIC_BASE
13
+ # define F_LINUX_SPECIFIC_BASE 1024
14
+ #endif
15
+
16
+ #ifndef F_GETPIPE_SZ
17
+ # define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
18
+ # define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8)
19
+ #endif
20
+
21
+ static int check_fileno(VALUE io)
22
+ {
23
+ int saved_errno = errno;
24
+ int fd = rb_sp_fileno(io);
25
+ errno = saved_errno;
26
+ return fd;
27
+ }
28
+
29
+ static void *nogvl_splice(void *ptr)
30
+ {
31
+ struct copy_args *a = ptr;
32
+
33
+ return (void *)splice(a->fd_in, a->off_in, a->fd_out, a->off_out,
34
+ a->len, a->flags);
35
+ }
36
+
37
+ /* :nodoc: */
38
+ static VALUE my_splice(VALUE mod, VALUE io_in, VALUE off_in,
39
+ VALUE io_out, VALUE off_out,
40
+ VALUE len, VALUE flags)
41
+ {
42
+ off_t i = 0, o = 0;
43
+ struct copy_args a;
44
+ ssize_t bytes;
45
+
46
+ a.off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i);
47
+ a.off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o);
48
+ a.len = NUM2SIZET(len);
49
+ a.flags = NUM2UINT(flags);
50
+
51
+ for (;;) {
52
+ a.fd_in = check_fileno(io_in);
53
+ a.fd_out = check_fileno(io_out);
54
+ bytes = (ssize_t)IO_RUN(nogvl_splice, &a);
55
+ if (bytes == 0) return Qnil;
56
+ if (bytes < 0) {
57
+ switch (errno) {
58
+ case EINTR: continue;
59
+ case EAGAIN: return sym_EAGAIN;
60
+ default: rb_sys_fail("splice");
61
+ }
62
+ }
63
+ return SSIZET2NUM(bytes);
64
+ }
65
+ }
66
+
67
+ struct tee_args {
68
+ int fd_in;
69
+ int fd_out;
70
+ size_t len;
71
+ unsigned flags;
72
+ };
73
+
74
+ /* runs without GVL */
75
+ static void *nogvl_tee(void *ptr)
76
+ {
77
+ struct tee_args *a = ptr;
78
+
79
+ return (void *)tee(a->fd_in, a->fd_out, a->len, a->flags);
80
+ }
81
+
82
+ /* :nodoc: */
83
+ static VALUE my_tee(VALUE mod, VALUE io_in, VALUE io_out,
84
+ VALUE len, VALUE flags)
85
+ {
86
+ struct tee_args a;
87
+ ssize_t bytes;
88
+
89
+ a.len = (size_t)NUM2SIZET(len);
90
+ a.flags = NUM2UINT(flags);
91
+
92
+ for (;;) {
93
+ a.fd_in = check_fileno(io_in);
94
+ a.fd_out = check_fileno(io_out);
95
+ bytes = (ssize_t)IO_RUN(nogvl_tee, &a);
96
+ if (bytes == 0) return Qnil;
97
+ if (bytes < 0) {
98
+ switch (errno) {
99
+ case EINTR: continue;
100
+ case EAGAIN: return sym_EAGAIN;
101
+ default: rb_sys_fail("tee");
102
+ }
103
+ }
104
+ return SSIZET2NUM(bytes);
105
+ }
106
+ }
107
+
108
+ void sleepy_penguin_init_splice(void)
109
+ {
110
+ VALUE mod = rb_define_module("SleepyPenguin");
111
+ rb_define_singleton_method(mod, "__splice", my_splice, 6);
112
+ rb_define_singleton_method(mod, "__tee", my_tee, 4);
113
+
114
+ /*
115
+ * Attempt to move pages instead of copying. This is only a hint
116
+ * and support for it was removed in Linux 2.6.21. It will be
117
+ * re-added for FUSE filesystems only in Linux 2.6.35.
118
+ */
119
+ rb_define_const(mod, "F_MOVE", UINT2NUM(SPLICE_F_MOVE));
120
+
121
+ /*
122
+ * Do not block on pipe I/O. This flag only affects the pipe(s)
123
+ * being spliced from/to and has no effect on the non-pipe
124
+ * descriptor (which requires non-blocking operation to be set
125
+ * explicitly).
126
+ *
127
+ * The non-blocking flag (O_NONBLOCK) on the pipe descriptors
128
+ * themselves are ignored by this family of functions, and
129
+ * using this flag is the only way to get non-blocking operation
130
+ * out of them.
131
+ *
132
+ * It is highly recommended this flag be set
133
+ * (or SleepyPenguin.trysplice used)
134
+ * whenever splicing from a socket into a pipe unless there is
135
+ * another (native) thread or process doing a blocking read on that
136
+ * pipe. Otherwise it is possible to block a single-threaded process
137
+ * if the socket buffers are larger than the pipe buffers.
138
+ */
139
+ rb_define_const(mod, "F_NONBLOCK", UINT2NUM(SPLICE_F_NONBLOCK));
140
+
141
+ /*
142
+ * Indicate that there may be more data coming into the outbound
143
+ * descriptor. This can allow the kernel to avoid sending partial
144
+ * frames from sockets. Currently only used with splice.
145
+ */
146
+ rb_define_const(mod, "F_MORE", UINT2NUM(SPLICE_F_MORE));
147
+
148
+ /*
149
+ * fcntl() command constant used to return the size of a pipe.
150
+ * This constant is only defined when running Linux 2.6.35
151
+ * or later.
152
+ *
153
+ * require 'fcntl'
154
+ * r, w = IO.pipe
155
+ * r.fcntl(SleepyPenguin::F_GETPIPE_SZ) => Integer
156
+ */
157
+ rb_define_const(mod, "F_GETPIPE_SZ", UINT2NUM(F_GETPIPE_SZ));
158
+
159
+ /*
160
+ * fcntl() command constant used to set the size of a pipe.
161
+ * This constant is only defined when running Linux 2.6.35
162
+ * or later.
163
+ *
164
+ * call-seq:
165
+ *
166
+ * require 'fcntl'
167
+ * r, w = IO.pipe
168
+ * r.fcntl(SleepyPenguin::F_SETPIPE_SZ, 131072)
169
+ */
170
+ rb_define_const(mod, "F_SETPIPE_SZ", UINT2NUM(F_SETPIPE_SZ));
171
+
172
+ sym_EAGAIN = ID2SYM(rb_intern("EAGAIN"));
173
+ }
174
+ #endif
@@ -31,10 +31,8 @@ static VALUE s_new(int argc, VALUE *argv, VALUE klass)
31
31
 
32
32
  fd = timerfd_create(clockid, flags);
33
33
  if (fd < 0) {
34
- if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
35
- rb_gc();
34
+ if (rb_sp_gc_for_fd(errno))
36
35
  fd = timerfd_create(clockid, flags);
37
- }
38
36
  if (fd < 0)
39
37
  rb_sys_fail("timerfd_create");
40
38
  }
@@ -122,8 +120,6 @@ static VALUE expirations(int argc, VALUE *argv, VALUE self)
122
120
  rb_scan_args(argc, argv, "01", &nonblock);
123
121
  if (RTEST(nonblock))
124
122
  rb_sp_set_nonblock(fd);
125
- else
126
- blocking_io_prepare(fd);
127
123
  retry:
128
124
  r = (ssize_t)rb_sp_fd_region(tfd_read, &buf, fd);
129
125
  if (r < 0) {
@@ -118,6 +118,9 @@ int rb_sp_fileno(VALUE io)
118
118
  {
119
119
  rb_io_t *fptr;
120
120
 
121
+ if (RB_TYPE_P(io, T_FIXNUM))
122
+ return FIX2INT(io);
123
+
121
124
  io = rb_io_get_io(io);
122
125
  GetOpenFile(io, fptr);
123
126
  return FPTR_TO_FD(fptr);
@@ -153,3 +156,12 @@ int rb_sp_wait(rb_sp_waitfn waiter, VALUE obj, int *fd)
153
156
  *fd = rb_sp_fileno(obj);
154
157
  return rc;
155
158
  }
159
+
160
+ int rb_sp_gc_for_fd(int err)
161
+ {
162
+ if (err == EMFILE || err == ENFILE || err == ENOMEM) {
163
+ rb_gc();
164
+ return 1;
165
+ }
166
+ return 0;
167
+ }
@@ -15,3 +15,31 @@ if defined?(SleepyPenguin::Inotify) &&
15
15
  # :startdoc
16
16
  end
17
17
  end
18
+
19
+ module SleepyPenguin
20
+ require_relative 'sleepy_penguin/splice' if respond_to?(:__splice)
21
+ require_relative 'sleepy_penguin/cfr' if respond_to?(:__cfr)
22
+ require_relative 'sleepy_penguin/epoll' if const_defined?(:Epoll)
23
+ require_relative 'sleepy_penguin/kqueue' if const_defined?(:Kqueue)
24
+
25
+ # Copies +len+ bytes from +src+ to +dst+, where +src+ refers to
26
+ # an open, mmap(2)-able File and +dst+ refers to a Socket.
27
+ # An optional +offset+ keyword may be specified for the +src+ File.
28
+ # Using +offset+ will not adjust the offset of the underlying file
29
+ # handle itself; in other words: this allows concurrent threads to
30
+ # use linux_sendfile to write data from one open file to multiple
31
+ # sockets.
32
+ #
33
+ # Returns the number of bytes written on success, or :wait_writable
34
+ # if the +dst+ Socket is non-blocking and the operation would block.
35
+ # A return value of zero bytes indicates EOF is reached on the +src+
36
+ # file.
37
+ #
38
+ # Newer OSes may be more flexible in whether or not +dst+ or +src+
39
+ # is a regular file or socket, respectively.
40
+ #
41
+ # This method was added in sleepy_penguin 3.5.0.
42
+ def self.linux_sendfile(dst, src, len, offset: nil)
43
+ __lsf(dst, src, offset, len)
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ module SleepyPenguin
2
+
3
+ # call-seq:
4
+ # SleepyPenguin.copy_file_range(src, dst, len[, keywords]) => # Integer
5
+ #
6
+ # Performs and in-kernel copy of +len+ bytes from +src+ to +dst+,
7
+ # where +src+ and +dst+ are regular files on the same filesystem.
8
+ # Returns the number of bytes copied, which may be less than
9
+ # requested.
10
+ #
11
+ # +flags+ is currently unused, but may be specified in the future.
12
+ #
13
+ # Keywords:
14
+ #
15
+ # :off_in and :off_out if non-nil may be used to specify an Integer
16
+ # offset for each respective descriptor. If specified, the file
17
+ # offsets of each file description will not be moved, providing
18
+ # pread(2)/pwrite(2)-like semantics.
19
+ #
20
+ # See copy_file_range(2) manpage for full documentation:
21
+ # http://man7.org/linux/man-pages/man2/copy_file_range.2.html
22
+ #
23
+ # This method only works in Linux 4.5+ with sleepy_penguin 3.5.0+,
24
+ # and may require up-to-date kernel headers for non-x86/x86-64 systems.
25
+ def self.copy_file_range(io_in, io_out, len, flags = 0,
26
+ off_in: nil, off_out: nil)
27
+ __cfr(io_in, off_in, io_out, off_out, len, flags)
28
+ end
29
+ end
@@ -45,13 +45,16 @@ class SleepyPenguin::Epoll
45
45
  end
46
46
  end
47
47
 
48
- # Calls epoll_wait(2) and yields Integer +events+ and IO objects watched
48
+ # Calls epoll_wait(2) and yields Integer +events+ and +IO+ objects watched
49
49
  # for. +maxevents+ is the maximum number of events to process at once,
50
50
  # lower numbers may prevent starvation when used by epoll_wait in multiple
51
51
  # threads. Larger +maxevents+ reduces syscall overhead for
52
52
  # single-threaded applications. +maxevents+ defaults to 64 events.
53
53
  # +timeout+ is specified in milliseconds, +nil+
54
54
  # (the default) meaning it will block and wait indefinitely.
55
+ #
56
+ # As of sleepy_penguin 3.5.0+, it is possible to nest
57
+ # #wait calls within the same thread.
55
58
  def wait(maxevents = 64, timeout = nil)
56
59
  # snapshot the marks so we do can sit this thread on epoll_wait while other
57
60
  # threads may call epoll_ctl. People say RCU is a poor man's GC, but our
@@ -62,7 +65,7 @@ class SleepyPenguin::Epoll
62
65
  end
63
66
 
64
67
  # we keep a snapshot of @marks around in case another thread closes
65
- # the IO while it is being transferred to userspace. We release mtx
68
+ # the io while it is being transferred to userspace. We release mtx
66
69
  # so another thread may add events to us while we're sleeping.
67
70
  @io.epoll_wait(maxevents, timeout) { |events, io| yield(events, io) }
68
71
  ensure
@@ -87,7 +90,7 @@ class SleepyPenguin::Epoll
87
90
  # call-seq:
88
91
  # ep.del(io) -> 0
89
92
  #
90
- # Disables an IO object from being watched.
93
+ # Disables an +IO+ object from being watched.
91
94
  def del(io)
92
95
  fd = io.to_io.fileno
93
96
  @mtx.synchronize do
@@ -125,7 +128,7 @@ class SleepyPenguin::Epoll
125
128
  # call-seq:
126
129
  # epoll.mod(io, flags) -> 0
127
130
  #
128
- # Changes the watch for an existing IO object based on +events+.
131
+ # Changes the watch for an existing +IO+ object based on +events+.
129
132
  # Returns zero on success, will raise SystemError on failure.
130
133
  def mod(io, events)
131
134
  events = __event_flags(events)
@@ -166,7 +169,7 @@ class SleepyPenguin::Epoll
166
169
  begin
167
170
  @io.epoll_ctl(CTL_MOD, io, events)
168
171
  rescue Errno::ENOENT
169
- warn "epoll event cache failed (mod -> add)"
172
+ warn "epoll event cache failed (mod -> add)\n"
170
173
  @io.epoll_ctl(CTL_ADD, io, events)
171
174
  @marks[fd] = io
172
175
  end
@@ -174,7 +177,7 @@ class SleepyPenguin::Epoll
174
177
  begin
175
178
  @io.epoll_ctl(CTL_ADD, io, events)
176
179
  rescue Errno::EEXIST
177
- warn "epoll event cache failed (add -> mod)"
180
+ warn "epoll event cache failed (add -> mod)\n"
178
181
  @io.epoll_ctl(CTL_MOD, io, events)
179
182
  end
180
183
  @marks[fd] = io
@@ -214,8 +217,8 @@ class SleepyPenguin::Epoll
214
217
  # call-seq:
215
218
  # ep.io_for(io) -> object
216
219
  #
217
- # Returns the given IO object currently being watched for. Different
218
- # IO objects may internally refer to the same process file descriptor.
220
+ # Returns the given +IO+ object currently being watched for. Different
221
+ # +IO+ objects may internally refer to the same process file descriptor.
219
222
  # Mostly used for debugging.
220
223
  def io_for(io)
221
224
  fd = __fileno(io)
@@ -244,9 +247,9 @@ class SleepyPenguin::Epoll
244
247
  # call-seq:
245
248
  # epoll.include?(io) -> true or false
246
249
  #
247
- # Returns whether or not a given IO is watched and prevented from being
250
+ # Returns whether or not a given +IO+ is watched and prevented from being
248
251
  # garbage-collected by the current Epoll object. This may include
249
- # closed IO objects.
252
+ # closed +IO+ objects.
250
253
  def include?(io)
251
254
  fd = __fileno(io)
252
255
  @mtx.synchronize do
@@ -1,7 +1,8 @@
1
1
  require 'thread'
2
+ require_relative 'kevent'
2
3
 
3
- # The high-level Kqueue interface. This provides fork-safety under Ruby 1.9
4
- # and later (but not Ruby 1.8).
4
+ # The high-level Kqueue interface. This provides fork-safety; as
5
+ # underlying kqueue descriptors are closed by the OS upon fork.
5
6
  # This also provides memory protection from bugs due to not storing an
6
7
  # external reference to an object, but still requires the user to store
7
8
  # their own object references.
@@ -33,10 +34,6 @@ class SleepyPenguin::Kqueue
33
34
 
34
35
  def __kq_check # :nodoc:
35
36
  return if @pid == $$ || @io.closed?
36
- unless @io.respond_to?(:autoclose=)
37
- raise RuntimeError,
38
- "Kqueue is not safe to use without IO#autoclose=, upgrade to Ruby 1.9+"
39
- end
40
37
 
41
38
  # kqueue has (strange) close-on-fork behavior
42
39
  objects = @copies.values
@@ -55,6 +52,9 @@ class SleepyPenguin::Kqueue
55
52
  # Ruby GC, otherwise ObjectSpace._id2ref may return invalid objects.
56
53
  # Unlike the low-level Kqueue::IO#kevent, the block given yields only
57
54
  # a single Kevent struct, not a 6-element array.
55
+ #
56
+ # As of sleepy_penguin 3.5.0+, it is possible to nest #kevent
57
+ # calls within the same thread.
58
58
  def kevent(changelist = nil, *args)
59
59
  @mtx.synchronize { __kq_check }
60
60
  if changelist
@@ -1,4 +1,4 @@
1
1
  # :stopdoc:
2
- require "sleepy_penguin"
2
+ require_relative '../sleepy_penguin'
3
3
  Object.const_set(:SP, SleepyPenguin)
4
4
  # :startdoc:
@@ -0,0 +1,125 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module SleepyPenguin
4
+ # call-seq:
5
+ # SleepyPenguin.splice(io_in, io_out, len[, flags [, keywords]) => Integer
6
+ #
7
+ # Splice +len+ bytes from/to a pipe. Either +io_in+ or +io_out+
8
+ # MUST be a pipe. +io_in+ and +io_out+ may BOTH be pipes as of
9
+ # Linux 2.6.31 or later.
10
+ #
11
+ # +flags+ defaults to zero if unspecified.
12
+ # It may be an Integer bitmask, a Symbol, or Array of Symbols
13
+ #
14
+ # The integer bitmask may any combination of:
15
+ #
16
+ # * SleepyPenguin::F_MOVE - attempt to move pages instead of copying
17
+ #
18
+ # * SleepyPenguin::F_NONBLOCK - do not block on pipe I/O (only)
19
+ #
20
+ # * SleepyPenguin::F_MORE - indicates more data will be sent soon
21
+ #
22
+ # Symbols may be used as well to specify a single flag:
23
+ #
24
+ # * :move - corresponds to F_MOVE
25
+ # * :nonblock - corresponds to F_NONBLOCK
26
+ # * :more - corresponds to F_MORE
27
+ #
28
+ # Or, an array of any combination of the above symbols.
29
+ #
30
+ # Keywords:
31
+ #
32
+ # :off_in and :off_out if non-nil may be used to
33
+ # specify an offset for the respective non-pipe file descriptor.
34
+ #
35
+ # :exception defaults to +true+. Setting it to +false+
36
+ # will return :EAGAIN symbol instead of raising Errno::EAGAIN.
37
+ # This will also return +nil+ instead of raising EOFError
38
+ # when +io_in+ is at the end.
39
+ #
40
+ # Raises EOFError when +io_in+ has reached end of file.
41
+ # Raises Errno::EAGAIN if the SleepyPenguin::F_NONBLOCK flag is set
42
+ # and the pipe has no data to read from or space to write to. May
43
+ # also raise Errno::EAGAIN if the non-pipe descriptor has no data
44
+ # to read from or space to write to.
45
+ #
46
+ # As splice never exposes buffers to userspace, it will not take
47
+ # into account userspace buffering done by Ruby or stdio. It is
48
+ # also not subject to encoding/decoding filters under Ruby 1.9+.
49
+ #
50
+ # Consider using `exception: false` if +io_out+ is a pipe or if you
51
+ # are using non-blocking I/O on both descriptors as it avoids the
52
+ # cost of raising common Errno::EAGAIN exceptions.
53
+ #
54
+ # See manpage for full documentation:
55
+ # http://man7.org/linux/man-pages/man2/splice.2.html
56
+ #
57
+ # Support for this exists in sleepy_penguin 3.5.0+
58
+ def self.splice(io_in, io_out, len, flags = 0,
59
+ off_in: nil, off_out: nil, exception: true)
60
+ flags = __map_splice_flags(flags)
61
+ ret = __splice(io_in, off_in, io_out, off_out, len, flags)
62
+ exception ? __map_exc(ret) : ret
63
+ end
64
+
65
+ # call-seq:
66
+ # SleepyPenguin.tee(io_in, io_out, len[, flags[, keywords]) => Integer
67
+ #
68
+ # Copies up to +len+ bytes of data from +io_in+ to +io_out+. +io_in+
69
+ # and +io_out+ must both refer to pipe descriptors. +io_in+ and +io_out+
70
+ # may not be endpoints of the same pipe.
71
+ #
72
+ # +flags+ may be zero (the default) or a combination of:
73
+ # * SleepyPenguin::F_NONBLOCK
74
+ #
75
+ # As a shortcut, the `:nonblock` symbol may be used instead.
76
+ #
77
+ # Other splice-related flags are currently unimplemented in the
78
+ # kernel and have no effect.
79
+ #
80
+ # Returns the number of bytes duplicated if successful.
81
+ # Raises EOFError when +io_in+ is closed and emptied.
82
+ # Raises Errno::EAGAIN when +io_in+ is empty and/or +io_out+ is full
83
+ # and +flags+ specifies non-blocking operation
84
+ #
85
+ # Keywords:
86
+ #
87
+ # :exception defaults to +true+. Setting it to +false+
88
+ # will return :EAGAIN symbol instead of raising Errno::EAGAIN.
89
+ # This will also return +nil+ instead of raising EOFError
90
+ # when +io_in+ is at the end.
91
+ #
92
+ # Consider using `exception: false` if +io_out+ is a pipe or if you
93
+ # are using non-blocking I/O on both descriptors as it avoids the
94
+ # cost of raising common Errno::EAGAIN exceptions.
95
+ #
96
+ # See manpage for full documentation:
97
+ # http://man7.org/linux/man-pages/man2/tee.2.html
98
+ #
99
+ # Support for this exists in sleepy_penguin 3.5.0+
100
+ def self.tee(io_in, io_out, len, flags = 0, exception: true)
101
+ flags = __map_splice_flags(flags)
102
+ ret = __tee(io_in, io_out, len, flags)
103
+ exception ? __map_exc(ret) : ret
104
+ end if respond_to?(:__tee)
105
+
106
+ @__splice_f_map = { # :nodoc:
107
+ :nonblock => F_NONBLOCK,
108
+ :more => F_MORE,
109
+ :move => F_MOVE
110
+ }
111
+
112
+ def self.__map_splice_flags(flags) # :nodoc:
113
+ onef = @__splice_f_map[flags] and return onef
114
+ flags.respond_to?(:inject) ?
115
+ flags.inject(0) { |fl, sym| fl |= @__splice_f_map[sym] } : flags
116
+ end
117
+
118
+ def self.__map_exc(ret) # :nodoc:
119
+ case ret
120
+ when :EAGAIN then raise Errno::EAGAIN, 'Resource temporarily unavailable'
121
+ when nil then raise EOFError, 'end of file reached'
122
+ end
123
+ ret
124
+ end
125
+ end