kgio 2.7.4 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|