kgio 2.7.4 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/ext/kgio/accept.c +3 -33
- data/ext/kgio/connect.c +122 -23
- data/ext/kgio/extconf.rb +4 -0
- data/ext/kgio/kgio.h +23 -0
- data/ext/kgio/kgio_ext.c +35 -0
- data/ext/kgio/read_write.c +309 -1
- data/ext/kgio/tryopen.c +4 -1
- data/test/lib_read_write.rb +129 -0
- data/test/lib_server_accept.rb +4 -4
- data/test/test_poll.rb +3 -5
- data/test/test_socket.rb +14 -0
- data/test/test_tfo.rb +70 -0
- data/test/test_unix_client_read_server_write.rb +1 -1
- metadata +7 -3
data/GIT-VERSION-GEN
CHANGED
data/ext/kgio/accept.c
CHANGED
@@ -79,15 +79,6 @@ static VALUE xaccept(void *ptr)
|
|
79
79
|
#ifdef HAVE_RB_THREAD_BLOCKING_REGION
|
80
80
|
# include <time.h>
|
81
81
|
# include "blocking_io_region.h"
|
82
|
-
/*
|
83
|
-
* Try to use a (real) blocking accept() since that can prevent
|
84
|
-
* thundering herds under Linux:
|
85
|
-
* http://www.citi.umich.edu/projects/linux-scalability/reports/accept.html
|
86
|
-
*
|
87
|
-
* So we periodically disable non-blocking, but not too frequently
|
88
|
-
* because other processes may set non-blocking (especially during
|
89
|
-
* a process upgrade) with Rainbows! concurrency model changes.
|
90
|
-
*/
|
91
82
|
static int thread_accept(struct accept_args *a, int force_nonblock)
|
92
83
|
{
|
93
84
|
if (force_nonblock)
|
@@ -95,28 +86,6 @@ static int thread_accept(struct accept_args *a, int force_nonblock)
|
|
95
86
|
return (int)rb_thread_io_blocking_region(xaccept, a, a->fd);
|
96
87
|
}
|
97
88
|
|
98
|
-
static void set_blocking_or_block(int fd)
|
99
|
-
{
|
100
|
-
static time_t last_set_blocking;
|
101
|
-
time_t now = time(NULL);
|
102
|
-
|
103
|
-
if (last_set_blocking == 0) {
|
104
|
-
last_set_blocking = now;
|
105
|
-
(void)rb_io_wait_readable(fd);
|
106
|
-
} else if ((now - last_set_blocking) <= 5) {
|
107
|
-
(void)rb_io_wait_readable(fd);
|
108
|
-
} else {
|
109
|
-
int flags = fcntl(fd, F_GETFL);
|
110
|
-
if (flags == -1)
|
111
|
-
rb_sys_fail("fcntl(F_GETFL)");
|
112
|
-
if (flags & O_NONBLOCK) {
|
113
|
-
flags = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
|
114
|
-
if (flags == -1)
|
115
|
-
rb_sys_fail("fcntl(F_SETFL)");
|
116
|
-
}
|
117
|
-
last_set_blocking = now;
|
118
|
-
}
|
119
|
-
}
|
120
89
|
#else /* ! HAVE_RB_THREAD_BLOCKING_REGION */
|
121
90
|
# include <rubysig.h>
|
122
91
|
static int thread_accept(struct accept_args *a, int force_nonblock)
|
@@ -134,7 +103,6 @@ static int thread_accept(struct accept_args *a, int force_nonblock)
|
|
134
103
|
TRAP_END;
|
135
104
|
return rv;
|
136
105
|
}
|
137
|
-
#define set_blocking_or_block(fd) (void)rb_io_wait_readable(fd)
|
138
106
|
#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
|
139
107
|
|
140
108
|
static void
|
@@ -209,7 +177,8 @@ retry:
|
|
209
177
|
return Qnil;
|
210
178
|
a->fd = my_fileno(a->accept_io);
|
211
179
|
errno = EAGAIN;
|
212
|
-
|
180
|
+
(void)rb_io_wait_readable(a->fd);
|
181
|
+
/* fall-through to EINTR case */
|
213
182
|
#ifdef ECONNABORTED
|
214
183
|
case ECONNABORTED:
|
215
184
|
#endif /* ECONNABORTED */
|
@@ -217,6 +186,7 @@ retry:
|
|
217
186
|
case EPROTO:
|
218
187
|
#endif /* EPROTO */
|
219
188
|
case EINTR:
|
189
|
+
/* raise IOError if closed during sleep */
|
220
190
|
a->fd = my_fileno(a->accept_io);
|
221
191
|
goto retry;
|
222
192
|
case ENOMEM:
|
data/ext/kgio/connect.c
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#include "kgio.h"
|
2
|
+
#include "my_fileno.h"
|
2
3
|
#include "sock_for_fd.h"
|
4
|
+
#include "blocking_io_region.h"
|
3
5
|
|
4
6
|
static void close_fail(int fd, const char *msg)
|
5
7
|
{
|
@@ -26,8 +28,8 @@ static int MY_SOCK_STREAM =
|
|
26
28
|
# define rb_fd_fix_cloexec(fd) for (;0;)
|
27
29
|
#endif /* HAVE_RB_FD_FIX_CLOEXEC */
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
+
/* try to use SOCK_NONBLOCK and SOCK_CLOEXEC */
|
32
|
+
static int my_socket(int domain)
|
31
33
|
{
|
32
34
|
int fd;
|
33
35
|
|
@@ -61,6 +63,14 @@ retry:
|
|
61
63
|
rb_fd_fix_cloexec(fd);
|
62
64
|
}
|
63
65
|
|
66
|
+
return fd;
|
67
|
+
}
|
68
|
+
|
69
|
+
static VALUE
|
70
|
+
my_connect(VALUE klass, int io_wait, int domain, void *addr, socklen_t addrlen)
|
71
|
+
{
|
72
|
+
int fd = my_socket(domain);
|
73
|
+
|
64
74
|
if (connect(fd, addr, addrlen) == -1) {
|
65
75
|
if (errno == EINPROGRESS) {
|
66
76
|
VALUE io = sock_for_fd(klass, fd);
|
@@ -76,10 +86,10 @@ retry:
|
|
76
86
|
return sock_for_fd(klass, fd);
|
77
87
|
}
|
78
88
|
|
79
|
-
static
|
89
|
+
static void
|
90
|
+
tcp_getaddr(struct addrinfo *hints, struct sockaddr_storage *addr,
|
91
|
+
VALUE ip, VALUE port)
|
80
92
|
{
|
81
|
-
struct addrinfo hints;
|
82
|
-
struct sockaddr_storage addr;
|
83
93
|
int rc;
|
84
94
|
struct addrinfo *res;
|
85
95
|
const char *ipname = StringValuePtr(ip);
|
@@ -93,28 +103,105 @@ static VALUE tcp_connect(VALUE klass, VALUE ip, VALUE port, int io_wait)
|
|
93
103
|
rc = snprintf(ipport, sizeof(ipport), "%u", uport);
|
94
104
|
if (rc >= (int)sizeof(ipport) || rc <= 0)
|
95
105
|
rb_raise(rb_eArgError, "invalid TCP port: %u", uport);
|
96
|
-
memset(
|
97
|
-
hints
|
98
|
-
hints
|
99
|
-
hints
|
106
|
+
memset(hints, 0, sizeof(struct addrinfo));
|
107
|
+
hints->ai_family = AF_UNSPEC;
|
108
|
+
hints->ai_socktype = SOCK_STREAM;
|
109
|
+
hints->ai_protocol = IPPROTO_TCP;
|
100
110
|
/* disallow non-deterministic DNS lookups */
|
101
|
-
hints
|
111
|
+
hints->ai_flags = AI_NUMERICHOST;
|
102
112
|
|
103
|
-
rc = getaddrinfo(ipname, ipport,
|
113
|
+
rc = getaddrinfo(ipname, ipport, hints, &res);
|
104
114
|
if (rc != 0)
|
105
115
|
rb_raise(rb_eArgError, "getaddrinfo(%s:%s): %s",
|
106
116
|
ipname, ipport, gai_strerror(rc));
|
107
117
|
|
108
118
|
/* copy needed data and free ASAP to avoid needing rb_ensure */
|
109
|
-
hints
|
110
|
-
hints
|
111
|
-
memcpy(
|
119
|
+
hints->ai_family = res->ai_family;
|
120
|
+
hints->ai_addrlen = res->ai_addrlen;
|
121
|
+
memcpy(addr, res->ai_addr, res->ai_addrlen);
|
112
122
|
freeaddrinfo(res);
|
123
|
+
}
|
124
|
+
|
125
|
+
static VALUE tcp_connect(VALUE klass, VALUE ip, VALUE port, int io_wait)
|
126
|
+
{
|
127
|
+
struct addrinfo hints;
|
128
|
+
struct sockaddr_storage addr;
|
129
|
+
|
130
|
+
tcp_getaddr(&hints, &addr, ip, port);
|
113
131
|
|
114
132
|
return my_connect(klass, io_wait, hints.ai_family,
|
115
133
|
&addr, hints.ai_addrlen);
|
116
134
|
}
|
117
135
|
|
136
|
+
static struct sockaddr *sockaddr_from(socklen_t *addrlen, VALUE addr)
|
137
|
+
{
|
138
|
+
if (TYPE(addr) == T_STRING) {
|
139
|
+
*addrlen = (socklen_t)RSTRING_LEN(addr);
|
140
|
+
return (struct sockaddr *)(RSTRING_PTR(addr));
|
141
|
+
}
|
142
|
+
rb_raise(rb_eTypeError, "invalid address");
|
143
|
+
return NULL;
|
144
|
+
}
|
145
|
+
|
146
|
+
#if defined(MSG_FASTOPEN) && defined(HAVE_RB_THREAD_BLOCKING_REGION)
|
147
|
+
#ifndef HAVE_RB_STR_SUBSEQ
|
148
|
+
#define rb_str_subseq rb_str_substr
|
149
|
+
#endif
|
150
|
+
struct tfo_args {
|
151
|
+
int fd;
|
152
|
+
void *buf;
|
153
|
+
size_t buflen;
|
154
|
+
struct sockaddr *addr;
|
155
|
+
socklen_t addrlen;
|
156
|
+
};
|
157
|
+
|
158
|
+
static VALUE tfo_sendto(void *_a)
|
159
|
+
{
|
160
|
+
struct tfo_args *a = _a;
|
161
|
+
ssize_t w;
|
162
|
+
|
163
|
+
w = sendto(a->fd, a->buf, a->buflen, MSG_FASTOPEN, a->addr, a->addrlen);
|
164
|
+
return (VALUE)w;
|
165
|
+
}
|
166
|
+
|
167
|
+
/*
|
168
|
+
* call-seq:
|
169
|
+
*
|
170
|
+
* s = Kgio::Socket.new(:INET, :STREAM)
|
171
|
+
* addr = Socket.pack_sockaddr_in(80, "example.com")
|
172
|
+
* s.kgio_fastopen("hello world", addr) -> nil
|
173
|
+
*
|
174
|
+
* Starts a TCP connection using TCP Fast Open. This uses a blocking
|
175
|
+
* sendto() syscall and is only available on Ruby 1.9 or later.
|
176
|
+
* This raises exceptions (including Errno::EINPROGRESS/Errno::EAGAIN)
|
177
|
+
* on errors. Using this is only recommended for blocking sockets.
|
178
|
+
*
|
179
|
+
* Timeouts may be set with setsockopt:
|
180
|
+
*
|
181
|
+
* s.setsockopt(:SOCKET, :SNDTIMEO, [1,0].pack("l_l_"))
|
182
|
+
*/
|
183
|
+
static VALUE fastopen(VALUE sock, VALUE buf, VALUE addr)
|
184
|
+
{
|
185
|
+
struct tfo_args a;
|
186
|
+
VALUE str = (TYPE(buf) == T_STRING) ? buf : rb_obj_as_string(buf);
|
187
|
+
ssize_t w;
|
188
|
+
|
189
|
+
a.fd = my_fileno(sock);
|
190
|
+
a.buf = RSTRING_PTR(str);
|
191
|
+
a.buflen = (size_t)RSTRING_LEN(str);
|
192
|
+
a.addr = sockaddr_from(&a.addrlen, addr);
|
193
|
+
|
194
|
+
/* n.b. rb_thread_blocking_region preserves errno */
|
195
|
+
w = (ssize_t)rb_thread_io_blocking_region(tfo_sendto, &a, a.fd);
|
196
|
+
if (w < 0)
|
197
|
+
rb_sys_fail("sendto");
|
198
|
+
if ((size_t)w == a.buflen)
|
199
|
+
return Qnil;
|
200
|
+
|
201
|
+
return rb_str_subseq(str, w, a.buflen - w);
|
202
|
+
}
|
203
|
+
#endif /* MSG_FASTOPEN */
|
204
|
+
|
118
205
|
/*
|
119
206
|
* call-seq:
|
120
207
|
*
|
@@ -209,14 +296,8 @@ static VALUE stream_connect(VALUE klass, VALUE addr, int io_wait)
|
|
209
296
|
{
|
210
297
|
int domain;
|
211
298
|
socklen_t addrlen;
|
212
|
-
struct sockaddr *sockaddr;
|
299
|
+
struct sockaddr *sockaddr = sockaddr_from(&addrlen, addr);
|
213
300
|
|
214
|
-
if (TYPE(addr) == T_STRING) {
|
215
|
-
sockaddr = (struct sockaddr *)(RSTRING_PTR(addr));
|
216
|
-
addrlen = (socklen_t)RSTRING_LEN(addr);
|
217
|
-
} else {
|
218
|
-
rb_raise(rb_eTypeError, "invalid address");
|
219
|
-
}
|
220
301
|
switch (((struct sockaddr_storage *)(sockaddr))->ss_family) {
|
221
302
|
case AF_UNIX: domain = PF_UNIX; break;
|
222
303
|
case AF_INET: domain = PF_INET; break;
|
@@ -247,6 +328,21 @@ static VALUE kgio_connect(VALUE klass, VALUE addr)
|
|
247
328
|
return stream_connect(klass, addr, 1);
|
248
329
|
}
|
249
330
|
|
331
|
+
/*
|
332
|
+
* If passed one argument, this is identical to Kgio::Socket.connect.
|
333
|
+
* If passed two or three arguments, it uses its superclass method:
|
334
|
+
*
|
335
|
+
* Socket.new(domain, socktype [, protocol ])
|
336
|
+
*/
|
337
|
+
static VALUE kgio_new(int argc, VALUE *argv, VALUE klass)
|
338
|
+
{
|
339
|
+
if (argc == 1)
|
340
|
+
/* backwards compat, the only way for kgio <= 2.7.4 */
|
341
|
+
return stream_connect(klass, argv[0], 1);
|
342
|
+
|
343
|
+
return rb_call_super(argc, argv);
|
344
|
+
}
|
345
|
+
|
250
346
|
/* call-seq:
|
251
347
|
*
|
252
348
|
* addr = Socket.pack_sockaddr_in(80, 'example.com')
|
@@ -282,9 +378,12 @@ void init_kgio_connect(void)
|
|
282
378
|
*/
|
283
379
|
cKgio_Socket = rb_define_class_under(mKgio, "Socket", cSocket);
|
284
380
|
rb_include_module(cKgio_Socket, mSocketMethods);
|
285
|
-
rb_define_singleton_method(cKgio_Socket, "new",
|
381
|
+
rb_define_singleton_method(cKgio_Socket, "new", kgio_new, -1);
|
382
|
+
rb_define_singleton_method(cKgio_Socket, "connect", kgio_connect, 1);
|
286
383
|
rb_define_singleton_method(cKgio_Socket, "start", kgio_start, 1);
|
287
|
-
|
384
|
+
#if defined(MSG_FASTOPEN) && defined(HAVE_RB_THREAD_BLOCKING_REGION)
|
385
|
+
rb_define_method(cKgio_Socket, "kgio_fastopen", fastopen, 2);
|
386
|
+
#endif
|
288
387
|
/*
|
289
388
|
* Document-class: Kgio::TCPSocket
|
290
389
|
*
|
data/ext/kgio/extconf.rb
CHANGED
@@ -23,6 +23,8 @@ have_type("struct sockaddr_storage", %w(sys/types.h sys/socket.h)) or
|
|
23
23
|
have_func('accept4', %w(sys/socket.h))
|
24
24
|
have_header("sys/select.h")
|
25
25
|
|
26
|
+
have_func("writev", "sys/uio.h")
|
27
|
+
|
26
28
|
if have_header('ruby/io.h')
|
27
29
|
rubyio = %w(ruby.h ruby/io.h)
|
28
30
|
have_struct_member("rb_io_t", "fd", rubyio)
|
@@ -49,5 +51,7 @@ have_func('rb_thread_io_blocking_region')
|
|
49
51
|
have_func('rb_str_set_len')
|
50
52
|
have_func('rb_time_interval')
|
51
53
|
have_func('rb_wait_for_single_fd')
|
54
|
+
have_func('rb_str_subseq')
|
55
|
+
have_func('rb_ary_subseq')
|
52
56
|
|
53
57
|
create_makefile('kgio_ext')
|
data/ext/kgio/kgio.h
CHANGED
@@ -49,4 +49,27 @@ VALUE kgio_call_wait_readable(VALUE io);
|
|
49
49
|
#ifndef HAVE_RB_UPDATE_MAX_FD
|
50
50
|
# define rb_update_max_fd(fd) for (;0;)
|
51
51
|
#endif
|
52
|
+
|
53
|
+
/*
|
54
|
+
* 2012/12/13 - Linux 3.7 was released on 2012/12/10 with TFO.
|
55
|
+
* Headers distributed with glibc will take some time to catch up and
|
56
|
+
* be officially released. Most GNU/Linux distros will take a few months
|
57
|
+
* to a year longer. "Enterprise" distros will probably take 5-7 years.
|
58
|
+
* So keep these until 2017 at least...
|
59
|
+
*/
|
60
|
+
#ifdef __linux__
|
61
|
+
# ifndef MSG_FASTOPEN
|
62
|
+
# define MSG_FASTOPEN 0x20000000 /* for clients */
|
63
|
+
# endif
|
64
|
+
# ifndef TCP_FASTOPEN
|
65
|
+
# define TCP_FASTOPEN 23 /* for listeners */
|
66
|
+
# endif
|
67
|
+
/* we _may_ have TFO support */
|
68
|
+
# define KGIO_TFO_MAYBE (1)
|
69
|
+
#else /* rely entirely on standard system headers */
|
70
|
+
# define KGIO_TFO_MAYBE (0)
|
71
|
+
#endif
|
72
|
+
|
73
|
+
extern unsigned kgio_tfo;
|
74
|
+
|
52
75
|
#endif /* KGIO_H */
|
data/ext/kgio/kgio_ext.c
CHANGED
@@ -1,7 +1,42 @@
|
|
1
1
|
#include "kgio.h"
|
2
|
+
#include <sys/utsname.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
/* true if TCP Fast Open is usable */
|
5
|
+
unsigned kgio_tfo;
|
6
|
+
|
7
|
+
static void tfo_maybe(void)
|
8
|
+
{
|
9
|
+
VALUE mKgio = rb_define_module("Kgio");
|
10
|
+
|
11
|
+
/* Deal with the case where system headers have not caught up */
|
12
|
+
if (KGIO_TFO_MAYBE) {
|
13
|
+
/* Ensure Linux 3.7 or later for TCP_FASTOPEN */
|
14
|
+
struct utsname buf;
|
15
|
+
unsigned maj, min;
|
16
|
+
|
17
|
+
if (uname(&buf) != 0)
|
18
|
+
rb_sys_fail("uname");
|
19
|
+
if (sscanf(buf.release, "%u.%u", &maj, &min) != 2)
|
20
|
+
return;
|
21
|
+
if (maj < 3 || (maj == 3 && min < 7))
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
25
|
+
/*
|
26
|
+
* KGIO_TFO_MAYBE will be false if a distro backports TFO
|
27
|
+
* to a pre-3.7 kernel, but includes the necessary constants
|
28
|
+
* in system headers
|
29
|
+
*/
|
30
|
+
#if defined(MSG_FASTOPEN) && defined(TCP_FASTOPEN)
|
31
|
+
rb_define_const(mKgio, "TCP_FASTOPEN", INT2NUM(TCP_FASTOPEN));
|
32
|
+
rb_define_const(mKgio, "MSG_FASTOPEN", INT2NUM(MSG_FASTOPEN));
|
33
|
+
kgio_tfo = 1;
|
34
|
+
#endif
|
35
|
+
}
|
2
36
|
|
3
37
|
void Init_kgio_ext(void)
|
4
38
|
{
|
39
|
+
tfo_maybe();
|
5
40
|
init_kgio_wait();
|
6
41
|
init_kgio_read_write();
|
7
42
|
init_kgio_connect();
|
data/ext/kgio/read_write.c
CHANGED
@@ -1,9 +1,33 @@
|
|
1
1
|
#include "kgio.h"
|
2
2
|
#include "my_fileno.h"
|
3
3
|
#include "nonblock.h"
|
4
|
+
#ifdef HAVE_WRITEV
|
5
|
+
# include <sys/uio.h>
|
6
|
+
# define USE_WRITEV 1
|
7
|
+
#else
|
8
|
+
# define USE_WRITEV 0
|
9
|
+
static ssize_t assert_writev(int fd, void* iov, int len)
|
10
|
+
{
|
11
|
+
assert(0 && "you should not try to call writev");
|
12
|
+
return -1;
|
13
|
+
}
|
14
|
+
# define writev assert_writev
|
15
|
+
#endif
|
4
16
|
static VALUE sym_wait_readable, sym_wait_writable;
|
5
17
|
static VALUE eErrno_EPIPE, eErrno_ECONNRESET;
|
6
18
|
static ID id_set_backtrace;
|
19
|
+
#ifndef HAVE_RB_STR_SUBSEQ
|
20
|
+
#define rb_str_subseq rb_str_substr
|
21
|
+
#endif
|
22
|
+
|
23
|
+
#ifndef HAVE_RB_ARY_SUBSEQ
|
24
|
+
static inline VALUE my_ary_subseq(VALUE ary, long idx, long len)
|
25
|
+
{
|
26
|
+
VALUE args[2] = {LONG2FIX(idx), LONG2FIX(len)};
|
27
|
+
return rb_ary_aref(2, args, ary);
|
28
|
+
}
|
29
|
+
#define rb_ary_subseq my_ary_subseq
|
30
|
+
#endif
|
7
31
|
|
8
32
|
/*
|
9
33
|
* we know MSG_DONTWAIT works properly on all stream sockets under Linux
|
@@ -338,7 +362,7 @@ done:
|
|
338
362
|
a->ptr = RSTRING_PTR(a->buf) + written;
|
339
363
|
return -1;
|
340
364
|
} else if (written > 0) {
|
341
|
-
a->buf =
|
365
|
+
a->buf = rb_str_subseq(a->buf, written, a->len);
|
342
366
|
} else {
|
343
367
|
a->buf = sym_wait_writable;
|
344
368
|
}
|
@@ -403,6 +427,253 @@ static VALUE kgio_trywrite(VALUE io, VALUE str)
|
|
403
427
|
return my_write(io, str, 0);
|
404
428
|
}
|
405
429
|
|
430
|
+
#ifndef HAVE_WRITEV
|
431
|
+
#define iovec my_iovec
|
432
|
+
struct my_iovec {
|
433
|
+
void *iov_base;
|
434
|
+
size_t iov_len;
|
435
|
+
};
|
436
|
+
#endif
|
437
|
+
|
438
|
+
/* tests for choosing following constants were done on Linux 3.0 x86_64
|
439
|
+
* (Ubuntu 12.04) Core i3 i3-2330M slowed to 1600MHz
|
440
|
+
* testing script https://gist.github.com/2850641
|
441
|
+
* fill free to make more thorough testing and choose better value
|
442
|
+
*/
|
443
|
+
|
444
|
+
/* test shows that its meaningless to set WRITEV_MEMLIMIT more that 1M
|
445
|
+
* even when tcp_wmem set to relatively high value (2M) (in fact, it becomes
|
446
|
+
* even slower). 512K performs a bit better in average case. */
|
447
|
+
#define WRITEV_MEMLIMIT (512*1024)
|
448
|
+
/* same test shows that custom_writev is faster than glibc writev when
|
449
|
+
* average string is smaller than ~500 bytes and slower when average strings
|
450
|
+
* is greater then ~600 bytes. 512 bytes were choosen cause current compilers
|
451
|
+
* turns x/512 into x>>9 */
|
452
|
+
#define WRITEV_IMPL_THRESHOLD 512
|
453
|
+
|
454
|
+
static unsigned int iov_max = 1024; /* this could be overriden in init */
|
455
|
+
|
456
|
+
struct io_args_v {
|
457
|
+
VALUE io;
|
458
|
+
VALUE buf;
|
459
|
+
VALUE vec_buf;
|
460
|
+
struct iovec *vec;
|
461
|
+
unsigned long iov_cnt;
|
462
|
+
size_t batch_len;
|
463
|
+
int something_written;
|
464
|
+
int fd;
|
465
|
+
};
|
466
|
+
|
467
|
+
static ssize_t custom_writev(int fd, const struct iovec *vec, unsigned int iov_cnt, size_t total_len)
|
468
|
+
{
|
469
|
+
unsigned int i;
|
470
|
+
ssize_t result;
|
471
|
+
char *buf, *curbuf;
|
472
|
+
const struct iovec *curvec = vec;
|
473
|
+
|
474
|
+
/* we do not want to use ruby's xmalloc because
|
475
|
+
* it can fire GC, and we'll free buffer shortly anyway */
|
476
|
+
curbuf = buf = malloc(total_len);
|
477
|
+
if (buf == NULL) return -1;
|
478
|
+
|
479
|
+
for (i = 0; i < iov_cnt; i++, curvec++) {
|
480
|
+
memcpy(curbuf, curvec->iov_base, curvec->iov_len);
|
481
|
+
curbuf += curvec->iov_len;
|
482
|
+
}
|
483
|
+
|
484
|
+
result = write(fd, buf, total_len);
|
485
|
+
|
486
|
+
/* well, it seems that `free` could not change errno
|
487
|
+
* but lets save it anyway */
|
488
|
+
i = errno;
|
489
|
+
free(buf);
|
490
|
+
errno = i;
|
491
|
+
|
492
|
+
return result;
|
493
|
+
}
|
494
|
+
|
495
|
+
static void prepare_writev(struct io_args_v *a, VALUE io, VALUE ary)
|
496
|
+
{
|
497
|
+
a->io = io;
|
498
|
+
a->fd = my_fileno(io);
|
499
|
+
a->something_written = 0;
|
500
|
+
|
501
|
+
if (TYPE(ary) == T_ARRAY)
|
502
|
+
/* rb_ary_subseq will not copy array unless it modified */
|
503
|
+
a->buf = rb_ary_subseq(ary, 0, RARRAY_LEN(ary));
|
504
|
+
else
|
505
|
+
a->buf = rb_Array(ary);
|
506
|
+
|
507
|
+
a->vec_buf = rb_str_new(0, 0);
|
508
|
+
a->vec = NULL;
|
509
|
+
}
|
510
|
+
|
511
|
+
static void fill_iovec(struct io_args_v *a)
|
512
|
+
{
|
513
|
+
unsigned long i;
|
514
|
+
struct iovec *curvec;
|
515
|
+
|
516
|
+
a->iov_cnt = RARRAY_LEN(a->buf);
|
517
|
+
a->batch_len = 0;
|
518
|
+
if (a->iov_cnt == 0) return;
|
519
|
+
if (a->iov_cnt > iov_max) a->iov_cnt = iov_max;
|
520
|
+
rb_str_resize(a->vec_buf, sizeof(struct iovec) * a->iov_cnt);
|
521
|
+
curvec = a->vec = (struct iovec*)RSTRING_PTR(a->vec_buf);
|
522
|
+
|
523
|
+
for (i=0; i < a->iov_cnt; i++, curvec++) {
|
524
|
+
/* rb_ary_store could reallocate array,
|
525
|
+
* so that ought to use RARRAY_PTR */
|
526
|
+
VALUE str = RARRAY_PTR(a->buf)[i];
|
527
|
+
long str_len, next_len;
|
528
|
+
|
529
|
+
if (TYPE(str) != T_STRING) {
|
530
|
+
str = rb_obj_as_string(str);
|
531
|
+
rb_ary_store(a->buf, i, str);
|
532
|
+
}
|
533
|
+
|
534
|
+
str_len = RSTRING_LEN(str);
|
535
|
+
|
536
|
+
/* lets limit total memory to write,
|
537
|
+
* but always take first string */
|
538
|
+
next_len = a->batch_len + str_len;
|
539
|
+
if (i && next_len > WRITEV_MEMLIMIT) {
|
540
|
+
a->iov_cnt = i;
|
541
|
+
break;
|
542
|
+
}
|
543
|
+
a->batch_len = next_len;
|
544
|
+
|
545
|
+
curvec->iov_base = RSTRING_PTR(str);
|
546
|
+
curvec->iov_len = str_len;
|
547
|
+
}
|
548
|
+
}
|
549
|
+
|
550
|
+
static long trim_writev_buffer(struct io_args_v *a, long n)
|
551
|
+
{
|
552
|
+
long i;
|
553
|
+
long ary_len = RARRAY_LEN(a->buf);
|
554
|
+
VALUE *elem = RARRAY_PTR(a->buf);
|
555
|
+
|
556
|
+
if (n == (long)a->batch_len) {
|
557
|
+
i = a->iov_cnt;
|
558
|
+
n = 0;
|
559
|
+
} else {
|
560
|
+
for (i = 0; n && i < ary_len; i++, elem++) {
|
561
|
+
n -= RSTRING_LEN(*elem);
|
562
|
+
if (n < 0) break;
|
563
|
+
}
|
564
|
+
}
|
565
|
+
|
566
|
+
/* all done */
|
567
|
+
if (i == ary_len) {
|
568
|
+
assert(n == 0 && "writev system call is broken");
|
569
|
+
a->buf = Qnil;
|
570
|
+
return 0;
|
571
|
+
}
|
572
|
+
|
573
|
+
/* partially done, remove fully-written buffers */
|
574
|
+
if (i > 0)
|
575
|
+
a->buf = rb_ary_subseq(a->buf, i, ary_len - i);
|
576
|
+
|
577
|
+
/* setup+replace partially written buffer */
|
578
|
+
if (n < 0) {
|
579
|
+
VALUE str = RARRAY_PTR(a->buf)[0];
|
580
|
+
long str_len = RSTRING_LEN(str);
|
581
|
+
str = rb_str_subseq(str, str_len + n, -n);
|
582
|
+
rb_ary_store(a->buf, 0, str);
|
583
|
+
}
|
584
|
+
return RARRAY_LEN(a->buf);
|
585
|
+
}
|
586
|
+
|
587
|
+
static int writev_check(struct io_args_v *a, long n, const char *msg, int io_wait)
|
588
|
+
{
|
589
|
+
if (n >= 0) {
|
590
|
+
if (n > 0) a->something_written = 1;
|
591
|
+
return trim_writev_buffer(a, n);
|
592
|
+
} else if (n == -1) {
|
593
|
+
if (errno == EINTR) {
|
594
|
+
a->fd = my_fileno(a->io);
|
595
|
+
return -1;
|
596
|
+
}
|
597
|
+
if (errno == EAGAIN) {
|
598
|
+
if (io_wait) {
|
599
|
+
(void)kgio_call_wait_writable(a->io);
|
600
|
+
return -1;
|
601
|
+
} else if (!a->something_written) {
|
602
|
+
a->buf = sym_wait_writable;
|
603
|
+
}
|
604
|
+
return 0;
|
605
|
+
}
|
606
|
+
wr_sys_fail(msg);
|
607
|
+
}
|
608
|
+
return 0;
|
609
|
+
}
|
610
|
+
|
611
|
+
static VALUE my_writev(VALUE io, VALUE str, int io_wait)
|
612
|
+
{
|
613
|
+
struct io_args_v a;
|
614
|
+
long n;
|
615
|
+
|
616
|
+
prepare_writev(&a, io, str);
|
617
|
+
set_nonblocking(a.fd);
|
618
|
+
|
619
|
+
do {
|
620
|
+
fill_iovec(&a);
|
621
|
+
if (a.iov_cnt == 0)
|
622
|
+
n = 0;
|
623
|
+
else if (a.iov_cnt == 1)
|
624
|
+
n = (long)write(a.fd, a.vec[0].iov_base, a.vec[0].iov_len);
|
625
|
+
/* for big strings use library function */
|
626
|
+
else if (USE_WRITEV && a.batch_len / WRITEV_IMPL_THRESHOLD > a.iov_cnt)
|
627
|
+
n = (long)writev(a.fd, a.vec, a.iov_cnt);
|
628
|
+
else
|
629
|
+
n = (long)custom_writev(a.fd, a.vec, a.iov_cnt, a.batch_len);
|
630
|
+
} while (writev_check(&a, n, "writev", io_wait) != 0);
|
631
|
+
rb_str_resize(a.vec_buf, 0);
|
632
|
+
|
633
|
+
if (TYPE(a.buf) != T_SYMBOL)
|
634
|
+
kgio_autopush_write(io);
|
635
|
+
return a.buf;
|
636
|
+
}
|
637
|
+
|
638
|
+
/*
|
639
|
+
* call-seq:
|
640
|
+
*
|
641
|
+
* io.kgio_writev(array) -> nil
|
642
|
+
*
|
643
|
+
* Returns nil when the write completes.
|
644
|
+
*
|
645
|
+
* This may block and call any method defined to +kgio_wait_writable+
|
646
|
+
* for the class.
|
647
|
+
*
|
648
|
+
* Note: it uses +Array()+ semantic for converting argument, so that
|
649
|
+
* it will succeed if you pass something else.
|
650
|
+
*/
|
651
|
+
static VALUE kgio_writev(VALUE io, VALUE ary)
|
652
|
+
{
|
653
|
+
return my_writev(io, ary, 1);
|
654
|
+
}
|
655
|
+
|
656
|
+
/*
|
657
|
+
* call-seq:
|
658
|
+
*
|
659
|
+
* io.kgio_trywritev(array) -> nil, Array or :wait_writable
|
660
|
+
*
|
661
|
+
* Returns nil if the write was completed in full.
|
662
|
+
*
|
663
|
+
* Returns an Array of strings containing the unwritten portion
|
664
|
+
* if EAGAIN was encountered, but some portion was successfully written.
|
665
|
+
*
|
666
|
+
* Returns :wait_writable if EAGAIN is encountered and nothing
|
667
|
+
* was written.
|
668
|
+
*
|
669
|
+
* Note: it uses +Array()+ semantic for converting argument, so that
|
670
|
+
* it will succeed if you pass something else.
|
671
|
+
*/
|
672
|
+
static VALUE kgio_trywritev(VALUE io, VALUE ary)
|
673
|
+
{
|
674
|
+
return my_writev(io, ary, 0);
|
675
|
+
}
|
676
|
+
|
406
677
|
#ifdef USE_MSG_DONTWAIT
|
407
678
|
/*
|
408
679
|
* This method behaves like Kgio::PipeMethods#kgio_write, except
|
@@ -486,6 +757,26 @@ static VALUE s_trywrite(VALUE mod, VALUE io, VALUE str)
|
|
486
757
|
return my_write(io, str, 0);
|
487
758
|
}
|
488
759
|
|
760
|
+
/*
|
761
|
+
* call-seq:
|
762
|
+
*
|
763
|
+
* Kgio.trywritev(io, array) -> nil, Array or :wait_writable
|
764
|
+
*
|
765
|
+
* Returns nil if the write was completed in full.
|
766
|
+
*
|
767
|
+
* Returns a Array of strings containing the unwritten portion if EAGAIN
|
768
|
+
* was encountered, but some portion was successfully written.
|
769
|
+
*
|
770
|
+
* Returns :wait_writable if EAGAIN is encountered and nothing
|
771
|
+
* was written.
|
772
|
+
*
|
773
|
+
* Maybe used in place of PipeMethods#kgio_trywritev for non-Kgio objects
|
774
|
+
*/
|
775
|
+
static VALUE s_trywritev(VALUE mod, VALUE io, VALUE ary)
|
776
|
+
{
|
777
|
+
return kgio_trywritev(io, ary);
|
778
|
+
}
|
779
|
+
|
489
780
|
void init_kgio_read_write(void)
|
490
781
|
{
|
491
782
|
VALUE mPipeMethods, mSocketMethods;
|
@@ -497,6 +788,7 @@ void init_kgio_read_write(void)
|
|
497
788
|
|
498
789
|
rb_define_singleton_method(mKgio, "tryread", s_tryread, -1);
|
499
790
|
rb_define_singleton_method(mKgio, "trywrite", s_trywrite, 2);
|
791
|
+
rb_define_singleton_method(mKgio, "trywritev", s_trywritev, 2);
|
500
792
|
rb_define_singleton_method(mKgio, "trypeek", s_trypeek, -1);
|
501
793
|
|
502
794
|
/*
|
@@ -510,8 +802,10 @@ void init_kgio_read_write(void)
|
|
510
802
|
rb_define_method(mPipeMethods, "kgio_read", kgio_read, -1);
|
511
803
|
rb_define_method(mPipeMethods, "kgio_read!", kgio_read_bang, -1);
|
512
804
|
rb_define_method(mPipeMethods, "kgio_write", kgio_write, 1);
|
805
|
+
rb_define_method(mPipeMethods, "kgio_writev", kgio_writev, 1);
|
513
806
|
rb_define_method(mPipeMethods, "kgio_tryread", kgio_tryread, -1);
|
514
807
|
rb_define_method(mPipeMethods, "kgio_trywrite", kgio_trywrite, 1);
|
808
|
+
rb_define_method(mPipeMethods, "kgio_trywritev", kgio_trywritev, 1);
|
515
809
|
|
516
810
|
/*
|
517
811
|
* Document-module: Kgio::SocketMethods
|
@@ -524,8 +818,10 @@ void init_kgio_read_write(void)
|
|
524
818
|
rb_define_method(mSocketMethods, "kgio_read", kgio_recv, -1);
|
525
819
|
rb_define_method(mSocketMethods, "kgio_read!", kgio_recv_bang, -1);
|
526
820
|
rb_define_method(mSocketMethods, "kgio_write", kgio_send, 1);
|
821
|
+
rb_define_method(mSocketMethods, "kgio_writev", kgio_writev, 1);
|
527
822
|
rb_define_method(mSocketMethods, "kgio_tryread", kgio_tryrecv, -1);
|
528
823
|
rb_define_method(mSocketMethods, "kgio_trywrite", kgio_trysend, 1);
|
824
|
+
rb_define_method(mSocketMethods, "kgio_trywritev", kgio_trywritev, 1);
|
529
825
|
rb_define_method(mSocketMethods, "kgio_trypeek", kgio_trypeek, -1);
|
530
826
|
rb_define_method(mSocketMethods, "kgio_peek", kgio_peek, -1);
|
531
827
|
|
@@ -541,4 +837,16 @@ void init_kgio_read_write(void)
|
|
541
837
|
eErrno_ECONNRESET = rb_const_get(rb_mErrno, rb_intern("ECONNRESET"));
|
542
838
|
rb_include_module(mPipeMethods, mWaiters);
|
543
839
|
rb_include_module(mSocketMethods, mWaiters);
|
840
|
+
|
841
|
+
#ifdef HAVE_WRITEV
|
842
|
+
{
|
843
|
+
# ifdef IOV_MAX
|
844
|
+
unsigned int sys_iov_max = IOV_MAX;
|
845
|
+
# else
|
846
|
+
unsigned int sys_iov_max = sysconf(_SC_IOV_MAX);
|
847
|
+
# endif
|
848
|
+
if (sys_iov_max < iov_max)
|
849
|
+
iov_max = sys_iov_max;
|
850
|
+
}
|
851
|
+
#endif
|
544
852
|
}
|
data/ext/kgio/tryopen.c
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
#include <sys/types.h>
|
15
15
|
#include <sys/stat.h>
|
16
16
|
#include <fcntl.h>
|
17
|
+
#include <errno.h>
|
17
18
|
#include "set_file_path.h"
|
18
19
|
#include "ancient_ruby.h"
|
19
20
|
|
@@ -42,7 +43,7 @@ static VALUE nogvl_open(void *ptr)
|
|
42
43
|
# include "rubysig.h"
|
43
44
|
typedef void rb_unblock_function_t(void *);
|
44
45
|
typedef VALUE rb_blocking_function_t(void *);
|
45
|
-
static VALUE
|
46
|
+
static VALUE my_thread_blocking_region(
|
46
47
|
rb_blocking_function_t *fn, void *data1,
|
47
48
|
rb_unblock_function_t *ubf, void *data2)
|
48
49
|
{
|
@@ -54,6 +55,8 @@ static VALUE rb_thread_blocking_region(
|
|
54
55
|
|
55
56
|
return rv;
|
56
57
|
}
|
58
|
+
#define rb_thread_blocking_region(fn,data1,ubf,data2) \
|
59
|
+
my_thread_blocking_region((fn),(data1),(ubf),(data2))
|
57
60
|
#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
|
58
61
|
|
59
62
|
/*
|
data/test/lib_read_write.rb
CHANGED
@@ -21,6 +21,14 @@ module LibReadWriteTest
|
|
21
21
|
assert_nil @wr.kgio_trywrite("")
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_writev_empty
|
25
|
+
assert_nil @wr.kgio_writev([])
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_trywritev_empty
|
29
|
+
assert_nil @wr.kgio_trywritev([])
|
30
|
+
end
|
31
|
+
|
24
32
|
def test_read_zero
|
25
33
|
assert_equal "", @rd.kgio_read(0)
|
26
34
|
buf = "foo"
|
@@ -116,6 +124,28 @@ module LibReadWriteTest
|
|
116
124
|
assert false, "should never get here (line:#{__LINE__})"
|
117
125
|
end
|
118
126
|
|
127
|
+
def test_writev_closed
|
128
|
+
@rd.close
|
129
|
+
begin
|
130
|
+
loop { @wr.kgio_writev ["HI"] }
|
131
|
+
rescue Errno::EPIPE, Errno::ECONNRESET => e
|
132
|
+
assert_equal [], e.backtrace
|
133
|
+
return
|
134
|
+
end
|
135
|
+
assert false, "should never get here (line:#{__LINE__})"
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_trywritev_closed
|
139
|
+
@rd.close
|
140
|
+
begin
|
141
|
+
loop { @wr.kgio_trywritev ["HI"] }
|
142
|
+
rescue Errno::EPIPE, Errno::ECONNRESET => e
|
143
|
+
assert_equal [], e.backtrace
|
144
|
+
return
|
145
|
+
end
|
146
|
+
assert false, "should never get here (line:#{__LINE__})"
|
147
|
+
end
|
148
|
+
|
119
149
|
def test_trywrite_full
|
120
150
|
buf = "\302\251" * 1024 * 1024
|
121
151
|
buf2 = ""
|
@@ -153,6 +183,43 @@ module LibReadWriteTest
|
|
153
183
|
assert_equal '8ff79d8115f9fe38d18be858c66aa08a1cc27a66', t.value
|
154
184
|
end
|
155
185
|
|
186
|
+
def test_trywritev_full
|
187
|
+
buf = ["\302\251" * 128] * 8 * 1024
|
188
|
+
buf2 = ""
|
189
|
+
dig = Digest::SHA1.new
|
190
|
+
t = Thread.new do
|
191
|
+
sleep 1
|
192
|
+
nr = 0
|
193
|
+
begin
|
194
|
+
dig.update(@rd.readpartial(4096, buf2))
|
195
|
+
nr += buf2.size
|
196
|
+
rescue EOFError
|
197
|
+
break
|
198
|
+
rescue => e
|
199
|
+
end while true
|
200
|
+
dig.hexdigest
|
201
|
+
end
|
202
|
+
50.times do
|
203
|
+
wr = buf
|
204
|
+
begin
|
205
|
+
rv = @wr.kgio_trywritev(wr)
|
206
|
+
case rv
|
207
|
+
when Array
|
208
|
+
wr = rv
|
209
|
+
when :wait_readable
|
210
|
+
assert false, "should never get here line=#{__LINE__}"
|
211
|
+
when :wait_writable
|
212
|
+
IO.select(nil, [ @wr ])
|
213
|
+
else
|
214
|
+
wr = false
|
215
|
+
end
|
216
|
+
end while wr
|
217
|
+
end
|
218
|
+
@wr.close
|
219
|
+
t.join
|
220
|
+
assert_equal '8ff79d8115f9fe38d18be858c66aa08a1cc27a66', t.value
|
221
|
+
end
|
222
|
+
|
156
223
|
def test_write_conv
|
157
224
|
assert_equal nil, @wr.kgio_write(10)
|
158
225
|
assert_equal "10", @rd.kgio_read(2)
|
@@ -175,6 +242,7 @@ module LibReadWriteTest
|
|
175
242
|
|
176
243
|
def test_tryread_too_much
|
177
244
|
assert_equal nil, @wr.kgio_trywrite("hi")
|
245
|
+
assert_equal @rd, @rd.kgio_wait_readable
|
178
246
|
assert_equal "hi", @rd.kgio_tryread(4)
|
179
247
|
end
|
180
248
|
|
@@ -214,6 +282,19 @@ module LibReadWriteTest
|
|
214
282
|
tmp.each { |count| assert_equal nil, count }
|
215
283
|
end
|
216
284
|
|
285
|
+
def test_trywritev_return_wait_writable
|
286
|
+
tmp = []
|
287
|
+
tmp << @wr.kgio_trywritev(["HI"]) until tmp[-1] == :wait_writable
|
288
|
+
assert :wait_writable === tmp[-1]
|
289
|
+
assert(!(:wait_readable === tmp[-1]))
|
290
|
+
assert_equal :wait_writable, tmp.pop
|
291
|
+
assert tmp.size > 0
|
292
|
+
penultimate = tmp.pop
|
293
|
+
assert(penultimate == "I" || penultimate == nil)
|
294
|
+
assert tmp.size > 0
|
295
|
+
tmp.each { |count| assert_equal nil, count }
|
296
|
+
end
|
297
|
+
|
217
298
|
def test_tryread_extra_buf_eagain_clears_buffer
|
218
299
|
tmp = "hello world"
|
219
300
|
rv = @rd.kgio_tryread(2, tmp)
|
@@ -248,6 +329,36 @@ module LibReadWriteTest
|
|
248
329
|
assert_equal buf, readed
|
249
330
|
end
|
250
331
|
|
332
|
+
def test_monster_trywritev
|
333
|
+
buf, start = [], 0
|
334
|
+
while start < RANDOM_BLOB.size
|
335
|
+
s = RANDOM_BLOB[start, 1000]
|
336
|
+
start += s.size
|
337
|
+
buf << s
|
338
|
+
end
|
339
|
+
rv = @wr.kgio_trywritev(buf)
|
340
|
+
assert_kind_of Array, rv
|
341
|
+
rv = rv.join
|
342
|
+
assert rv.size < RANDOM_BLOB.size
|
343
|
+
@rd.nonblock = false
|
344
|
+
assert_equal(RANDOM_BLOB, @rd.read(RANDOM_BLOB.size - rv.size) + rv)
|
345
|
+
end
|
346
|
+
|
347
|
+
def test_monster_writev
|
348
|
+
buf, start = [], 0
|
349
|
+
while start < RANDOM_BLOB.size
|
350
|
+
s = RANDOM_BLOB[start, 10000]
|
351
|
+
start += s.size
|
352
|
+
buf << s
|
353
|
+
end
|
354
|
+
thr = Thread.new { @wr.kgio_writev(buf) }
|
355
|
+
@rd.nonblock = false
|
356
|
+
readed = @rd.read(RANDOM_BLOB.size)
|
357
|
+
thr.join
|
358
|
+
assert_nil thr.value
|
359
|
+
assert_equal RANDOM_BLOB, readed
|
360
|
+
end
|
361
|
+
|
251
362
|
def test_monster_write_wait_writable
|
252
363
|
@wr.instance_variable_set :@nr, 0
|
253
364
|
def @wr.kgio_wait_writable
|
@@ -256,6 +367,7 @@ module LibReadWriteTest
|
|
256
367
|
end
|
257
368
|
buf = "." * 1024 * 1024 * 10
|
258
369
|
thr = Thread.new { @wr.kgio_write(buf) }
|
370
|
+
Thread.pass until thr.stop?
|
259
371
|
readed = @rd.read(buf.size)
|
260
372
|
thr.join
|
261
373
|
assert_nil thr.value
|
@@ -263,6 +375,23 @@ module LibReadWriteTest
|
|
263
375
|
assert @wr.instance_variable_get(:@nr) > 0
|
264
376
|
end
|
265
377
|
|
378
|
+
def test_monster_writev_wait_writable
|
379
|
+
@wr.instance_variable_set :@nr, 0
|
380
|
+
def @wr.kgio_wait_writable
|
381
|
+
@nr += 1
|
382
|
+
IO.select(nil, [self])
|
383
|
+
end
|
384
|
+
buf = ["." * 1024] * 1024 * 10
|
385
|
+
buf_size = buf.inject(0){|c, s| c + s.size}
|
386
|
+
thr = Thread.new { @wr.kgio_writev(buf) }
|
387
|
+
Thread.pass until thr.stop?
|
388
|
+
readed = @rd.read(buf_size)
|
389
|
+
thr.join
|
390
|
+
assert_nil thr.value
|
391
|
+
assert_equal buf.join, readed
|
392
|
+
assert @wr.instance_variable_get(:@nr) > 0
|
393
|
+
end
|
394
|
+
|
266
395
|
def test_wait_readable_ruby_default
|
267
396
|
elapsed = 0
|
268
397
|
foo = nil
|
data/test/lib_server_accept.rb
CHANGED
@@ -47,7 +47,7 @@ module LibServerAccept
|
|
47
47
|
elapsed = Time.now - t0
|
48
48
|
assert_kind_of Kgio::Socket, b
|
49
49
|
assert_equal @host, b.kgio_addr
|
50
|
-
Process.kill(:
|
50
|
+
Process.kill(:KILL, pid)
|
51
51
|
Process.waitpid(pid)
|
52
52
|
assert elapsed >= 1, "elapsed: #{elapsed}"
|
53
53
|
end
|
@@ -60,7 +60,7 @@ module LibServerAccept
|
|
60
60
|
elapsed = Time.now - t0
|
61
61
|
assert_kind_of Kgio::Socket, b
|
62
62
|
assert_equal @host, b.kgio_addr
|
63
|
-
Process.kill(:
|
63
|
+
Process.kill(:KILL, pid)
|
64
64
|
Process.waitpid(pid)
|
65
65
|
assert elapsed >= 1, "elapsed: #{elapsed}"
|
66
66
|
|
@@ -70,7 +70,7 @@ module LibServerAccept
|
|
70
70
|
elapsed = Time.now - t0
|
71
71
|
assert_kind_of Kgio::Socket, b
|
72
72
|
assert_equal @host, b.kgio_addr
|
73
|
-
Process.kill(:
|
73
|
+
Process.kill(:KILL, pid)
|
74
74
|
Process.waitpid(pid)
|
75
75
|
assert elapsed >= 6, "elapsed: #{elapsed}"
|
76
76
|
|
@@ -80,7 +80,7 @@ module LibServerAccept
|
|
80
80
|
elapsed = Time.now - t0
|
81
81
|
assert_kind_of Kgio::Socket, b
|
82
82
|
assert_equal @host, b.kgio_addr
|
83
|
-
Process.kill(:
|
83
|
+
Process.kill(:KILL, pid)
|
84
84
|
Process.waitpid(pid)
|
85
85
|
assert elapsed >= 1, "elapsed: #{elapsed}"
|
86
86
|
end
|
data/test/test_poll.rb
CHANGED
@@ -126,9 +126,7 @@ class TestPoll < Test::Unit::TestCase
|
|
126
126
|
_, status = Process.waitpid2(pid)
|
127
127
|
assert status.success?, status.inspect
|
128
128
|
assert usr1 > 0, "usr1: #{usr1}"
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
trap(:USR1, "DEFAULT")
|
133
|
-
end
|
129
|
+
ensure
|
130
|
+
trap(:USR1, "DEFAULT")
|
131
|
+
end unless RUBY_PLATFORM =~ /kfreebsd-gnu/
|
134
132
|
end if Kgio.respond_to?(:poll)
|
data/test/test_socket.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'kgio'
|
3
|
+
|
4
|
+
class TestKgioSocket < Test::Unit::TestCase
|
5
|
+
def test_socket_args
|
6
|
+
s = Kgio::Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
7
|
+
assert_kind_of Socket, s
|
8
|
+
assert_instance_of Kgio::Socket, s
|
9
|
+
|
10
|
+
s = Kgio::Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
11
|
+
assert_kind_of Socket, s
|
12
|
+
assert_instance_of Kgio::Socket, s
|
13
|
+
end
|
14
|
+
end
|
data/test/test_tfo.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'kgio'
|
3
|
+
|
4
|
+
class TestTFO < Test::Unit::TestCase
|
5
|
+
def test_constants
|
6
|
+
if `uname -s`.chomp == "Linux" && `uname -r`.to_f >= 3.7
|
7
|
+
assert_equal 23, Kgio::TCP_FASTOPEN
|
8
|
+
assert_equal 0x20000000, Kgio::MSG_FASTOPEN
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def fastopen_ok?
|
13
|
+
if RUBY_PLATFORM =~ /linux/
|
14
|
+
tfo = File.read("/proc/sys/net/ipv4/tcp_fastopen").to_i
|
15
|
+
client_enable = 1
|
16
|
+
server_enable = 2
|
17
|
+
enable = client_enable | server_enable
|
18
|
+
(tfo & enable) == enable
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_tfo_client_server
|
25
|
+
unless fastopen_ok?
|
26
|
+
warn "TCP Fast Open not enabled on this system (check kernel docs)"
|
27
|
+
return
|
28
|
+
end
|
29
|
+
addr = '127.0.0.1'
|
30
|
+
qlen = 1024
|
31
|
+
s = Kgio::TCPServer.new(addr, 0)
|
32
|
+
s.setsockopt(:TCP, Kgio::TCP_FASTOPEN, qlen)
|
33
|
+
port = s.local_address.ip_port
|
34
|
+
addr = Socket.pack_sockaddr_in(port, addr)
|
35
|
+
c = Kgio::Socket.new(:INET, :STREAM)
|
36
|
+
assert_nil c.kgio_fastopen("HELLO", addr)
|
37
|
+
a = s.accept
|
38
|
+
assert_equal "HELLO", a.read(5)
|
39
|
+
c.close
|
40
|
+
a.close
|
41
|
+
|
42
|
+
# ensure empty sends work
|
43
|
+
c = Kgio::Socket.new(:INET, :STREAM)
|
44
|
+
assert_nil c.kgio_fastopen("", addr)
|
45
|
+
a = s.accept
|
46
|
+
Thread.new { c.close }
|
47
|
+
assert_nil a.read(1)
|
48
|
+
a.close
|
49
|
+
|
50
|
+
# try a monster packet
|
51
|
+
buf = 'x' * (1024 * 1024 * 320)
|
52
|
+
|
53
|
+
c = Kgio::Socket.new(:INET, :STREAM)
|
54
|
+
thr = Thread.new do
|
55
|
+
a = s.accept
|
56
|
+
assert_equal buf.size, a.read(buf.size).size
|
57
|
+
a.close
|
58
|
+
end
|
59
|
+
assert_nil c.kgio_fastopen(buf, addr)
|
60
|
+
thr.join
|
61
|
+
c.close
|
62
|
+
|
63
|
+
# allow timeouts
|
64
|
+
c = Kgio::Socket.new(:INET, :STREAM)
|
65
|
+
c.setsockopt(:SOCKET, :SNDTIMEO, [ 0, 10 ].pack("l_l_"))
|
66
|
+
unsent = c.kgio_fastopen(buf, addr)
|
67
|
+
c.close
|
68
|
+
assert_equal s.accept.read.size + unsent.size, buf.size
|
69
|
+
end if defined?(Addrinfo) && defined?(Kgio::TCP_FASTOPEN)
|
70
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kgio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.8.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-18 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! 'kgio provides non-blocking I/O methods for Ruby without raising
|
15
15
|
|
@@ -95,12 +95,14 @@ files:
|
|
95
95
|
- test/test_pipe_read_write.rb
|
96
96
|
- test/test_poll.rb
|
97
97
|
- test/test_singleton_read_write.rb
|
98
|
+
- test/test_socket.rb
|
98
99
|
- test/test_socketpair_read_write.rb
|
99
100
|
- test/test_tcp6_client_read_server_write.rb
|
100
101
|
- test/test_tcp_client_read_server_write.rb
|
101
102
|
- test/test_tcp_connect.rb
|
102
103
|
- test/test_tcp_server.rb
|
103
104
|
- test/test_tcp_server_read_client_write.rb
|
105
|
+
- test/test_tfo.rb
|
104
106
|
- test/test_tryopen.rb
|
105
107
|
- test/test_unix_client_read_server_write.rb
|
106
108
|
- test/test_unix_connect.rb
|
@@ -130,13 +132,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
132
|
version: '0'
|
131
133
|
requirements: []
|
132
134
|
rubyforge_project: rainbows
|
133
|
-
rubygems_version: 1.8.
|
135
|
+
rubygems_version: 1.8.23
|
134
136
|
signing_key:
|
135
137
|
specification_version: 3
|
136
138
|
summary: kinder, gentler I/O for Ruby
|
137
139
|
test_files:
|
138
140
|
- test/test_poll.rb
|
139
141
|
- test/test_peek.rb
|
142
|
+
- test/test_socket.rb
|
140
143
|
- test/test_default_wait.rb
|
141
144
|
- test/test_no_dns_on_tcp_connect.rb
|
142
145
|
- test/test_unix_connect.rb
|
@@ -144,6 +147,7 @@ test_files:
|
|
144
147
|
- test/test_unix_server.rb
|
145
148
|
- test/test_accept_flags.rb
|
146
149
|
- test/test_socketpair_read_write.rb
|
150
|
+
- test/test_tfo.rb
|
147
151
|
- test/test_tcp_server.rb
|
148
152
|
- test/test_unix_server_read_client_write.rb
|
149
153
|
- test/test_cross_thread_close.rb
|