sendfile 0.9.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/FILES CHANGED
@@ -1,5 +1,5 @@
1
1
  FILES
2
- README
2
+ README.textile
3
3
  LICENSE
4
4
  ChangeLog
5
5
  ext/extconf.rb
data/README.textile ADDED
@@ -0,0 +1,31 @@
1
+ h1. Ruby sendfile(2) Interface
2
+
3
+ This module allows Ruby programs to access their OS's native
4
+ <code>sendfile(2)</code> system call from any IO object. Your kernel must
5
+ export a recognized signature for the <code>sendfile(2)</code> system call
6
+ to use this module. Currently, that includes Linux, Solaris
7
+ and FreeBSD.
8
+
9
+ h2. Installation
10
+
11
+ Download and install the latest package from the rubyforge.org
12
+ RubyGems repository.
13
+
14
+ $ sudo gem install sendfile
15
+
16
+ If the tests all pass, you're ready to start using sendfile.
17
+
18
+ h2. Usage
19
+
20
+ Here's a small example of a use of <code>IO#sendfile</code>.
21
+
22
+ require 'socket'
23
+ require 'rubygems'
24
+ require 'sendfile'
25
+ s = TCPSocket.new 'yourdomain.com', 5000
26
+ File.open 'somefile.txt' { |f| s.sendfile f }
27
+ s.close
28
+
29
+ See the test scripts for more examples on how to use this
30
+ module.
31
+
data/ext/extconf.rb CHANGED
@@ -49,5 +49,6 @@ f.print <<EOF
49
49
  EOF
50
50
  end
51
51
 
52
+ have_func('rb_thread_blocking_region')
52
53
  create_makefile( "sendfile")
53
54
 
data/ext/sendfile.c CHANGED
@@ -33,11 +33,55 @@
33
33
  */
34
34
  #include <sys/stat.h>
35
35
  #include <sys/types.h>
36
+ #include <limits.h>
36
37
  #include "ruby.h"
37
- #include "rubyio.h"
38
- #include "rubysig.h"
38
+ #ifdef HAVE_RUBY_IO_H
39
+ # include "ruby/io.h"
40
+ #else
41
+ # include "rubyio.h"
42
+ #endif
43
+ #include <unistd.h>
44
+ #include <fcntl.h>
39
45
  #include "config.h"
40
46
 
47
+ #ifndef HAVE_RB_THREAD_BLOCKING_REGION
48
+ /*
49
+ * For non-natively threaded interpreters, do not monopolize the
50
+ * process and send in smaller chunks. 64K was chosen as it is
51
+ * half the typical max readahead size in Linux 2.6, giving the
52
+ * kernel some time to populate the page cache in between
53
+ * subsequent sendfile() calls.
54
+ */
55
+ # define MAX_SEND_SIZE ((off_t)(0x10000))
56
+
57
+ /* (very) partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
58
+ # include <rubysig.h>
59
+ # define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
60
+ typedef void rb_unblock_function_t(void *);
61
+ typedef VALUE rb_blocking_function_t(void *);
62
+ static VALUE
63
+ rb_thread_blocking_region(
64
+ rb_blocking_function_t *fn, void *data1,
65
+ rb_unblock_function_t *ubf, void *data2)
66
+ {
67
+ VALUE rv;
68
+
69
+ TRAP_BEG;
70
+ rv = fn(data1);
71
+ TRAP_END;
72
+
73
+ return rv;
74
+ }
75
+ #else
76
+ /*
77
+ * We can release the GVL and block as long as we need to.
78
+ * Limit this to the maximum ssize_t anyways, since 32-bit machines with
79
+ * Large File Support can't send more than this number of bytes
80
+ * in one shot.
81
+ */
82
+ # define MAX_SEND_SIZE ((off_t)LONG_MAX)
83
+ #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
84
+
41
85
  #if defined(RUBY_PLATFORM_FREEBSD)
42
86
  # include <sys/socket.h>
43
87
  # include <sys/uio.h>
@@ -48,103 +92,202 @@
48
92
  # include <sys/sendfile.h>
49
93
  #endif
50
94
 
51
- #define SENDFILE_PAUSE_SEC 0
52
- #define SENDFILE_PAUSE_USEC 500
95
+ static size_t count_max(off_t count)
96
+ {
97
+ return (size_t)(count > MAX_SEND_SIZE ? MAX_SEND_SIZE : count);
98
+ }
99
+
100
+ struct sendfile_args {
101
+ int out;
102
+ int in;
103
+ off_t off;
104
+ off_t count;
105
+ int eof;
106
+ };
107
+
108
+ #if ! HAVE_RB_IO_T
109
+ # define rb_io_t OpenFile
110
+ #endif
111
+
112
+ #ifdef GetReadFile
113
+ # define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
114
+ #else
115
+ # if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
116
+ # define FPTR_TO_FD(fptr) fileno(fptr->f)
117
+ # else
118
+ # define FPTR_TO_FD(fptr) fptr->fd
119
+ # endif
120
+ #endif
121
+
122
+ static int my_rb_fileno(VALUE io)
123
+ {
124
+ rb_io_t *fptr;
125
+
126
+ GetOpenFile(io, fptr);
127
+
128
+ return FPTR_TO_FD(fptr);
129
+ }
53
130
 
54
131
  #if defined(RUBY_PLATFORM_FREEBSD)
55
- static off_t __sendfile(int out, int in, off_t off, size_t count, struct timeval *tv)
132
+ static VALUE nogvl_sendfile(void *data)
56
133
  {
134
+ struct sendfile_args *args = data;
57
135
  int rv;
58
- off_t written, initial = off;
136
+ off_t written;
137
+ size_t w = count_max(args->count);
59
138
 
60
- while (1) {
61
- TRAP_BEG;
62
- rv = sendfile(in, out, off, count, NULL, &written, 0);
63
- TRAP_END;
64
- off += written;
65
- count -= written;
66
- if (rv < 0 && errno != EAGAIN)
67
- rb_sys_fail("sendfile");
68
- if (!rv)
69
- break;
70
- rb_thread_select(0, NULL, NULL, NULL, tv);
139
+ rv = sendfile(args->in, args->out, args->off, args->count,
140
+ NULL, &written, 0);
141
+ if (written == 0 && rv == 0) {
142
+ args->eof = 1;
143
+ } else {
144
+ args->off += written;
145
+ args->count -= written;
71
146
  }
72
- return off - initial;
147
+
148
+ return (VALUE)rv;
73
149
  }
74
150
  #else
75
- static size_t __sendfile(int out, int in, off_t off, size_t count, struct timeval *tv)
151
+ static VALUE nogvl_sendfile(void *data)
76
152
  {
77
- ssize_t rv, remaining = count;
78
-
153
+ ssize_t rv;
154
+ struct sendfile_args *args = data;
155
+ size_t w = count_max(args->count);
156
+
157
+ rv = sendfile(args->out, args->in, &args->off, w);
158
+ if (rv == 0)
159
+ args->eof = 1;
160
+ if (rv > 0)
161
+ args->count -= rv;
162
+
163
+ return (VALUE)rv;
164
+ }
165
+ #endif
166
+
167
+ static off_t sendfile_full(struct sendfile_args *args)
168
+ {
169
+ ssize_t rv;
170
+ off_t all = args->count;
171
+
79
172
  while (1) {
80
- TRAP_BEG;
81
- rv = sendfile(out, in, &off, remaining);
82
- TRAP_END;
83
- if (rv < 0 && errno != EAGAIN)
84
- rb_sys_fail("sendfile");
85
- if (rv > 0)
86
- remaining -= rv;
87
- if (!remaining)
173
+ rv = (ssize_t)rb_thread_blocking_region(nogvl_sendfile, args,
174
+ RUBY_UBF_IO, NULL);
175
+ if (!args->count)
88
176
  break;
89
- rb_thread_select(0, NULL, NULL, NULL, tv);
177
+ if (args->eof) {
178
+ if (all != args->count)
179
+ break;
180
+ rb_eof_error();
181
+ }
182
+ if (rv < 0 && ! rb_io_wait_writable(args->out))
183
+ rb_sys_fail("sendfile");
90
184
  }
91
- return count;
185
+ return all - args->count;
92
186
  }
93
- #endif
94
187
 
95
- /* call-seq:
96
- * writeIO.sendfile( readIO, offset=0, count=nil) => integer
97
- *
98
- * Transfers count bytes starting at offset from readIO directly to writeIO
99
- * without copying (i.e. invoking the kernel to do it for you).
100
- *
101
- * If offset is omitted, transfer starts at the beginning of the file.
102
- *
103
- * If count is omitted, the full length of the file will be sent.
104
- *
105
- * Returns the number of bytes sent on success. Will throw system error
106
- * exception on error. (check man sendfile(2) on your platform for
107
- * information on what errors could result and how to handle them)
108
- */
109
- static VALUE rb_io_sendfile(int argc, VALUE *argv, VALUE self)
188
+ static off_t sendfile_nonblock(struct sendfile_args *args)
189
+ {
190
+ ssize_t rv;
191
+ off_t before = args->count;
192
+ int flags;
193
+
194
+ flags = fcntl(args->out, F_GETFL);
195
+ if (flags == -1)
196
+ rb_sys_fail("fcntl");
197
+ if ((flags & O_NONBLOCK) == 0) {
198
+ if (fcntl(args->out, F_SETFL, flags | O_NONBLOCK) == -1)
199
+ rb_sys_fail("fcntl");
200
+ }
201
+
202
+ rv = (ssize_t)rb_thread_blocking_region(nogvl_sendfile, args,
203
+ RUBY_UBF_IO, NULL);
204
+ if (rv < 0)
205
+ rb_sys_fail("sendfile");
206
+ if (args->eof)
207
+ rb_eof_error();
208
+
209
+ return before - args->count;
210
+ }
211
+
212
+ static void convert_args(int argc, VALUE *argv, VALUE self,
213
+ struct sendfile_args *args)
110
214
  {
111
- int i, o;
112
- size_t c;
113
- off_t off;
114
- OpenFile *iptr, *optr;
115
215
  VALUE in, offset, count;
116
- struct timeval _sendfile_sleep;
117
216
 
118
217
  /* get fds for files involved to pass to sendfile(2) */
119
218
  rb_scan_args(argc, argv, "12", &in, &offset, &count);
120
- if (TYPE(in) != T_FILE)
121
- rb_raise(rb_eArgError, "invalid first argument\n");
122
- GetOpenFile(self, optr);
123
- GetOpenFile(in, iptr);
124
- o = fileno(optr->f);
125
- i = fileno(iptr->f);
126
-
127
- _sendfile_sleep.tv_sec = SENDFILE_PAUSE_SEC;
128
- _sendfile_sleep.tv_usec = SENDFILE_PAUSE_USEC;
129
-
219
+ in = rb_convert_type(in, T_FILE, "IO", "to_io");
220
+ args->out = my_rb_fileno(self);
221
+ args->in = my_rb_fileno(in);
222
+ args->eof = 0;
223
+
130
224
  /* determine offset and count parameters */
131
- off = (NIL_P(offset)) ? 0 : NUM2ULONG(offset);
225
+ args->off = (NIL_P(offset)) ? 0 : NUM2OFFT(offset);
132
226
  if (NIL_P(count)) {
133
227
  /* FreeBSD's sendfile() can take 0 as an indication to send
134
228
  * until end of file, but Linux and Solaris can't, and anyway
135
229
  * we need the file size to ensure we send it all in the case
136
230
  * of a non-blocking fd */
137
231
  struct stat s;
138
- if (fstat(i, &s) == -1)
232
+ if (fstat(args->in, &s) == -1)
139
233
  rb_sys_fail("sendfile");
140
- c = s.st_size;
141
- c -= off;
234
+ args->count = s.st_size;
235
+ args->count -= args->off;
142
236
  } else {
143
- c = NUM2ULONG(count);
237
+ args->count = NUM2OFFT(count);
144
238
  }
239
+ }
240
+
241
+ /* call-seq:
242
+ * writeIO.sendfile( readIO, offset=0, count=nil) => integer
243
+ *
244
+ * Transfers count bytes starting at offset from readIO directly to writeIO
245
+ * without copying (i.e. invoking the kernel to do it for you).
246
+ *
247
+ * If offset is omitted, transfer starts at the beginning of the file.
248
+ *
249
+ * If count is omitted, the full length of the file will be sent.
250
+ *
251
+ * Returns the number of bytes sent on success. Will throw system error
252
+ * exception on error. (check man sendfile(2) on your platform for
253
+ * information on what errors could result and how to handle them)
254
+ */
255
+ static VALUE rb_io_sendfile(int argc, VALUE *argv, VALUE self)
256
+ {
257
+ struct sendfile_args args;
258
+
259
+ convert_args(argc, argv, self, &args);
145
260
 
146
261
  /* now send the file */
147
- return INT2FIX(__sendfile(o, i, off, c, &_sendfile_sleep));
262
+ return OFFT2NUM(sendfile_full(&args));
263
+ }
264
+
265
+ /* call-seq:
266
+ * writeIO.sendfile_nonblock(readIO, offset=0, count=nil) => integer
267
+ *
268
+ * Transfers count bytes starting at offset from readIO directly to writeIO
269
+ * without copying (i.e. invoking the kernel to do it for you).
270
+ *
271
+ * Unlike IO#sendfile, this will set the O_NONBLOCK flag on writeIO
272
+ * before calling sendfile(2) and will raise Errno::EAGAIN instead
273
+ * of blocking. This method is intended for use with non-blocking
274
+ * event frameworks, including those that rely on Fibers.
275
+ *
276
+ * If offset is omitted, transfer starts at the beginning of the file.
277
+ *
278
+ * If count is omitted, the full length of the file will be sent.
279
+ *
280
+ * Returns the number of bytes sent on success. Will throw system error
281
+ * exception on error. (check man sendfile(2) on your platform for
282
+ * information on what errors could result and how to handle them)
283
+ */
284
+ static VALUE rb_io_sendfile_nonblock(int argc, VALUE *argv, VALUE self)
285
+ {
286
+ struct sendfile_args args;
287
+
288
+ convert_args(argc, argv, self, &args);
289
+
290
+ return OFFT2NUM(sendfile_nonblock(&args));
148
291
  }
149
292
 
150
293
  /* Interface to the UNIX sendfile(2) system call. Should work on FreeBSD,
@@ -153,5 +296,7 @@ static VALUE rb_io_sendfile(int argc, VALUE *argv, VALUE self)
153
296
  void Init_sendfile(void)
154
297
  {
155
298
  rb_define_method(rb_cIO, "sendfile", rb_io_sendfile, -1);
299
+ rb_define_method(rb_cIO, "sendfile_nonblock",
300
+ rb_io_sendfile_nonblock, -1);
156
301
  }
157
302
 
data/sendfile.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  spec = Gem::Specification.new do |gs|
4
4
  gs.name = 'sendfile'
5
- gs.version = '0.9.3'
5
+ gs.version = '1.0.0'
6
6
  gs.summary = 'Ruby interface to sendfile(2) system call'
7
7
  gs.description = <<-EOF
8
8
  Allows Ruby programs to access sendfile(2) functionality on
@@ -19,7 +19,7 @@ EOF
19
19
  gs.extensions << 'ext/extconf.rb'
20
20
 
21
21
  gs.has_rdoc = true
22
- gs.extra_rdoc_files = %w(README)
22
+ gs.extra_rdoc_files = %w(README.textile)
23
23
  gs.required_ruby_version = '>= 1.8.0'
24
24
  end
25
25
 
@@ -170,5 +170,58 @@ class TestSendfile < Test::Unit::TestCase
170
170
  assert_equal data.size, read.size
171
171
  assert_equal data, read
172
172
  end
173
+
174
+ def test_sendfile_nonblock
175
+ c, s = UNIXSocket.pair
176
+ nr_sent = 0
177
+ assert_raises(Errno::EAGAIN) do
178
+ loop do
179
+ nr_sent += c.sendfile_nonblock @small
180
+ end
181
+ end
182
+ c.close
183
+ nr_read = s.read.size
184
+ s.close
185
+ assert_equal nr_read, nr_sent
186
+ end
187
+
188
+ def test_tempfile
189
+ tmp = Tempfile.new ''
190
+ tmp.write @small_data
191
+ tmp.rewind
192
+ sent, read = __do_sendfile(tmp)
193
+ assert_equal @small_data.size, sent
194
+ assert_equal @small_data.size, read.size
195
+ assert_equal @small_data, read
196
+ end
197
+
198
+ def test_invalid_file
199
+ assert_raises(TypeError) { __do_sendfile(:hello) }
200
+ end
201
+
202
+ def test_sendfile_too_big_eof
203
+ sent = read = nil
204
+ count = @small_data.size * 2
205
+ s = TCPSocket.new @host, @port
206
+ sent = s.sendfile @small, nil, count
207
+ assert_raises(EOFError) do
208
+ s.sendfile @small, sent, 1
209
+ end
210
+ s.close
211
+ len = @rd.read( 4).unpack( "N")[0]
212
+ read = @rd.read len
213
+ assert_equal @small_data.size, sent
214
+ assert_equal @small_data.size, read.size
215
+ assert_equal @small_data, read
216
+ end
217
+
218
+ def test_sendfile_nonblock_eof
219
+ s = TCPSocket.new @host, @port
220
+ off = @small_data.size
221
+ assert_raises(EOFError) do
222
+ s.sendfile_nonblock @small, off, 1
223
+ end
224
+ s.close
225
+ end
173
226
  end # class TestSendfile
174
227
 
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sendfile
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Toby DiPasquale
@@ -9,21 +15,25 @@ autorequire: sendfile
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2008-11-30 00:00:00 -05:00
18
+ date: 2010-06-26 00:00:00 -04:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
16
- description: Allows Ruby programs to access sendfile(2) functionality on any IO object. Works on Linux, Solaris and FreeBSD with blocking and non-blocking sockets.
22
+ description: |
23
+ Allows Ruby programs to access sendfile(2) functionality on
24
+ any IO object. Works on Linux, Solaris and FreeBSD with
25
+ blocking and non-blocking sockets.
26
+
17
27
  email: toby@cbcg.net
18
28
  executables: []
19
29
 
20
30
  extensions:
21
31
  - ext/extconf.rb
22
32
  extra_rdoc_files:
23
- - README
33
+ - README.textile
24
34
  files:
25
35
  - FILES
26
- - README
36
+ - README.textile
27
37
  - LICENSE
28
38
  - ChangeLog
29
39
  - ext/extconf.rb
@@ -34,29 +44,39 @@ files:
34
44
  - sendfile.gemspec
35
45
  has_rdoc: true
36
46
  homepage:
47
+ licenses: []
48
+
37
49
  post_install_message:
38
50
  rdoc_options: []
39
51
 
40
52
  require_paths:
41
53
  - lib
42
54
  required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
43
56
  requirements:
44
57
  - - ">="
45
58
  - !ruby/object:Gem::Version
59
+ hash: 55
60
+ segments:
61
+ - 1
62
+ - 8
63
+ - 0
46
64
  version: 1.8.0
47
- version:
48
65
  required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
49
67
  requirements:
50
68
  - - ">="
51
69
  - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
52
73
  version: "0"
53
- version:
54
74
  requirements: []
55
75
 
56
76
  rubyforge_project: ruby-sendfile
57
- rubygems_version: 1.3.0
77
+ rubygems_version: 1.3.7
58
78
  signing_key:
59
- specification_version: 2
79
+ specification_version: 3
60
80
  summary: Ruby interface to sendfile(2) system call
61
81
  test_files:
62
82
  - test/test_sendfile.rb
data/README DELETED
@@ -1,57 +0,0 @@
1
- = Ruby sendfile(2) Interface
2
-
3
- This module allows Ruby programs to access their OS's native
4
- sendfile(2) system call from any IO object. Your kernel must
5
- export a recognized signature for the sendfile(2) system call
6
- to use this module. Currently, that includes Linux, Solaris
7
- and FreeBSD.
8
-
9
- == Installation
10
-
11
- Download and install the latest package from the rubyforge.org
12
- RubyGems repository.
13
-
14
- gem install sendfile --remote
15
- gem check sendfile --test
16
-
17
- If the tests all pass, you're ready to start using sendfile.
18
-
19
- Or, if you don't have rubygems installed, you can install by
20
- hand by downloading the tarball:
21
-
22
- tar xzvf ruby-sendfile-<latest>.tar.gz
23
- cd ruby-sendfile-<latest>/ext
24
- ruby extconf.rb
25
- make
26
- sudo make install
27
-
28
- You can then run the tests with:
29
-
30
- ruby test/test_*.rb
31
-
32
- == Usage
33
-
34
- Here's a small example of a use of IO#sendfile.
35
-
36
- require 'socket'
37
- require 'rubygems'
38
- require 'sendfile'
39
-
40
- s = TCPSocket.new 'yourdomain.com', 5000
41
- File.open 'somefile.txt' { |f| s.sendfile f }
42
- s.close
43
-
44
- See the test scripts for more examples on how to use this
45
- module.
46
-
47
- == Contact Information
48
-
49
- This project's homepage is:
50
-
51
- http://rubyforge.org/projects/ruby-sendfile
52
-
53
- Thereupon are additional resources, such as news and forums
54
- can be found for working out any issues you may have with this
55
- module. In the last case, you can email questions or patches
56
- to toby@cbcg.net.
57
-