sendfile 0.9.3 → 1.0.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/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
-