sleepy_penguin 3.4.1 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
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