io_splice 0.1.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.
data/Rakefile ADDED
@@ -0,0 +1,156 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # most tasks are in the GNUmakefile which offers better parallelism
4
+
5
+ def tags
6
+ timefmt = '%Y-%m-%dT%H:%M:%SZ'
7
+ @tags ||= `git tag -l`.split(/\n/).map do |tag|
8
+ if %r{\Av[\d\.]+\z} =~ tag
9
+ header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
10
+ header = header.split(/\n/)
11
+ tagger = header.grep(/\Atagger /).first
12
+ body ||= "initial"
13
+ {
14
+ :time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
15
+ :tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip,
16
+ :tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
17
+ :id => `git rev-parse refs/tags/#{tag}`.chomp!,
18
+ :tag => tag,
19
+ :subject => subject,
20
+ :body => body,
21
+ }
22
+ end
23
+ end.compact.sort { |a,b| b[:time] <=> a[:time] }
24
+ end
25
+
26
+ cgit_url = "http://git.bogomips.org/cgit/ruby_io_splice.git"
27
+ git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/ruby_io_splice.git'
28
+ web_url = "http://bogomips.org/ruby_io_splice/"
29
+
30
+ desc 'prints news as an Atom feed'
31
+ task :news_atom do
32
+ require 'nokogiri'
33
+ new_tags = tags[0,10]
34
+ puts(Nokogiri::XML::Builder.new do
35
+ feed :xmlns => "http://www.w3.org/2005/Atom" do
36
+ id! "#{web_url}NEWS.atom.xml"
37
+ title "Ruby io_splice news"
38
+ subtitle "splice and tee Linux syscalls from Ruby"
39
+ link! :rel => "alternate", :type => "text/html",
40
+ :href => "#{web_url}NEWS.html"
41
+ updated(new_tags.empty? ? "1970-01-01T00:00:00Z" : new_tags.first[:time])
42
+ new_tags.each do |tag|
43
+ entry do
44
+ title tag[:subject]
45
+ updated tag[:time]
46
+ published tag[:time]
47
+ author {
48
+ name tag[:tagger_name]
49
+ email tag[:tagger_email]
50
+ }
51
+ url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
52
+ link! :rel => "alternate", :type => "text/html", :href =>url
53
+ id! url
54
+ message_only = tag[:body].split(/\n.+\(\d+\):\n {6}/s).first.strip
55
+ content({:type =>:text}, message_only)
56
+ content(:type =>:xhtml) { pre tag[:body] }
57
+ end
58
+ end
59
+ end
60
+ end.to_xml)
61
+ end
62
+
63
+ desc 'prints RDoc-formatted news'
64
+ task :news_rdoc do
65
+ tags.each do |tag|
66
+ time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC')
67
+ puts "=== #{tag[:tag].sub(/^v/, '')} / #{time}"
68
+ puts ""
69
+
70
+ body = tag[:body]
71
+ puts tag[:body].gsub(/^/sm, " ").gsub(/[ \t]+$/sm, "")
72
+ puts ""
73
+ end
74
+ end
75
+
76
+ desc "print release changelog for Rubyforge"
77
+ task :release_changes do
78
+ version = ENV['VERSION'] or abort "VERSION= needed"
79
+ version = "v#{version}"
80
+ vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort
81
+ prev = vtags[vtags.index(version) - 1]
82
+ if prev
83
+ system('git', 'diff', '--stat', prev, version) or abort $?
84
+ puts ""
85
+ system('git', 'log', "#{prev}..#{version}") or abort $?
86
+ else
87
+ system('git', 'log', version) or abort $?
88
+ end
89
+ end
90
+
91
+ desc "print release notes for Rubyforge"
92
+ task :release_notes do
93
+ spec = Gem::Specification.load('io_splice.gemspec')
94
+ puts spec.description.strip
95
+ puts ""
96
+ puts "* #{spec.homepage}"
97
+ puts "* #{spec.email}"
98
+ puts "* #{git_url}"
99
+
100
+ _, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
101
+ print "\nChanges:\n\n"
102
+ puts body
103
+ end
104
+
105
+ desc "read news article from STDIN and post to rubyforge"
106
+ task :publish_news do
107
+ require 'rubyforge'
108
+ IO.select([STDIN], nil, nil, 1) or abort "E: news must be read from stdin"
109
+ msg = STDIN.readlines
110
+ subject = msg.shift
111
+ blank = msg.shift
112
+ blank == "\n" or abort "no newline after subject!"
113
+ subject.strip!
114
+ body = msg.join("").strip!
115
+
116
+ rf = RubyForge.new.configure
117
+ rf.login
118
+ rf.post_news('qrp', subject, body)
119
+ end
120
+
121
+ desc "post to RAA"
122
+ task :raa_update do
123
+ require 'net/http'
124
+ require 'net/netrc'
125
+ rc = Net::Netrc.locate('io_splice-raa') or abort "~/.netrc not found"
126
+ password = rc.password
127
+
128
+ s = Gem::Specification.load('io_splice.gemspec')
129
+ desc = [ s.description.strip ]
130
+ desc << ""
131
+ desc << "* #{s.email}"
132
+ desc << "* #{git_url}"
133
+ desc << "* #{cgit_url}"
134
+ desc = desc.join("\n")
135
+ uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
136
+ form = {
137
+ :name => s.name,
138
+ :short_description => s.summary,
139
+ :version => s.version.to_s,
140
+ :status => 'experimental',
141
+ :owner => s.authors.first,
142
+ :email => s.email,
143
+ :category_major => 'Library',
144
+ :category_minor => 'System',
145
+ :url => s.homepage,
146
+ :download => 'http://rubyforge.org/frs/?group_id=5626',
147
+ :license => 'LGPL', # LGPLv3, actually, but RAA is ancient...
148
+ :description_style => 'Plain',
149
+ :description => desc,
150
+ :pass => password,
151
+ :submit => 'Update',
152
+ }
153
+ res = Net::HTTP.post_form(uri, form)
154
+ p res
155
+ puts res.body
156
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+
4
+ # Example of using IO.splice to copy a file
5
+ # This can be significantly faster than IO.copy_stream as data
6
+ # is never copied into userspace.
7
+
8
+ require 'io/splice'
9
+
10
+ usage = "#$0 SOURCE DEST"
11
+ source = ARGV.shift or abort usage
12
+ dest = ARGV.shift or abort usage
13
+
14
+ source = File.open(source, 'rb')
15
+ dest = File.open(dest, 'wb')
16
+ source_fd, dest_fd = source.fileno, dest.fileno
17
+
18
+ # We use a pipe as a ring buffer in kernel space.
19
+ # pipes may store up to IO::Splice::PIPE_CAPA bytes
20
+ pipe = IO.pipe
21
+ rfd, wfd = pipe.map { |io| io.fileno }
22
+
23
+ begin
24
+ nread = begin
25
+ # first pull as many bytes as possible into the pipe
26
+ IO.splice(source_fd, nil, wfd, nil, IO::Splice::PIPE_CAPA, 0)
27
+ rescue EOFError
28
+ break
29
+ end
30
+
31
+ # now move the contents of the pipe buffer into the destination file
32
+ # the copied data never enters userspace
33
+ nwritten = IO.splice(rfd, nil, dest_fd, nil, nread, 0)
34
+
35
+ nwritten == nread or
36
+ abort "short splice to destination file: #{nwritten} != #{nread}"
37
+ end while true
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+
4
+ # An example of using IO.tee, this is a limited version of the standard
5
+ # "tee" utility that requires stdin and stdout to both be pipes.
6
+ require 'io/splice'
7
+
8
+ usage = "filter_prog1 | #$0 DEST | filter_prog2"
9
+ dest = ARGV.shift or abort usage
10
+ $stdin.stat.pipe? or abort "stdin must be a pipe"
11
+ $stdout.stat.pipe? or abort "stdout must be a pipe"
12
+
13
+ dest = File.open(dest, 'wb')
14
+ out_fd = dest.fileno
15
+
16
+ stdin_fd = $stdin.fileno
17
+ stdout_fd = $stdout.fileno
18
+
19
+ begin
20
+ nread = begin
21
+ # "copy" data from stdin to stdout, without consuming stdin
22
+ IO.tee(stdin_fd, stdout_fd, IO::Splice::PIPE_CAPA, 0)
23
+ rescue EOFError
24
+ break
25
+ end
26
+
27
+ # sends data to the file, consumes stdin
28
+ nwritten = IO.splice(stdin_fd, nil, out_fd, nil, nread, 0)
29
+
30
+ nwritten == nread or
31
+ abort "short splice to file: #{nwritten} != #{nread}"
32
+ end while true
@@ -0,0 +1,9 @@
1
+ require 'mkmf'
2
+ $CPPFLAGS << ' -D_GNU_SOURCE=1'
3
+
4
+ have_func('splice', %w(fcntl.h)) or abort 'splice(2) not defined'
5
+ have_func('tee', %w(fcntl.h)) or abort 'tee(2) not defined'
6
+ have_func('rb_thread_blocking_region')
7
+
8
+ dir_config('io_splice')
9
+ create_makefile('io_splice_ext')
@@ -0,0 +1,319 @@
1
+ #include "ruby.h"
2
+ #ifdef HAVE_RUBY_IO_H
3
+ # include "ruby/io.h"
4
+ #else
5
+ # include "rubyio.h"
6
+ #endif
7
+ #include <fcntl.h>
8
+ #include <assert.h>
9
+ #include <sys/uio.h>
10
+ #include <limits.h>
11
+ #include <alloca.h>
12
+
13
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
14
+ # define RUBY_1_8_TRAP_BEG for(;0;)
15
+ # define RUBY_1_8_TRAP_END for(;0;)
16
+ #else
17
+ /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
18
+ # include <rubysig.h>
19
+ # define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
20
+ typedef void rb_unblock_function_t(void *);
21
+ typedef VALUE rb_blocking_function_t(void *);
22
+ static VALUE
23
+ rb_thread_blocking_region(
24
+ rb_blocking_function_t *fn, void *data1,
25
+ rb_unblock_function_t *ubf, void *data2)
26
+ {
27
+ VALUE rv;
28
+
29
+ assert(RUBY_UBF_IO == ubf && "RUBY_UBF_IO required for emulation");
30
+
31
+ TRAP_BEG;
32
+ rv = fn(data1);
33
+ TRAP_END;
34
+
35
+ return rv;
36
+ }
37
+ # define RUBY_1_8_TRAP_BEG TRAP_BEG
38
+ # define RUBY_1_8_TRAP_END TRAP_END
39
+ #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
40
+
41
+ #ifndef RSTRING_PTR
42
+ # define RSTRING_PTR(s) (RSTRING(s)->ptr)
43
+ #endif
44
+ #ifndef RSTRING_LEN
45
+ # define RSTRING_LEN(s) (RSTRING(s)->len)
46
+ #endif
47
+ #ifndef RARRAY_PTR
48
+ # define RARRAY_PTR(s) (RARRAY(s)->ptr)
49
+ #endif
50
+ #ifndef RARRAY_LEN
51
+ # define RARRAY_LEN(s) (RARRAY(s)->len)
52
+ #endif
53
+
54
+ /*
55
+ * Releases GVL only iff blocking I/O is used.
56
+ * We'll trust programmers who use non-blocking I/O explicitly to
57
+ * want the fastest possible performance without resorting to threads,
58
+ * so releasing and them immediately reacquiring the GVL would be
59
+ * a waste of time.
60
+ */
61
+ static VALUE nb_io_run(rb_blocking_function_t *fn, void *data, unsigned flags)
62
+ {
63
+ if (flags & SPLICE_F_NONBLOCK)
64
+ return fn(data);
65
+ return rb_thread_blocking_region(fn, data, RUBY_UBF_IO, 0);
66
+ }
67
+
68
+ struct splice_args {
69
+ int fd_in;
70
+ off_t *off_in;
71
+ int fd_out;
72
+ off_t *off_out;
73
+ size_t len;
74
+ unsigned flags;
75
+ };
76
+
77
+ static VALUE nogvl_splice(void *ptr)
78
+ {
79
+ struct splice_args *a = ptr;
80
+ long n;
81
+
82
+ /*
83
+ * it's still possible to block because the SPLICE_F_NONBLOCK flag
84
+ * only affects the pipe descriptor, not the non-pipe descriptor.
85
+ * So use TRAP_BEG/TRAP_END (only) to make Ruby 1.8 happy. We also
86
+ * don't want the TRAP_BEG/TRAP_END compatibility layer in 1.9,
87
+ * so we use the 1.8-only versions
88
+ */
89
+ RUBY_1_8_TRAP_BEG;
90
+ n = splice(a->fd_in, a->off_in, a->fd_out, a->off_out,
91
+ a->len, a->flags);
92
+ RUBY_1_8_TRAP_END;
93
+
94
+ return (VALUE)n;
95
+ }
96
+
97
+ /*
98
+ * call-seq:
99
+ * IO.splice(fd_in, off_in, fd_out, off_out, len, flags) => integer
100
+ *
101
+ * Splice +len+ bytes from/to a pipe. Either +fd_in+ or +fd_out+
102
+ * MUST be a pipe. +fd_in+ and +fd_out+ may BOTH be pipes as of
103
+ * Linux 2.6.31 or later.
104
+ *
105
+ * +off_in+ and +off_out+ if non-nil may be used to
106
+ * specify an offset for the non-pipe file descriptor.
107
+ *
108
+ * +flags+ may be a bitmask of the following flags:
109
+ *
110
+ * IO::Splice::F_MOVE, IO::Splice::F_NONBLOCK, IO::Splice::F_MORE
111
+ *
112
+ * Returns the number of bytes spliced.
113
+ * Raises EOFError when +fd_in+ has reached end of file.
114
+ * Raises Errno::EAGAIN if the IO::Splice::F_NONBLOCK flag is set
115
+ * and the pipe has no data to read from or space to write to. May
116
+ * also raise Errno::EAGAIN if the non-pipe descriptor has no data
117
+ * to read from or space to write to.
118
+ *
119
+ * rd, wr = (pipe = IO.pipe).map { |io| io.fileno }
120
+ * src_io, dst_io = File.open("/path/to/src"), File.open("/path/to/dst")
121
+ * src, dst = src_io.fileno, dst_io.fileno
122
+ *
123
+ * nr = IO.splice(src, nil, wr, nil, IO::Splice::PIPE_CAPA, 0)
124
+ * IO.splice(rd, nil, dst, nil, nr, 0)
125
+ *
126
+ * As splice never exposes buffers to userspace, it will not take
127
+ * into account userspace buffering done by Ruby or stdio. It is
128
+ * also not subject to encoding/decoding filters under Ruby 1.9.
129
+ *
130
+ * See manpage for full documentation:
131
+ * http://kernel.org/doc/man-pages/online/pages/man2/splice.2.html
132
+ */
133
+ static VALUE my_splice(VALUE self,
134
+ VALUE fd_in, VALUE off_in,
135
+ VALUE fd_out, VALUE off_out,
136
+ VALUE len, VALUE flags)
137
+ {
138
+ off_t i, o;
139
+ long n;
140
+ struct splice_args a = {
141
+ .off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i),
142
+ .off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o),
143
+ .fd_in = NUM2INT(fd_in),
144
+ .fd_out = NUM2INT(fd_out),
145
+ .len = (size_t)NUM2ULONG(len),
146
+ .flags = NUM2UINT(flags),
147
+ };
148
+
149
+ n = (long)nb_io_run(nogvl_splice, &a, a.flags);
150
+ if (n == 0)
151
+ rb_eof_error();
152
+ if (n < 0)
153
+ rb_sys_fail("splice");
154
+ return LONG2NUM(n);
155
+ }
156
+
157
+ struct tee_args {
158
+ int fd_in;
159
+ int fd_out;
160
+ size_t len;
161
+ unsigned flags;
162
+ };
163
+
164
+ /* runs without GVL */
165
+ static VALUE nogvl_tee(void *ptr)
166
+ {
167
+ struct tee_args *a = ptr;
168
+
169
+ return (VALUE)tee(a->fd_in, a->fd_out, a->len, a->flags);
170
+ }
171
+
172
+ /*
173
+ * call-seq:
174
+ * IO.tee(fd_in, fd_out, len, flags) => integer
175
+ *
176
+ * Copies up to +len+ bytes of data from +fd_in+ to +fd_out+. +fd_in+
177
+ * and +fd_out+ must both refer to pipe descriptors. +fd_in+ and +fd_out+
178
+ * may not be endpoints of the same pipe.
179
+ *
180
+ * +flags+ may be zero or IO::Splice::F_NONBLOCK
181
+ * Other IO::Splice flags are currently unimplemented or have no effect.
182
+ *
183
+ * Returns the number of bytes duplicated if successful.
184
+ * Raises EOFError when +fd_in+ is closed and emptied.
185
+ * Raises Errno::EAGAIN when +fd_in+ is empty and/or +fd_out+ is full
186
+ * and +flags+ contains IO::Splice::F_NONBLOCK
187
+ *
188
+ * See manpage for full documentation:
189
+ * http://kernel.org/doc/man-pages/online/pages/man2/tee.2.html
190
+ */
191
+ static VALUE my_tee(VALUE self,
192
+ VALUE fd_in, VALUE fd_out,
193
+ VALUE len, VALUE flags)
194
+ {
195
+ long n;
196
+ struct tee_args a = {
197
+ .fd_in = NUM2INT(fd_in),
198
+ .fd_out = NUM2INT(fd_out),
199
+ .len = (size_t)NUM2ULONG(len),
200
+ .flags = NUM2UINT(flags),
201
+ };
202
+
203
+ n = (long)nb_io_run(nogvl_tee, &a, a.flags);
204
+ if (n == 0)
205
+ rb_eof_error();
206
+ if (n < 0)
207
+ rb_sys_fail("tee");
208
+
209
+ return LONG2NUM(n);
210
+ }
211
+
212
+ struct vmsplice_args {
213
+ int fd;
214
+ struct iovec *iov;
215
+ unsigned long nr_segs;
216
+ unsigned flags;
217
+ };
218
+
219
+ static VALUE nogvl_vmsplice(void *ptr)
220
+ {
221
+ struct vmsplice_args *a = ptr;
222
+
223
+ return (VALUE)vmsplice(a->fd, a->iov, a->nr_segs, a->flags);
224
+ }
225
+
226
+ /*
227
+ * call-seq:
228
+ * IO.vmsplice(fd, string_array, flags) => integer
229
+ *
230
+ * Transfers an array of strings into the pipe descriptor given by fd.
231
+ * +fd+ must be the writable end of a pipe.
232
+ *
233
+ * This may allow the kernel to avoid data copies in some cases.
234
+ * but is (probably) of limited usefulness in Ruby.
235
+ *
236
+ * See manpage for full documentation:
237
+ * http://kernel.org/doc/man-pages/online/pages/man2/vmsplice.2.html
238
+ */
239
+ static VALUE my_vmsplice(VALUE self, VALUE fd, VALUE data, VALUE flags)
240
+ {
241
+ long n;
242
+ struct vmsplice_args a;
243
+ struct iovec *tmp;
244
+ VALUE *ary;
245
+
246
+ switch (TYPE(data)) {
247
+ case T_ARRAY:
248
+ ary = RARRAY_PTR(data);
249
+ a.nr_segs = RARRAY_LEN(data);
250
+
251
+ if (a.nr_segs > IOV_MAX)
252
+ rb_raise(rb_eArgError, "array larger than IOV_MAX");
253
+
254
+ a.iov = tmp = alloca(sizeof(struct iovec) * a.nr_segs);
255
+
256
+ for (n = (long)a.nr_segs; --n >= 0; tmp++, ary++) {
257
+ if (TYPE(*ary) != T_STRING)
258
+ rb_raise(rb_eArgError,
259
+ "must be an array of strings");
260
+ tmp->iov_base = RSTRING_PTR(*ary);
261
+ tmp->iov_len = RSTRING_LEN(*ary);
262
+ }
263
+ break;
264
+ default:
265
+ rb_raise(rb_eArgError, "must be an array of strings");
266
+ }
267
+
268
+ a.fd = NUM2INT(fd);
269
+ a.flags = NUM2UINT(flags);
270
+
271
+ n = (long)nb_io_run(nogvl_vmsplice, &a, a.flags);
272
+ if (n < 0)
273
+ rb_sys_fail("vmsplice");
274
+
275
+ return LONG2NUM(n);
276
+ }
277
+
278
+ void Init_io_splice_ext(void)
279
+ {
280
+ VALUE cSplice = rb_define_class_under(rb_cIO, "Splice", rb_cObject);
281
+
282
+ rb_define_singleton_method(rb_cIO, "splice", my_splice, 6);
283
+ rb_define_singleton_method(rb_cIO, "tee", my_tee, 4);
284
+ rb_define_singleton_method(rb_cIO, "vmsplice", my_vmsplice, 3);
285
+
286
+ /*
287
+ * Attempt to move pages instead of copying. This is only a hint
288
+ * and support for it was removed in Linux 2.6.21 and has not been
289
+ * readded as of 2.6.30.
290
+ */
291
+ rb_define_const(cSplice, "F_MOVE", UINT2NUM(SPLICE_F_MOVE));
292
+
293
+ /*
294
+ * Do not block on I/O. This flag only affects the pipe(s) being
295
+ * spliced from/to and has no effect on the non-pipe descriptor
296
+ * (which requires non-blocking operation to be set explicitly).
297
+ */
298
+ rb_define_const(cSplice, "F_NONBLOCK", UINT2NUM(SPLICE_F_NONBLOCK));
299
+
300
+ /*
301
+ * Indicate that there may be more data coming into the outbound
302
+ * descriptor. This can allow the kernel to avoid sending partial
303
+ * frames from sockets. Currently only used with splice.
304
+ */
305
+ rb_define_const(cSplice, "F_MORE", UINT2NUM(SPLICE_F_MORE));
306
+
307
+ /*
308
+ * Only usable by vmsplice. This flag probably not useful in the
309
+ * context of Ruby applications which cannot control alignment.
310
+ */
311
+ rb_define_const(cSplice, "F_GIFT", UINT2NUM(SPLICE_F_GIFT));
312
+
313
+ /*
314
+ * The maximum size of an atomic write to a pipe
315
+ * POSIX requires this to be at least 512 bytes.
316
+ * Under Linux, this is 4096 bytes.
317
+ */
318
+ rb_define_const(cSplice, "PIPE_BUF", UINT2NUM(PIPE_BUF));
319
+ }