kgio-sendfile 1.2.1.1
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.
- 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
|