kgio-sendfile 1.2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ChangeLog +21 -0
- data/FILES +10 -0
- data/LICENSE +21 -0
- data/README.textile +35 -0
- data/ext/extconf.rb +58 -0
- data/ext/sendfile.c +371 -0
- data/sendfile.gemspec +26 -0
- data/test/large.gz +0 -0
- data/test/small +1 -0
- data/test/test_sendfile.rb +252 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 72978ae5f2835c3a37bd201aa17c4d18259fb9a7
|
4
|
+
data.tar.gz: 756d41104b237627b23b90e5f598096f2a036615
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bf9c72e3a21c2e0bdd542ff3780f7d5a2c8f2e5721ddd1e1870db3e5e8744d6b7f0ca0b77f07554271066bdcf472b9f2a3ab0e954d21f73b01992305c35d70c3
|
7
|
+
data.tar.gz: 6a682a3e4719b10421fadb891d552186a608791461840a68cd9e29170ad7c0eb0199096e2538bed75b9f520964ee5854c75f27be6db841fe2ab8ef17e8a2d665
|
data/ChangeLog
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
* sendfile 0.9.3 - 2008.11.30 <toby@cbcg.net>
|
2
|
+
|
3
|
+
- Included sys/stat.h regardless of platform to fix compilation on
|
4
|
+
OS X 10.5 (credit: Damon Morda <dmorda@andrew.cmu.edu>)
|
5
|
+
- Source formatting clean-up
|
6
|
+
|
7
|
+
* sendfile 0.9.2 - 2006.03.27 <toby@cbcg.net>
|
8
|
+
|
9
|
+
- Fixed typo: s/rv_sys_fail/rb_sys_fail
|
10
|
+
|
11
|
+
* sendfile 0.9.1 - 2006.03.24 <toby@cbcg.net>
|
12
|
+
|
13
|
+
- Improved interface with non-blocking sockets
|
14
|
+
- Added unit tests for blocking sends
|
15
|
+
- Added unit tests for nonblocking sends
|
16
|
+
- Added README
|
17
|
+
- Packaged in a RubyGem
|
18
|
+
|
19
|
+
* sendfile 0.0.1 - 2005.11.16 <toby@cbcg.net>
|
20
|
+
|
21
|
+
- Initial release
|
data/FILES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2005,2008 Tobias DiPasquale
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
5
|
+
copy of this software and associated documentation files (the "Software"),
|
6
|
+
to deal in the Software without restriction, including without limitation
|
7
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
8
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included
|
12
|
+
in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
17
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
19
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
20
|
+
DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.textile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
kgio-sendfile is a (hopefully temporary) fork of the original "sendfile"
|
2
|
+
gem for Ruby 2.2.0dev compatibility. Only Linux (and maybe FreeBSD)
|
3
|
+
are supported in this version.
|
4
|
+
|
5
|
+
Send plain-text email to our mailing list at kgio@librelist.org for
|
6
|
+
anything related to this.
|
7
|
+
|
8
|
+
|
9
|
+
h1. Ruby sendfile(2) Interface
|
10
|
+
|
11
|
+
This module allows Ruby programs to access their OS's native <code>sendfile(2)</code> system call from any IO object. Your kernel must export a recognized signature for the <code>sendfile(2)</code> system call to use this module. Currently, that includes Linux, Solaris and FreeBSD.
|
12
|
+
|
13
|
+
h2. Installation
|
14
|
+
|
15
|
+
Download and install the latest package from the rubyforge.org RubyGems repository.
|
16
|
+
|
17
|
+
<code>
|
18
|
+
$ gem install sendfile
|
19
|
+
</code>
|
20
|
+
|
21
|
+
If the tests all pass, you're ready to start using sendfile!
|
22
|
+
|
23
|
+
h2. Usage
|
24
|
+
|
25
|
+
Here's a small example of a use of <code>IO#sendfile</code>.
|
26
|
+
|
27
|
+
bc.. require 'socket'
|
28
|
+
require 'rubygems'
|
29
|
+
require 'sendfile'
|
30
|
+
s = TCPSocket.new 'yourdomain.com', 5000
|
31
|
+
File.open 'somefile.txt' { |f| s.sendfile f }
|
32
|
+
s.close
|
33
|
+
|
34
|
+
p. See the test scripts for more examples on how to use this module.
|
35
|
+
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2005 Tobias DiPasquale
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
5
|
+
# copy of this software and associated documentation files (the "Software"),
|
6
|
+
# to deal in the Software without restriction, including without limitation
|
7
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
8
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
9
|
+
# Software is furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included
|
12
|
+
# in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
17
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
19
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
20
|
+
# DEALINGS IN THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
#
|
24
|
+
# $Id: extconf.rb,v 1.1 2006/03/25 01:42:02 codeslinger Exp $
|
25
|
+
#
|
26
|
+
require 'mkmf'
|
27
|
+
|
28
|
+
$config_h = ""
|
29
|
+
case RUBY_PLATFORM
|
30
|
+
when /solaris/
|
31
|
+
have_header( "sys/sendfile.h")
|
32
|
+
have_library( "sendfile", "sendfile")
|
33
|
+
$config_h << "#define RUBY_PLATFORM_SOLARIS"
|
34
|
+
when /linux/
|
35
|
+
have_header( "sys/sendfile.h")
|
36
|
+
have_library( "c", "sendfile")
|
37
|
+
$config_h << "#define RUBY_PLATFORM_LINUX"
|
38
|
+
when /freebsd/
|
39
|
+
have_library( "c", "sendfile")
|
40
|
+
$config_h << "#define RUBY_PLATFORM_FREEBSD"
|
41
|
+
when /darwin/
|
42
|
+
have_library( "c", "sendfile")
|
43
|
+
$config_h << "#define RUBY_PLATFORM_DARWIN"
|
44
|
+
end
|
45
|
+
|
46
|
+
File.open( "config.h", "w") do |f|
|
47
|
+
f.print <<EOF
|
48
|
+
#ifndef CONFIG_H
|
49
|
+
#define CONFIG_H
|
50
|
+
#{$config_h}
|
51
|
+
#endif /* CONFIG_H */
|
52
|
+
EOF
|
53
|
+
end
|
54
|
+
|
55
|
+
unless have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
|
56
|
+
have_func('rb_thread_blocking_region')
|
57
|
+
end
|
58
|
+
create_makefile( "sendfile")
|
data/ext/sendfile.c
ADDED
@@ -0,0 +1,371 @@
|
|
1
|
+
/*
|
2
|
+
* ------------------------------------------------------------------------
|
3
|
+
*
|
4
|
+
* Copyright (c) 2005,2008 Tobias DiPasquale
|
5
|
+
*
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a
|
7
|
+
* copy of this software and associated documentation files (the "Software"),
|
8
|
+
* to deal in the Software without restriction, including without limitation
|
9
|
+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
10
|
+
* and/or sell copies of the Software, and to permit persons to whom the
|
11
|
+
* Software is furnished to do so, subject to the following conditions:
|
12
|
+
*
|
13
|
+
* The above copyright notice and this permission notice shall be included
|
14
|
+
* in all copies or substantial portions of the Software.
|
15
|
+
*
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
17
|
+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
19
|
+
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
22
|
+
* DEALINGS IN THE SOFTWARE.
|
23
|
+
*
|
24
|
+
* ------------------------------------------------------------------------
|
25
|
+
*
|
26
|
+
* Ruby binding for the UNIX sendfile(2) facility. Should work on FreeBSD,
|
27
|
+
* Linux and Solaris systems that support sendfile(2).
|
28
|
+
*
|
29
|
+
* Original Author: Toby DiPasquale <toby@cbcg.net>
|
30
|
+
* Current Maintainer: Toby DiPasquale <toby@cbcg.net>
|
31
|
+
*
|
32
|
+
* $Id: sendfile.c,v 1.4 2006/03/27 19:14:53 codeslinger Exp $
|
33
|
+
*/
|
34
|
+
#include <sys/stat.h>
|
35
|
+
#include <sys/types.h>
|
36
|
+
#include <limits.h>
|
37
|
+
#include "ruby.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>
|
45
|
+
#include "config.h"
|
46
|
+
static VALUE sym_wait_writable;
|
47
|
+
|
48
|
+
#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && defined(HAVE_RUBY_THREAD_H)
|
49
|
+
/* Ruby 2.0+ */
|
50
|
+
# include <ruby/thread.h>
|
51
|
+
typedef void * (*my_blocking_fn_t)(void*);
|
52
|
+
# define WITHOUT_GVL(fn,a) \
|
53
|
+
rb_thread_call_without_gvl((my_blocking_fn_t)(fn),(a),RUBY_UBF_IO,0)
|
54
|
+
#elif defined(HAVE_RB_THREAD_BLOCKING_REGION) /* Ruby 1.9 */
|
55
|
+
typedef VALUE (*my_blocking_fn_t)(void*);
|
56
|
+
# define WITHOUT_GVL(fn,a) \
|
57
|
+
rb_thread_blocking_region((my_blocking_fn_t)(fn),(a),RUBY_UBF_IO,0)
|
58
|
+
#else /* MRI 1.8 threads */
|
59
|
+
/*
|
60
|
+
* For non-natively threaded interpreters, do not monopolize the
|
61
|
+
* process and send in smaller chunks. 64K was chosen as it is
|
62
|
+
* half the typical max readahead size in Linux 2.6, giving the
|
63
|
+
* kernel some time to populate the page cache in between
|
64
|
+
* subsequent sendfile() calls.
|
65
|
+
*/
|
66
|
+
# define MAX_SEND_SIZE ((off_t)(0x10000))
|
67
|
+
|
68
|
+
/* (very) partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
|
69
|
+
# include <rubysig.h>
|
70
|
+
typedef VALUE rb_blocking_function_t(void *);
|
71
|
+
static VALUE
|
72
|
+
WITHOUT_GVL(rb_blocking_function_t *fn, void *data1)
|
73
|
+
{
|
74
|
+
VALUE rv;
|
75
|
+
|
76
|
+
TRAP_BEG;
|
77
|
+
rv = fn(data1);
|
78
|
+
TRAP_END;
|
79
|
+
|
80
|
+
return rv;
|
81
|
+
}
|
82
|
+
#endif /* WITHOUT_GVL definitions */
|
83
|
+
|
84
|
+
#ifndef MAX_SEND_SIZE
|
85
|
+
/*
|
86
|
+
* We can release the GVL and block as long as we need to.
|
87
|
+
* Limit this to the maximum ssize_t anyways, since 32-bit machines with
|
88
|
+
* Large File Support can't send more than this number of bytes
|
89
|
+
* in one shot.
|
90
|
+
*/
|
91
|
+
# define MAX_SEND_SIZE ((off_t)LONG_MAX)
|
92
|
+
#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
|
93
|
+
|
94
|
+
#if defined(RUBY_PLATFORM_FREEBSD)
|
95
|
+
# include <sys/socket.h>
|
96
|
+
# include <sys/uio.h>
|
97
|
+
#elif defined(RUBY_PLATFORM_LINUX)
|
98
|
+
# include <sys/sendfile.h>
|
99
|
+
# include <unistd.h>
|
100
|
+
#elif defined(RUBY_PLATFORM_SOLARIS)
|
101
|
+
# include <sys/sendfile.h>
|
102
|
+
#elif defined(RUBY_PLATFORM_DARWIN)
|
103
|
+
# include <sys/types.h>
|
104
|
+
# include <sys/socket.h>
|
105
|
+
# include <sys/uio.h>
|
106
|
+
#endif
|
107
|
+
|
108
|
+
static size_t count_max(off_t count)
|
109
|
+
{
|
110
|
+
return (size_t)(count > MAX_SEND_SIZE ? MAX_SEND_SIZE : count);
|
111
|
+
}
|
112
|
+
|
113
|
+
struct sendfile_args {
|
114
|
+
int out;
|
115
|
+
int in;
|
116
|
+
off_t off;
|
117
|
+
off_t count;
|
118
|
+
int eof;
|
119
|
+
};
|
120
|
+
|
121
|
+
#if ! HAVE_RB_IO_T
|
122
|
+
# define rb_io_t OpenFile
|
123
|
+
#endif
|
124
|
+
|
125
|
+
#ifdef GetReadFile
|
126
|
+
# define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
|
127
|
+
#else
|
128
|
+
# if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
|
129
|
+
# define FPTR_TO_FD(fptr) fileno(fptr->f)
|
130
|
+
# else
|
131
|
+
# define FPTR_TO_FD(fptr) fptr->fd
|
132
|
+
# endif
|
133
|
+
#endif
|
134
|
+
|
135
|
+
static int my_rb_fileno(VALUE io)
|
136
|
+
{
|
137
|
+
rb_io_t *fptr;
|
138
|
+
|
139
|
+
GetOpenFile(io, fptr);
|
140
|
+
|
141
|
+
return FPTR_TO_FD(fptr);
|
142
|
+
}
|
143
|
+
|
144
|
+
#if defined(RUBY_PLATFORM_FREEBSD)
|
145
|
+
static VALUE nogvl_sendfile(void *data)
|
146
|
+
{
|
147
|
+
struct sendfile_args *args = data;
|
148
|
+
int rv;
|
149
|
+
off_t written = 0;
|
150
|
+
size_t w = count_max(args->count);
|
151
|
+
|
152
|
+
rv = sendfile(args->in, args->out, args->off, w, NULL, &written, 0);
|
153
|
+
if (written == 0 && rv == 0) {
|
154
|
+
args->eof = 1;
|
155
|
+
} else if (written > 0) {
|
156
|
+
args->off += written;
|
157
|
+
args->count -= written;
|
158
|
+
rv = 0; /* anything written is a success */
|
159
|
+
}
|
160
|
+
|
161
|
+
return (VALUE)rv;
|
162
|
+
}
|
163
|
+
#elif defined(RUBY_PLATFORM_DARWIN)
|
164
|
+
static VALUE nogvl_sendfile(void *data)
|
165
|
+
{
|
166
|
+
struct sendfile_args *args = data;
|
167
|
+
int rv;
|
168
|
+
off_t written = args->count;
|
169
|
+
|
170
|
+
rv = sendfile(args->in, args->out, args->off, &written,
|
171
|
+
NULL, 0);
|
172
|
+
if (written == 0 && rv == 0) {
|
173
|
+
args->eof = 1;
|
174
|
+
} else {
|
175
|
+
args->off += written;
|
176
|
+
args->count -= written;
|
177
|
+
}
|
178
|
+
|
179
|
+
return (VALUE)rv;
|
180
|
+
}
|
181
|
+
#else
|
182
|
+
static VALUE nogvl_sendfile(void *data)
|
183
|
+
{
|
184
|
+
ssize_t rv;
|
185
|
+
struct sendfile_args *args = data;
|
186
|
+
size_t w = count_max(args->count);
|
187
|
+
|
188
|
+
rv = sendfile(args->out, args->in, &args->off, w);
|
189
|
+
if (rv == 0)
|
190
|
+
args->eof = 1;
|
191
|
+
if (rv > 0)
|
192
|
+
args->count -= rv;
|
193
|
+
|
194
|
+
return (VALUE)rv;
|
195
|
+
}
|
196
|
+
#endif
|
197
|
+
|
198
|
+
static off_t sendfile_full(struct sendfile_args *args)
|
199
|
+
{
|
200
|
+
ssize_t rv;
|
201
|
+
off_t all = args->count;
|
202
|
+
|
203
|
+
while (1) {
|
204
|
+
rv = (ssize_t)WITHOUT_GVL(nogvl_sendfile, args);
|
205
|
+
if (!args->count)
|
206
|
+
break;
|
207
|
+
if (args->eof) {
|
208
|
+
if (all != args->count)
|
209
|
+
break;
|
210
|
+
rb_eof_error();
|
211
|
+
}
|
212
|
+
if (rv < 0 && ! rb_io_wait_writable(args->out))
|
213
|
+
rb_sys_fail("sendfile");
|
214
|
+
}
|
215
|
+
return all - args->count;
|
216
|
+
}
|
217
|
+
|
218
|
+
static VALUE sendfile_nonblock(struct sendfile_args *args, int try)
|
219
|
+
{
|
220
|
+
ssize_t rv;
|
221
|
+
off_t before = args->count;
|
222
|
+
int flags;
|
223
|
+
|
224
|
+
flags = fcntl(args->out, F_GETFL);
|
225
|
+
if (flags == -1)
|
226
|
+
rb_sys_fail("fcntl");
|
227
|
+
if ((flags & O_NONBLOCK) == 0) {
|
228
|
+
if (fcntl(args->out, F_SETFL, flags | O_NONBLOCK) == -1)
|
229
|
+
rb_sys_fail("fcntl");
|
230
|
+
}
|
231
|
+
|
232
|
+
rv = (ssize_t)WITHOUT_GVL(nogvl_sendfile, args);
|
233
|
+
if (rv < 0) {
|
234
|
+
if (try && errno == EAGAIN)
|
235
|
+
return sym_wait_writable;
|
236
|
+
rb_sys_fail("sendfile");
|
237
|
+
}
|
238
|
+
if (args->eof) {
|
239
|
+
if (try)
|
240
|
+
return Qnil;
|
241
|
+
rb_eof_error();
|
242
|
+
}
|
243
|
+
|
244
|
+
return OFFT2NUM(before - args->count);
|
245
|
+
}
|
246
|
+
|
247
|
+
static void convert_args(int argc, VALUE *argv, VALUE self,
|
248
|
+
struct sendfile_args *args)
|
249
|
+
{
|
250
|
+
VALUE in, offset, count;
|
251
|
+
|
252
|
+
/* get fds for files involved to pass to sendfile(2) */
|
253
|
+
rb_scan_args(argc, argv, "12", &in, &offset, &count);
|
254
|
+
in = rb_convert_type(in, T_FILE, "IO", "to_io");
|
255
|
+
args->out = my_rb_fileno(self);
|
256
|
+
args->in = my_rb_fileno(in);
|
257
|
+
args->eof = 0;
|
258
|
+
|
259
|
+
/* determine offset and count parameters */
|
260
|
+
args->off = (NIL_P(offset)) ? 0 : NUM2OFFT(offset);
|
261
|
+
if (NIL_P(count)) {
|
262
|
+
/* FreeBSD's sendfile() can take 0 as an indication to send
|
263
|
+
* until end of file, but Linux and Solaris can't, and anyway
|
264
|
+
* we need the file size to ensure we send it all in the case
|
265
|
+
* of a non-blocking fd */
|
266
|
+
struct stat s;
|
267
|
+
if (fstat(args->in, &s) == -1)
|
268
|
+
rb_sys_fail("sendfile");
|
269
|
+
args->count = s.st_size;
|
270
|
+
args->count -= args->off;
|
271
|
+
} else {
|
272
|
+
args->count = NUM2OFFT(count);
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
/* call-seq:
|
277
|
+
* writeIO.sendfile( readIO, offset=0, count=nil) => integer
|
278
|
+
*
|
279
|
+
* Transfers count bytes starting at offset from readIO directly to writeIO
|
280
|
+
* without copying (i.e. invoking the kernel to do it for you).
|
281
|
+
*
|
282
|
+
* If offset is omitted, transfer starts at the beginning of the file.
|
283
|
+
*
|
284
|
+
* If count is omitted, the full length of the file will be sent.
|
285
|
+
*
|
286
|
+
* Returns the number of bytes sent on success. Will throw system error
|
287
|
+
* exception on error. (check man sendfile(2) on your platform for
|
288
|
+
* information on what errors could result and how to handle them)
|
289
|
+
*/
|
290
|
+
static VALUE rb_io_sendfile(int argc, VALUE *argv, VALUE self)
|
291
|
+
{
|
292
|
+
struct sendfile_args args;
|
293
|
+
|
294
|
+
convert_args(argc, argv, self, &args);
|
295
|
+
|
296
|
+
/* now send the file */
|
297
|
+
return OFFT2NUM(sendfile_full(&args));
|
298
|
+
}
|
299
|
+
|
300
|
+
/* call-seq:
|
301
|
+
* writeIO.sendfile_nonblock(readIO, offset=0, count=nil) => integer
|
302
|
+
*
|
303
|
+
* Transfers count bytes starting at offset from readIO directly to writeIO
|
304
|
+
* without copying (i.e. invoking the kernel to do it for you).
|
305
|
+
*
|
306
|
+
* Unlike IO#sendfile, this will set the O_NONBLOCK flag on writeIO
|
307
|
+
* before calling sendfile(2) and will raise Errno::EAGAIN instead
|
308
|
+
* of blocking. This method is intended for use with non-blocking
|
309
|
+
* event frameworks, including those that rely on Fibers.
|
310
|
+
*
|
311
|
+
* If offset is omitted, transfer starts at the beginning of the file.
|
312
|
+
*
|
313
|
+
* If count is omitted, the full length of the file will be sent.
|
314
|
+
*
|
315
|
+
* Returns the number of bytes sent on success. Will throw system error
|
316
|
+
* exception on error. (check man sendfile(2) on your platform for
|
317
|
+
* information on what errors could result and how to handle them)
|
318
|
+
*/
|
319
|
+
static VALUE rb_io_sendfile_nonblock(int argc, VALUE *argv, VALUE self)
|
320
|
+
{
|
321
|
+
struct sendfile_args args;
|
322
|
+
|
323
|
+
convert_args(argc, argv, self, &args);
|
324
|
+
|
325
|
+
return sendfile_nonblock(&args, 0);
|
326
|
+
}
|
327
|
+
|
328
|
+
/* call-seq:
|
329
|
+
* writeIO.trysendfile(readIO, offset=0, count=nil) => integer, nil, or
|
330
|
+
* :wait_writable
|
331
|
+
*
|
332
|
+
* Transfers count bytes starting at offset from readIO directly to writeIO
|
333
|
+
* without copying (i.e. invoking the kernel to do it for you).
|
334
|
+
*
|
335
|
+
* Unlike IO#sendfile, this will set the O_NONBLOCK flag on writeIO
|
336
|
+
* before calling sendfile(2) and will return :wait_writable instead
|
337
|
+
* of blocking. This method is intended for use with non-blocking
|
338
|
+
* event frameworks, including those that rely on Fibers.
|
339
|
+
*
|
340
|
+
* If offset is omitted, transfer starts at the beginning of the file.
|
341
|
+
*
|
342
|
+
* If count is omitted, the full length of the file will be sent.
|
343
|
+
*
|
344
|
+
* Returns the number of bytes sent on success, nil on EOF, and
|
345
|
+
* :wait_writable on EAGAIN. Will throw system error exception on error.
|
346
|
+
* (check man sendfile(2) on your platform for
|
347
|
+
* information on what errors could result and how to handle them)
|
348
|
+
*
|
349
|
+
* This method is a faster alternative to sendfile_nonblock as it does
|
350
|
+
* not raise exceptions on common EAGAIN errors.
|
351
|
+
*/
|
352
|
+
static VALUE rb_io_trysendfile(int argc, VALUE *argv, VALUE self)
|
353
|
+
{
|
354
|
+
struct sendfile_args args;
|
355
|
+
|
356
|
+
convert_args(argc, argv, self, &args);
|
357
|
+
|
358
|
+
return sendfile_nonblock(&args, 1);
|
359
|
+
}
|
360
|
+
|
361
|
+
/* Interface to the UNIX sendfile(2) system call. Should work on FreeBSD,
|
362
|
+
* Linux and Solaris systems that support the sendfile(2) system call.
|
363
|
+
*/
|
364
|
+
void Init_sendfile(void)
|
365
|
+
{
|
366
|
+
sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
|
367
|
+
rb_define_method(rb_cIO, "sendfile", rb_io_sendfile, -1);
|
368
|
+
rb_define_method(rb_cIO, "sendfile_nonblock",
|
369
|
+
rb_io_sendfile_nonblock, -1);
|
370
|
+
rb_define_method(rb_cIO, "trysendfile", rb_io_trysendfile, -1);
|
371
|
+
}
|
data/sendfile.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# vim:set ts=4 sw=4 ai syntax=ruby:
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |gs|
|
4
|
+
gs.name = 'kgio-sendfile'
|
5
|
+
gs.version = '1.2.1.1'
|
6
|
+
gs.summary = 'Ruby interface to sendfile(2) system call'
|
7
|
+
gs.description = <<-EOF
|
8
|
+
This is a (hopefully temporary) fork of the original "sendfile" gem for
|
9
|
+
Ruby 2.2.0dev compatibility. Only Linux (and maybe FreeBSD) are
|
10
|
+
supported in this version.
|
11
|
+
|
12
|
+
Allows Ruby programs to access sendfile(2) functionality on
|
13
|
+
any IO object. Works on Linux, Solaris, FreeBSD and Darwin with
|
14
|
+
blocking and non-blocking sockets.
|
15
|
+
EOF
|
16
|
+
gs.authors = [ 'Eric Wong', 'Toby DiPasquale' ]
|
17
|
+
gs.email = 'kgio@librelist.org'
|
18
|
+
gs.homepage = 'http://bogomips.org/ruby-sendfile.git'
|
19
|
+
gs.files = File.read('FILES').split($/)
|
20
|
+
gs.test_files = Dir.glob 'test/test_*.rb'
|
21
|
+
gs.extensions << 'ext/extconf.rb'
|
22
|
+
gs.has_rdoc = true
|
23
|
+
gs.extra_rdoc_files = %w(README.textile)
|
24
|
+
gs.required_ruby_version = '>= 1.8.0'
|
25
|
+
end
|
26
|
+
|
data/test/large.gz
ADDED
Binary file
|
data/test/small
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
I love Pocky!
|
@@ -0,0 +1,252 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim:set ts=4 sw=4 ai:
|
3
|
+
require 'io/nonblock'
|
4
|
+
begin
|
5
|
+
require 'rubygems'
|
6
|
+
rescue
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
require 'sendfile'
|
10
|
+
require 'socket'
|
11
|
+
require 'tempfile'
|
12
|
+
require "minitest/autorun"
|
13
|
+
require 'minitest'
|
14
|
+
require 'zlib'
|
15
|
+
|
16
|
+
class TestSendfile < MiniTest::Test
|
17
|
+
def __fork_server
|
18
|
+
# open pipe for backchannel
|
19
|
+
@rd, @wr = IO.pipe
|
20
|
+
# fork server child
|
21
|
+
@pid = fork do
|
22
|
+
# close read end in child
|
23
|
+
@rd.close
|
24
|
+
|
25
|
+
# start listening and send port back to parent
|
26
|
+
ss = TCPServer.new @host, 0
|
27
|
+
@wr.write( [ ss.addr[1] ].pack( "S"))
|
28
|
+
@wr.flush
|
29
|
+
|
30
|
+
# read what they send and push it back up the pipe
|
31
|
+
while s = ss.accept
|
32
|
+
data = s.read
|
33
|
+
s.close
|
34
|
+
@wr.write( [data.length].pack( "N"))
|
35
|
+
@wr.write data
|
36
|
+
@wr.flush
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# close write end in parent and get server port
|
41
|
+
@wr.close
|
42
|
+
@port = @rd.read( 2).unpack( "S")[0]
|
43
|
+
end
|
44
|
+
|
45
|
+
def setup
|
46
|
+
@dir = File.dirname __FILE__
|
47
|
+
@host = '127.0.0.1'
|
48
|
+
__fork_server
|
49
|
+
|
50
|
+
@smallfile = "#{@dir}/small"
|
51
|
+
@small = File.open @smallfile
|
52
|
+
@small_data = File.read @smallfile
|
53
|
+
|
54
|
+
File.open( "#{@dir}/large.gz") do |f|
|
55
|
+
gzipped = Zlib::GzipReader.new f
|
56
|
+
@large_data = gzipped.read
|
57
|
+
end
|
58
|
+
@largefile = "/tmp/sendfiletest"
|
59
|
+
@large = File.open @largefile, 'w+'
|
60
|
+
@large.write @large_data
|
61
|
+
@large.flush
|
62
|
+
end
|
63
|
+
|
64
|
+
def teardown
|
65
|
+
@small.close
|
66
|
+
@large.close
|
67
|
+
File.unlink @largefile
|
68
|
+
|
69
|
+
Process.kill 'KILL', @pid
|
70
|
+
Process.wait
|
71
|
+
end
|
72
|
+
|
73
|
+
def __do_sendfile file, off=nil, count=nil
|
74
|
+
s = TCPSocket.new @host, @port
|
75
|
+
yield s if block_given?
|
76
|
+
sent = s.sendfile file, off, count
|
77
|
+
s.close
|
78
|
+
len = @rd.read( 4).unpack( "N")[0]
|
79
|
+
read = @rd.read len
|
80
|
+
[ sent, read ]
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_blocking_full_small
|
84
|
+
sent, read = __do_sendfile( @small)
|
85
|
+
assert_equal @small_data.size, sent
|
86
|
+
assert_equal @small_data.size, read.size
|
87
|
+
assert_equal @small_data, read
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_nonblocking_full_small
|
91
|
+
sent, read = __do_sendfile( @small) { |s| s.nonblock = true }
|
92
|
+
assert_equal @small_data.size, sent
|
93
|
+
assert_equal @small_data.size, read.size
|
94
|
+
assert_equal @small_data, read
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_blocking_full_large
|
98
|
+
sent, read = __do_sendfile( @large)
|
99
|
+
assert_equal @large_data.size, sent
|
100
|
+
assert_equal @large_data.size, read.size
|
101
|
+
assert_equal @large_data, read
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_nonblocking_full_large
|
105
|
+
sent, read = __do_sendfile( @large) { |s| s.nonblock = true }
|
106
|
+
assert_equal @large_data.size, sent
|
107
|
+
assert_equal @large_data.size, read.size
|
108
|
+
assert_equal @large_data, read
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_blocking_from_offset
|
112
|
+
data = @large_data[4096..-1]
|
113
|
+
sent, read = __do_sendfile( @large, 4096)
|
114
|
+
assert_equal data.size, sent
|
115
|
+
assert_equal data.size, read.size
|
116
|
+
assert_equal data, read
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_blocking_partial_from_beginning
|
120
|
+
data = @large_data[0, 1048576]
|
121
|
+
sent, read = __do_sendfile( @large, 0, 1048576)
|
122
|
+
assert_equal data.size, sent
|
123
|
+
assert_equal data.size, read.size
|
124
|
+
assert_equal data, read
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_blocking_partial_from_middle
|
128
|
+
data = @large_data[2048, 1048576]
|
129
|
+
sent, read = __do_sendfile( @large, 2048, 1048576)
|
130
|
+
assert_equal data.size, sent
|
131
|
+
assert_equal data.size, read.size
|
132
|
+
assert_equal data, read
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_blocking_partial_to_end
|
136
|
+
data = @large_data[-1048576, 1048576]
|
137
|
+
sent, read = __do_sendfile( @large, @large_data.size - 1048576, 1048576)
|
138
|
+
assert_equal data.size, sent
|
139
|
+
assert_equal data.size, read.size
|
140
|
+
assert_equal data, read
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_nonblocking_from_offset
|
144
|
+
data = @large_data[4096..-1]
|
145
|
+
sent, read = __do_sendfile( @large, 4096) { |s| s.nonblock = true }
|
146
|
+
assert_equal data.size, sent
|
147
|
+
assert_equal data.size, read.size
|
148
|
+
assert_equal data, read
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_nonblocking_partial_from_beginning
|
152
|
+
data = @large_data[0, 1048576]
|
153
|
+
sent, read = __do_sendfile( @large, 0, 1048576) { |s| s.nonblock = true }
|
154
|
+
assert_equal data.size, sent
|
155
|
+
assert_equal data.size, read.size
|
156
|
+
assert_equal data, read
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_nonblocking_partial_from_middle
|
160
|
+
data = @large_data[2048, 1048576]
|
161
|
+
sent, read = __do_sendfile( @large, 2048, 1048576) { |s| s.nonblock = true }
|
162
|
+
assert_equal data.size, sent
|
163
|
+
assert_equal data.size, read.size
|
164
|
+
assert_equal data, read
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_nonblocking_partial_to_end
|
168
|
+
data = @large_data[-1048576, 1048576]
|
169
|
+
sent, read = __do_sendfile( @large, @large_data.size - 1048576, 1048576) { |s| s.nonblock = true }
|
170
|
+
assert_equal data.size, sent
|
171
|
+
assert_equal data.size, read.size
|
172
|
+
assert_equal data, read
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_sendfile_nonblock
|
176
|
+
c, s = UNIXSocket.pair
|
177
|
+
nr_sent = 0
|
178
|
+
assert_raises(Errno::EAGAIN) do
|
179
|
+
loop do
|
180
|
+
nr_sent += c.sendfile_nonblock @small
|
181
|
+
end
|
182
|
+
end
|
183
|
+
c.close
|
184
|
+
nr_read = s.read.size
|
185
|
+
s.close
|
186
|
+
assert_equal nr_read, nr_sent
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_trysendfile
|
190
|
+
c, s = UNIXSocket.pair
|
191
|
+
nr_sent = 0
|
192
|
+
case rv = c.trysendfile(@small)
|
193
|
+
when :wait_writable
|
194
|
+
break
|
195
|
+
when Integer
|
196
|
+
nr_sent += rv
|
197
|
+
else
|
198
|
+
raise "Unexpected return: #{rv.inspect}"
|
199
|
+
end while true
|
200
|
+
assert nr_sent > 0, "nr_sent: #{nr_sent} <= 0"
|
201
|
+
c.close
|
202
|
+
nr_read = s.read.size
|
203
|
+
s.close
|
204
|
+
assert_equal nr_read, nr_sent
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_tempfile
|
208
|
+
tmp = Tempfile.new ''
|
209
|
+
tmp.write @small_data
|
210
|
+
tmp.rewind
|
211
|
+
sent, read = __do_sendfile(tmp)
|
212
|
+
assert_equal @small_data.size, sent
|
213
|
+
assert_equal @small_data.size, read.size
|
214
|
+
assert_equal @small_data, read
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_invalid_file
|
218
|
+
assert_raises(TypeError) { __do_sendfile(:hello) }
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_sendfile_too_big_eof
|
222
|
+
sent = read = nil
|
223
|
+
count = @small_data.size * 2
|
224
|
+
s = TCPSocket.new @host, @port
|
225
|
+
sent = s.sendfile @small, nil, count
|
226
|
+
assert_raises(EOFError) do
|
227
|
+
s.sendfile @small, sent, 1
|
228
|
+
end
|
229
|
+
s.close
|
230
|
+
len = @rd.read( 4).unpack( "N")[0]
|
231
|
+
read = @rd.read len
|
232
|
+
assert_equal @small_data.size, sent
|
233
|
+
assert_equal @small_data.size, read.size
|
234
|
+
assert_equal @small_data, read
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_sendfile_nonblock_eof
|
238
|
+
s = TCPSocket.new @host, @port
|
239
|
+
off = @small_data.size
|
240
|
+
assert_raises(EOFError) do
|
241
|
+
s.sendfile_nonblock @small, off, 1
|
242
|
+
end
|
243
|
+
s.close
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_trysendfile_eof
|
247
|
+
s = TCPSocket.new @host, @port
|
248
|
+
off = @small_data.size
|
249
|
+
assert_nil s.trysendfile(@small, off, 1)
|
250
|
+
s.close
|
251
|
+
end
|
252
|
+
end # class TestSendfile
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kgio-sendfile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Wong
|
8
|
+
- Toby DiPasquale
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-13 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: "This is a (hopefully temporary) fork of the original \"sendfile\" gem
|
15
|
+
for\nRuby 2.2.0dev compatibility. Only Linux (and maybe FreeBSD) are\nsupported
|
16
|
+
in this version.\n\nAllows Ruby programs to access sendfile(2) functionality on
|
17
|
+
\nany IO object. Works on Linux, Solaris, FreeBSD and Darwin with\nblocking and
|
18
|
+
non-blocking sockets.\n"
|
19
|
+
email: kgio@librelist.org
|
20
|
+
executables: []
|
21
|
+
extensions:
|
22
|
+
- ext/extconf.rb
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.textile
|
25
|
+
files:
|
26
|
+
- ChangeLog
|
27
|
+
- FILES
|
28
|
+
- LICENSE
|
29
|
+
- README.textile
|
30
|
+
- ext/extconf.rb
|
31
|
+
- ext/sendfile.c
|
32
|
+
- sendfile.gemspec
|
33
|
+
- test/large.gz
|
34
|
+
- test/small
|
35
|
+
- test/test_sendfile.rb
|
36
|
+
homepage: http://bogomips.org/ruby-sendfile.git
|
37
|
+
licenses: []
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.8.0
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 2.2.2
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: Ruby interface to sendfile(2) system call
|
59
|
+
test_files:
|
60
|
+
- test/test_sendfile.rb
|