kgio 2.3.3 → 2.4.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/HACKING +3 -6
- data/ext/kgio/accept.c +141 -88
- data/ext/kgio/autopush.c +1 -0
- data/ext/kgio/broken_system_compat.h +62 -0
- data/ext/kgio/connect.c +1 -0
- data/ext/kgio/extconf.rb +6 -2
- data/ext/kgio/poll.c +58 -3
- data/ext/kgio/read_write.c +10 -4
- data/kgio.gemspec +1 -2
- data/test/lib_read_write.rb +36 -0
- data/test/lib_server_accept.rb +19 -0
- data/test/test_autopush.rb +1 -1
- data/test/test_cross_thread_close.rb +26 -0
- data/test/test_poll.rb +36 -11
- data/test/test_tcp_connect.rb +1 -1
- metadata +12 -12
data/GIT-VERSION-GEN
CHANGED
data/HACKING
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
=== Documentation
|
4
4
|
|
5
|
-
We use
|
6
|
-
|
7
|
-
the lack of RDoc-to-manpage converters we know about, we're writing
|
8
|
-
manpages in Markdown and converting to troff/HTML with Pandoc.
|
5
|
+
We use the latest version of {wrongdoc}[http://bogomips.org/wrongdoc] as
|
6
|
+
much as possible.
|
9
7
|
|
10
8
|
Please wrap documentation at 72 characters-per-line or less (long URLs
|
11
9
|
are exempt) so it is comfortably readable from terminals.
|
@@ -22,7 +20,7 @@ respective C APIs.
|
|
22
20
|
All of our C code should be compatible with all reasonably modern Unices
|
23
21
|
and should run on compilers supported by the versions of Ruby we target.
|
24
22
|
|
25
|
-
We will NEVER support non-Free platforms under any circumstances.
|
23
|
+
We will NEVER directly support non-Free platforms under any circumstances.
|
26
24
|
|
27
25
|
Our C code follows K&R indentation style (hard tabs, tabs are always 8
|
28
26
|
characters wide) and NOT the indentation style of Matz Ruby.
|
@@ -63,4 +61,3 @@ Without RubyGems (via setup.rb):
|
|
63
61
|
|
64
62
|
It is not at all recommended to mix a RubyGems installation with an
|
65
63
|
installation done without RubyGems, however.
|
66
|
-
|
data/ext/kgio/accept.c
CHANGED
@@ -16,8 +16,11 @@ static int accept4_flags = SOCK_CLOEXEC | SOCK_NONBLOCK;
|
|
16
16
|
|
17
17
|
struct accept_args {
|
18
18
|
int fd;
|
19
|
+
int flags;
|
19
20
|
struct sockaddr *addr;
|
20
21
|
socklen_t *addrlen;
|
22
|
+
VALUE accept_io;
|
23
|
+
VALUE accepted_class;
|
21
24
|
};
|
22
25
|
|
23
26
|
/*
|
@@ -53,15 +56,19 @@ static VALUE get_accepted(VALUE klass)
|
|
53
56
|
return cClientSocket;
|
54
57
|
}
|
55
58
|
|
59
|
+
/*
|
60
|
+
* accept() wrapper that'll fall back on accept() if we were built on
|
61
|
+
* a system with accept4() but run on a system without accept4()
|
62
|
+
*/
|
56
63
|
static VALUE xaccept(void *ptr)
|
57
64
|
{
|
58
65
|
struct accept_args *a = ptr;
|
59
66
|
int rv;
|
60
67
|
|
61
|
-
rv = accept_fn(a->fd, a->addr, a->addrlen,
|
68
|
+
rv = accept_fn(a->fd, a->addr, a->addrlen, a->flags);
|
62
69
|
if (rv == -1 && errno == ENOSYS && accept_fn != my_accept4) {
|
63
70
|
accept_fn = my_accept4;
|
64
|
-
rv = accept_fn(a->fd, a->addr, a->addrlen,
|
71
|
+
rv = accept_fn(a->fd, a->addr, a->addrlen, a->flags);
|
65
72
|
}
|
66
73
|
|
67
74
|
return (VALUE)rv;
|
@@ -124,16 +131,56 @@ static int thread_accept(struct accept_args *a, int force_nonblock)
|
|
124
131
|
#define set_blocking_or_block(fd) (void)rb_io_wait_readable(fd)
|
125
132
|
#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
|
126
133
|
|
127
|
-
static
|
134
|
+
static void
|
135
|
+
prepare_accept(struct accept_args *a, VALUE self, int argc, const VALUE *argv)
|
128
136
|
{
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
137
|
+
a->fd = my_fileno(self);
|
138
|
+
a->accept_io = self;
|
139
|
+
|
140
|
+
switch (argc) {
|
141
|
+
case 2:
|
142
|
+
a->flags = NUM2INT(argv[1]);
|
143
|
+
a->accepted_class = NIL_P(argv[0]) ? cClientSocket : argv[0];
|
144
|
+
return;
|
145
|
+
case 0: /* default, legacy behavior */
|
146
|
+
a->flags = accept4_flags;
|
147
|
+
a->accepted_class = cClientSocket;
|
148
|
+
return;
|
149
|
+
case 1:
|
150
|
+
a->flags = accept4_flags;
|
151
|
+
a->accepted_class = NIL_P(argv[0]) ? cClientSocket : argv[0];
|
152
|
+
return;
|
153
|
+
}
|
133
154
|
|
134
155
|
rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc);
|
135
156
|
}
|
136
157
|
|
158
|
+
static VALUE in_addr_set(VALUE io, struct sockaddr_storage *addr, socklen_t len)
|
159
|
+
{
|
160
|
+
VALUE host;
|
161
|
+
int host_len, rc;
|
162
|
+
char *host_ptr;
|
163
|
+
|
164
|
+
switch (addr->ss_family) {
|
165
|
+
case AF_INET:
|
166
|
+
host_len = (long)INET_ADDRSTRLEN;
|
167
|
+
break;
|
168
|
+
case AF_INET6:
|
169
|
+
host_len = (long)INET6_ADDRSTRLEN;
|
170
|
+
break;
|
171
|
+
default:
|
172
|
+
rb_raise(rb_eRuntimeError, "unsupported address family");
|
173
|
+
}
|
174
|
+
host = rb_str_new(NULL, host_len);
|
175
|
+
host_ptr = RSTRING_PTR(host);
|
176
|
+
rc = getnameinfo((struct sockaddr *)addr, len,
|
177
|
+
host_ptr, host_len, NULL, 0, NI_NUMERICHOST);
|
178
|
+
if (rc != 0)
|
179
|
+
rb_raise(rb_eRuntimeError, "getnameinfo: %s", gai_strerror(rc));
|
180
|
+
rb_str_set_len(host, strlen(host_ptr));
|
181
|
+
return rb_ivar_set(io, iv_kgio_addr, host);
|
182
|
+
}
|
183
|
+
|
137
184
|
#if defined(__linux__)
|
138
185
|
# define post_accept kgio_autopush_accept
|
139
186
|
#else
|
@@ -141,24 +188,21 @@ static VALUE acceptor(int argc, const VALUE *argv)
|
|
141
188
|
#endif
|
142
189
|
|
143
190
|
static VALUE
|
144
|
-
my_accept(
|
145
|
-
struct sockaddr *addr, socklen_t *addrlen, int nonblock)
|
191
|
+
my_accept(struct accept_args *a, int force_nonblock)
|
146
192
|
{
|
147
|
-
int
|
193
|
+
int client_fd;
|
148
194
|
VALUE client_io;
|
149
|
-
|
195
|
+
int retried = 0;
|
150
196
|
|
151
|
-
a.fd = my_fileno(accept_io);
|
152
|
-
a.addr = addr;
|
153
|
-
a.addrlen = addrlen;
|
154
197
|
retry:
|
155
|
-
|
156
|
-
if (
|
198
|
+
client_fd = thread_accept(a, force_nonblock);
|
199
|
+
if (client_fd == -1) {
|
157
200
|
switch (errno) {
|
158
201
|
case EAGAIN:
|
159
|
-
if (
|
202
|
+
if (force_nonblock)
|
160
203
|
return Qnil;
|
161
|
-
|
204
|
+
a->fd = my_fileno(a->accept_io);
|
205
|
+
set_blocking_or_block(a->fd);
|
162
206
|
#ifdef ECONNABORTED
|
163
207
|
case ECONNABORTED:
|
164
208
|
#endif /* ECONNABORTED */
|
@@ -166,6 +210,7 @@ retry:
|
|
166
210
|
case EPROTO:
|
167
211
|
#endif /* EPROTO */
|
168
212
|
case EINTR:
|
213
|
+
a->fd = my_fileno(a->accept_io);
|
169
214
|
goto retry;
|
170
215
|
case ENOMEM:
|
171
216
|
case EMFILE:
|
@@ -173,47 +218,27 @@ retry:
|
|
173
218
|
#ifdef ENOBUFS
|
174
219
|
case ENOBUFS:
|
175
220
|
#endif /* ENOBUFS */
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
if (client == -1) {
|
181
|
-
if (errno == EINTR)
|
221
|
+
if (!retried) {
|
222
|
+
retried = 1;
|
223
|
+
errno = 0;
|
224
|
+
rb_gc();
|
182
225
|
goto retry;
|
226
|
+
}
|
227
|
+
default:
|
183
228
|
rb_sys_fail("accept");
|
184
229
|
}
|
185
230
|
}
|
186
|
-
client_io = sock_for_fd(
|
187
|
-
post_accept(accept_io, client_io);
|
231
|
+
client_io = sock_for_fd(a->accepted_class, client_fd);
|
232
|
+
post_accept(a->accept_io, client_io);
|
233
|
+
|
234
|
+
if (a->addr)
|
235
|
+
in_addr_set(client_io,
|
236
|
+
(struct sockaddr_storage *)a->addr, *a->addrlen);
|
237
|
+
else
|
238
|
+
rb_ivar_set(client_io, iv_kgio_addr, localhost);
|
188
239
|
return client_io;
|
189
240
|
}
|
190
241
|
|
191
|
-
static VALUE in_addr_set(VALUE io, struct sockaddr_storage *addr, socklen_t len)
|
192
|
-
{
|
193
|
-
VALUE host;
|
194
|
-
int host_len, rc;
|
195
|
-
char *host_ptr;
|
196
|
-
|
197
|
-
switch (addr->ss_family) {
|
198
|
-
case AF_INET:
|
199
|
-
host_len = (long)INET_ADDRSTRLEN;
|
200
|
-
break;
|
201
|
-
case AF_INET6:
|
202
|
-
host_len = (long)INET6_ADDRSTRLEN;
|
203
|
-
break;
|
204
|
-
default:
|
205
|
-
rb_raise(rb_eRuntimeError, "unsupported address family");
|
206
|
-
}
|
207
|
-
host = rb_str_new(NULL, host_len);
|
208
|
-
host_ptr = RSTRING_PTR(host);
|
209
|
-
rc = getnameinfo((struct sockaddr *)addr, len,
|
210
|
-
host_ptr, host_len, NULL, 0, NI_NUMERICHOST);
|
211
|
-
if (rc != 0)
|
212
|
-
rb_raise(rb_eRuntimeError, "getnameinfo: %s", gai_strerror(rc));
|
213
|
-
rb_str_set_len(host, strlen(host_ptr));
|
214
|
-
return rb_ivar_set(io, iv_kgio_addr, host);
|
215
|
-
}
|
216
|
-
|
217
242
|
/*
|
218
243
|
* call-seq:
|
219
244
|
*
|
@@ -239,6 +264,8 @@ static VALUE addr_bang(VALUE io)
|
|
239
264
|
*
|
240
265
|
* server = Kgio::TCPServer.new('0.0.0.0', 80)
|
241
266
|
* server.kgio_tryaccept -> Kgio::Socket or nil
|
267
|
+
* server.kgio_tryaccept(klass = MySocket) -> MySocket or nil
|
268
|
+
* server.kgio_tryaccept(nil, flags) -> Kgio::Socket or nil
|
242
269
|
*
|
243
270
|
* Initiates a non-blocking accept and returns a generic Kgio::Socket
|
244
271
|
* object with the kgio_addr attribute set to the IP address of the
|
@@ -246,21 +273,26 @@ static VALUE addr_bang(VALUE io)
|
|
246
273
|
*
|
247
274
|
* Returns nil on EAGAIN, and raises on other errors.
|
248
275
|
*
|
249
|
-
* An optional
|
250
|
-
* Kgio::Socket-class return value
|
276
|
+
* An optional +klass+ argument may be specified to override the
|
277
|
+
* Kgio::Socket-class on a successful return value.
|
278
|
+
*
|
279
|
+
* An optional +flags+ argument may also be specifed to override the
|
280
|
+
* value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+
|
281
|
+
* is a bitmask that may contain any combination of:
|
251
282
|
*
|
252
|
-
*
|
283
|
+
* - Fcntl::FD_CLOEXEC - close-on-exec flag
|
284
|
+
* - IO::NONBLOCK - non-blocking flag
|
253
285
|
*/
|
254
|
-
static VALUE tcp_tryaccept(int argc, VALUE *argv, VALUE
|
286
|
+
static VALUE tcp_tryaccept(int argc, VALUE *argv, VALUE self)
|
255
287
|
{
|
256
288
|
struct sockaddr_storage addr;
|
257
289
|
socklen_t addrlen = sizeof(struct sockaddr_storage);
|
258
|
-
|
259
|
-
VALUE rv = my_accept(io, klass, (struct sockaddr *)&addr, &addrlen, 1);
|
290
|
+
struct accept_args a;
|
260
291
|
|
261
|
-
|
262
|
-
|
263
|
-
|
292
|
+
a.addr = (struct sockaddr *)&addr;
|
293
|
+
a.addrlen = &addrlen;
|
294
|
+
prepare_accept(&a, self, argc, argv);
|
295
|
+
return my_accept(&a, 1);
|
264
296
|
}
|
265
297
|
|
266
298
|
/*
|
@@ -268,6 +300,8 @@ static VALUE tcp_tryaccept(int argc, VALUE *argv, VALUE io)
|
|
268
300
|
*
|
269
301
|
* server = Kgio::TCPServer.new('0.0.0.0', 80)
|
270
302
|
* server.kgio_accept -> Kgio::Socket or nil
|
303
|
+
* server.kgio_tryaccept -> Kgio::Socket or nil
|
304
|
+
* server.kgio_tryaccept(klass = MySocket) -> MySocket or nil
|
271
305
|
*
|
272
306
|
* Initiates a blocking accept and returns a generic Kgio::Socket
|
273
307
|
* object with the kgio_addr attribute set to the IP address of
|
@@ -276,20 +310,26 @@ static VALUE tcp_tryaccept(int argc, VALUE *argv, VALUE io)
|
|
276
310
|
* On Ruby implementations using native threads, this can use a blocking
|
277
311
|
* accept(2) (or accept4(2)) system call to avoid thundering herds.
|
278
312
|
*
|
279
|
-
* An optional
|
280
|
-
* Kgio::Socket-class return value
|
313
|
+
* An optional +klass+ argument may be specified to override the
|
314
|
+
* Kgio::Socket-class on a successful return value.
|
315
|
+
*
|
316
|
+
* An optional +flags+ argument may also be specifed to override the
|
317
|
+
* value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+
|
318
|
+
* is a bitmask that may contain any combination of:
|
281
319
|
*
|
282
|
-
*
|
320
|
+
* - Fcntl::FD_CLOEXEC - close-on-exec flag
|
321
|
+
* - IO::NONBLOCK - non-blocking flag
|
283
322
|
*/
|
284
|
-
static VALUE tcp_accept(int argc, VALUE *argv, VALUE
|
323
|
+
static VALUE tcp_accept(int argc, VALUE *argv, VALUE self)
|
285
324
|
{
|
286
325
|
struct sockaddr_storage addr;
|
287
326
|
socklen_t addrlen = sizeof(struct sockaddr_storage);
|
288
|
-
|
289
|
-
VALUE rv = my_accept(io, klass, (struct sockaddr *)&addr, &addrlen, 0);
|
327
|
+
struct accept_args a;
|
290
328
|
|
291
|
-
|
292
|
-
|
329
|
+
a.addr = (struct sockaddr *)&addr;
|
330
|
+
a.addrlen = &addrlen;
|
331
|
+
prepare_accept(&a, self, argc, argv);
|
332
|
+
return my_accept(&a, 0);
|
293
333
|
}
|
294
334
|
|
295
335
|
/*
|
@@ -297,26 +337,31 @@ static VALUE tcp_accept(int argc, VALUE *argv, VALUE io)
|
|
297
337
|
*
|
298
338
|
* server = Kgio::UNIXServer.new("/path/to/unix/socket")
|
299
339
|
* server.kgio_tryaccept -> Kgio::Socket or nil
|
340
|
+
* server.kgio_tryaccept(klass = MySocket) -> MySocket or nil
|
341
|
+
* server.kgio_tryaccept(nil, flags) -> Kgio::Socket or nil
|
300
342
|
*
|
301
343
|
* Initiates a non-blocking accept and returns a generic Kgio::Socket
|
302
344
|
* object with the kgio_addr attribute set (to the value of
|
303
345
|
* Kgio::LOCALHOST) on success.
|
304
346
|
*
|
305
|
-
*
|
347
|
+
* An optional +klass+ argument may be specified to override the
|
348
|
+
* Kgio::Socket-class on a successful return value.
|
306
349
|
*
|
307
|
-
* An optional
|
308
|
-
* Kgio
|
350
|
+
* An optional +flags+ argument may also be specifed to override the
|
351
|
+
* value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+
|
352
|
+
* is a bitmask that may contain any combination of:
|
309
353
|
*
|
310
|
-
*
|
354
|
+
* - Fcntl::FD_CLOEXEC - close-on-exec flag
|
355
|
+
* - IO::NONBLOCK - non-blocking flag
|
311
356
|
*/
|
312
|
-
static VALUE unix_tryaccept(int argc, VALUE *argv, VALUE
|
357
|
+
static VALUE unix_tryaccept(int argc, VALUE *argv, VALUE self)
|
313
358
|
{
|
314
|
-
|
315
|
-
VALUE rv = my_accept(io, klass, NULL, NULL, 1);
|
359
|
+
struct accept_args a;
|
316
360
|
|
317
|
-
|
318
|
-
|
319
|
-
|
361
|
+
a.addr = NULL;
|
362
|
+
a.addrlen = NULL;
|
363
|
+
prepare_accept(&a, self, argc, argv);
|
364
|
+
return my_accept(&a, 1);
|
320
365
|
}
|
321
366
|
|
322
367
|
/*
|
@@ -324,6 +369,8 @@ static VALUE unix_tryaccept(int argc, VALUE *argv, VALUE io)
|
|
324
369
|
*
|
325
370
|
* server = Kgio::UNIXServer.new("/path/to/unix/socket")
|
326
371
|
* server.kgio_accept -> Kgio::Socket or nil
|
372
|
+
* server.kgio_accept(klass = MySocket) -> MySocket or nil
|
373
|
+
* server.kgio_accept(nil, flags) -> Kgio::Socket or nil
|
327
374
|
*
|
328
375
|
* Initiates a blocking accept and returns a generic Kgio::Socket
|
329
376
|
* object with the kgio_addr attribute set (to the value of
|
@@ -332,18 +379,24 @@ static VALUE unix_tryaccept(int argc, VALUE *argv, VALUE io)
|
|
332
379
|
* On Ruby implementations using native threads, this can use a blocking
|
333
380
|
* accept(2) (or accept4(2)) system call to avoid thundering herds.
|
334
381
|
*
|
335
|
-
* An optional
|
336
|
-
* Kgio::Socket-class return value
|
382
|
+
* An optional +klass+ argument may be specified to override the
|
383
|
+
* Kgio::Socket-class on a successful return value.
|
384
|
+
*
|
385
|
+
* An optional +flags+ argument may also be specifed to override the
|
386
|
+
* value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+
|
387
|
+
* is a bitmask that may contain any combination of:
|
337
388
|
*
|
338
|
-
*
|
389
|
+
* - Fcntl::FD_CLOEXEC - close-on-exec flag
|
390
|
+
* - IO::NONBLOCK - non-blocking flag
|
339
391
|
*/
|
340
|
-
static VALUE unix_accept(int argc, VALUE *argv, VALUE
|
392
|
+
static VALUE unix_accept(int argc, VALUE *argv, VALUE self)
|
341
393
|
{
|
342
|
-
|
343
|
-
VALUE rv = my_accept(io, klass, NULL, NULL, 0);
|
394
|
+
struct accept_args a;
|
344
395
|
|
345
|
-
|
346
|
-
|
396
|
+
a.addr = NULL;
|
397
|
+
a.addrlen = NULL;
|
398
|
+
prepare_accept(&a, self, argc, argv);
|
399
|
+
return my_accept(&a, 0);
|
347
400
|
}
|
348
401
|
|
349
402
|
/*
|
@@ -384,7 +437,7 @@ static VALUE get_nonblock(VALUE mod)
|
|
384
437
|
* TCPServer#kgio_tryaccept,
|
385
438
|
* UNIXServer#kgio_accept,
|
386
439
|
* and UNIXServer#kgio_tryaccept
|
387
|
-
*
|
440
|
+
* default to being created with the FD_CLOEXEC file descriptor flag.
|
388
441
|
*
|
389
442
|
* This is on by default, as there is little reason to deal to enable
|
390
443
|
* it for client sockets on a socket server.
|
data/ext/kgio/autopush.c
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
/*
|
2
|
+
* this header includes functions to support broken systems
|
3
|
+
* without clock_gettime() or CLOCK_MONOTONIC
|
4
|
+
*/
|
5
|
+
|
6
|
+
#ifndef HAVE_TYPE_CLOCKID_T
|
7
|
+
typedef int clockid_t;
|
8
|
+
#endif
|
9
|
+
|
10
|
+
#ifndef HAVE_CLOCK_GETTIME
|
11
|
+
# ifndef CLOCK_REALTIME
|
12
|
+
# define CLOCK_REALTIME 0 /* whatever */
|
13
|
+
# endif
|
14
|
+
static int fake_clock_gettime(clockid_t clk_id, struct timespec *res)
|
15
|
+
{
|
16
|
+
struct timeval tv;
|
17
|
+
int r = gettimeofday(&tv, NULL);
|
18
|
+
|
19
|
+
assert(0 == r && "gettimeofday() broke!?");
|
20
|
+
res->tv_sec = tv.tv_sec;
|
21
|
+
res->tv_nsec = tv.tv_usec * 1000;
|
22
|
+
|
23
|
+
return r;
|
24
|
+
}
|
25
|
+
# define clock_gettime fake_clock_gettime
|
26
|
+
#endif /* broken systems w/o clock_gettime() */
|
27
|
+
|
28
|
+
/*
|
29
|
+
* UGH
|
30
|
+
* CLOCK_MONOTONIC is not guaranteed to be a macro, either
|
31
|
+
*/
|
32
|
+
#ifndef CLOCK_MONOTONIC
|
33
|
+
# if (!defined(_POSIX_MONOTONIC_CLOCK) || !defined(HAVE_CLOCK_MONOTONIC))
|
34
|
+
# define CLOCK_MONOTONIC CLOCK_REALTIME
|
35
|
+
# endif
|
36
|
+
#endif
|
37
|
+
|
38
|
+
/*
|
39
|
+
* Availability of a monotonic clock needs to be detected at runtime
|
40
|
+
* since we could've been built on a different system than we're run
|
41
|
+
* under.
|
42
|
+
*/
|
43
|
+
static clockid_t hopefully_CLOCK_MONOTONIC;
|
44
|
+
|
45
|
+
static int check_clock(void)
|
46
|
+
{
|
47
|
+
struct timespec now;
|
48
|
+
|
49
|
+
hopefully_CLOCK_MONOTONIC = CLOCK_MONOTONIC;
|
50
|
+
|
51
|
+
/* we can't check this reliably at compile time */
|
52
|
+
if (clock_gettime(CLOCK_MONOTONIC, &now) == 0)
|
53
|
+
return 1;
|
54
|
+
|
55
|
+
if (clock_gettime(CLOCK_REALTIME, &now) == 0) {
|
56
|
+
hopefully_CLOCK_MONOTONIC = CLOCK_REALTIME;
|
57
|
+
rb_warn("CLOCK_MONOTONIC not available, "
|
58
|
+
"falling back to CLOCK_REALTIME");
|
59
|
+
return 2;
|
60
|
+
}
|
61
|
+
return -1;
|
62
|
+
}
|
data/ext/kgio/connect.c
CHANGED
@@ -69,6 +69,7 @@ static VALUE tcp_connect(VALUE klass, VALUE ip, VALUE port, int io_wait)
|
|
69
69
|
rc = snprintf(ipport, sizeof(ipport), "%u", uport);
|
70
70
|
if (rc >= (int)sizeof(ipport) || rc <= 0)
|
71
71
|
rb_raise(rb_eArgError, "invalid TCP port: %u", uport);
|
72
|
+
memset(&hints, 0, sizeof(hints));
|
72
73
|
hints.ai_family = AF_UNSPEC;
|
73
74
|
hints.ai_socktype = SOCK_STREAM;
|
74
75
|
hints.ai_protocol = IPPROTO_TCP;
|
data/ext/kgio/extconf.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
$CPPFLAGS << ' -D_GNU_SOURCE'
|
3
3
|
$CPPFLAGS << ' -DPOSIX_C_SOURCE=1'
|
4
|
-
|
4
|
+
$CPPFLAGS += '-D_POSIX_C_SOURCE=200112L'
|
5
|
+
unless have_macro('CLOCK_MONOTONIC', 'time.h')
|
6
|
+
have_func('CLOCK_MONOTONIC', 'time.h')
|
7
|
+
end
|
8
|
+
have_type('clockid_t', 'time.h')
|
9
|
+
have_library('rt', 'clock_gettime', 'time.h')
|
5
10
|
have_func("poll", "poll.h")
|
6
11
|
have_func("getaddrinfo", %w(sys/types.h sys/socket.h netdb.h)) or
|
7
12
|
abort "getaddrinfo required"
|
@@ -30,5 +35,4 @@ have_func('rb_thread_blocking_region')
|
|
30
35
|
have_func('rb_thread_io_blocking_region')
|
31
36
|
have_func('rb_str_set_len')
|
32
37
|
|
33
|
-
dir_config('kgio')
|
34
38
|
create_makefile('kgio_ext')
|
data/ext/kgio/poll.c
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
#include "kgio.h"
|
2
2
|
#if defined(USE_KGIO_POLL)
|
3
|
+
#include <time.h>
|
4
|
+
#include "broken_system_compat.h"
|
3
5
|
#include <poll.h>
|
6
|
+
#include <errno.h>
|
4
7
|
#ifdef HAVE_RUBY_ST_H
|
5
8
|
# include <ruby/st.h>
|
6
9
|
#else
|
@@ -16,8 +19,43 @@ struct poll_args {
|
|
16
19
|
int timeout;
|
17
20
|
VALUE ios;
|
18
21
|
st_table *fd_to_io;
|
22
|
+
struct timespec start;
|
19
23
|
};
|
20
24
|
|
25
|
+
static int interrupted(void)
|
26
|
+
{
|
27
|
+
switch (errno) {
|
28
|
+
case EINTR:
|
29
|
+
#ifdef ERESTART
|
30
|
+
case ERESTART:
|
31
|
+
#endif
|
32
|
+
return 1;
|
33
|
+
}
|
34
|
+
return 0;
|
35
|
+
}
|
36
|
+
|
37
|
+
static int retryable(struct poll_args *a)
|
38
|
+
{
|
39
|
+
struct timespec ts;
|
40
|
+
|
41
|
+
if (a->timeout < 0)
|
42
|
+
return 1;
|
43
|
+
if (a->timeout == 0)
|
44
|
+
return 0;
|
45
|
+
|
46
|
+
clock_gettime(hopefully_CLOCK_MONOTONIC, &ts);
|
47
|
+
|
48
|
+
ts.tv_sec -= a->start.tv_sec;
|
49
|
+
ts.tv_nsec -= a->start.tv_nsec;
|
50
|
+
if (ts.tv_nsec < 0) {
|
51
|
+
ts.tv_sec--;
|
52
|
+
ts.tv_nsec += 1000000000;
|
53
|
+
}
|
54
|
+
a->timeout -= ts.tv_sec * 1000;
|
55
|
+
a->timeout -= ts.tv_nsec / 1000000;
|
56
|
+
return (a->timeout >= 0);
|
57
|
+
}
|
58
|
+
|
21
59
|
static int num2timeout(VALUE timeout)
|
22
60
|
{
|
23
61
|
switch (TYPE(timeout)) {
|
@@ -62,6 +100,7 @@ static int io_to_pollfd_i(VALUE key, VALUE value, VALUE args)
|
|
62
100
|
|
63
101
|
static void hash2pollfds(struct poll_args *a)
|
64
102
|
{
|
103
|
+
a->nfds = 0;
|
65
104
|
a->fds = xmalloc(sizeof(struct pollfd) * RHASH_SIZE(a->ios));
|
66
105
|
a->fd_to_io = st_init_numtable();
|
67
106
|
rb_hash_foreach(a->ios, io_to_pollfd_i, (VALUE)a);
|
@@ -70,6 +109,10 @@ static void hash2pollfds(struct poll_args *a)
|
|
70
109
|
static VALUE nogvl_poll(void *ptr)
|
71
110
|
{
|
72
111
|
struct poll_args *a = ptr;
|
112
|
+
|
113
|
+
if (a->timeout > 0)
|
114
|
+
clock_gettime(hopefully_CLOCK_MONOTONIC, &a->start);
|
115
|
+
|
73
116
|
return (VALUE)poll(a->fds, a->nfds, a->timeout);
|
74
117
|
}
|
75
118
|
|
@@ -98,10 +141,20 @@ static VALUE do_poll(VALUE args)
|
|
98
141
|
int nr;
|
99
142
|
|
100
143
|
Check_Type(a->ios, T_HASH);
|
101
|
-
hash2pollfds(a);
|
102
144
|
|
145
|
+
retry:
|
146
|
+
hash2pollfds(a);
|
103
147
|
nr = (int)rb_thread_blocking_region(nogvl_poll, a, RUBY_UBF_IO, NULL);
|
104
|
-
if (nr < 0)
|
148
|
+
if (nr < 0) {
|
149
|
+
if (interrupted()) {
|
150
|
+
if (retryable(a)) {
|
151
|
+
poll_free(args);
|
152
|
+
goto retry;
|
153
|
+
}
|
154
|
+
return Qnil;
|
155
|
+
}
|
156
|
+
rb_sys_fail("poll");
|
157
|
+
}
|
105
158
|
if (nr == 0) return Qnil;
|
106
159
|
|
107
160
|
return poll_result(nr, a);
|
@@ -146,7 +199,6 @@ static VALUE s_poll(int argc, VALUE *argv, VALUE self)
|
|
146
199
|
|
147
200
|
rb_scan_args(argc, argv, "11", &a.ios, &timeout);
|
148
201
|
a.timeout = num2timeout(timeout);
|
149
|
-
a.nfds = 0;
|
150
202
|
a.fds = NULL;
|
151
203
|
a.fd_to_io = NULL;
|
152
204
|
|
@@ -156,6 +208,9 @@ static VALUE s_poll(int argc, VALUE *argv, VALUE self)
|
|
156
208
|
void init_kgio_poll(void)
|
157
209
|
{
|
158
210
|
VALUE mKgio = rb_define_module("Kgio");
|
211
|
+
|
212
|
+
if (check_clock() < 0)
|
213
|
+
return;
|
159
214
|
rb_define_singleton_method(mKgio, "poll", s_poll, -1);
|
160
215
|
|
161
216
|
sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
|
data/ext/kgio/read_write.c
CHANGED
@@ -68,6 +68,7 @@ static void prepare_read(struct io_args *a, int argc, VALUE *argv, VALUE io)
|
|
68
68
|
a->buf = rb_str_new(NULL, a->len);
|
69
69
|
} else {
|
70
70
|
StringValue(a->buf);
|
71
|
+
rb_str_modify(a->buf);
|
71
72
|
rb_str_resize(a->buf, a->len);
|
72
73
|
}
|
73
74
|
a->ptr = RSTRING_PTR(a->buf);
|
@@ -76,14 +77,17 @@ static void prepare_read(struct io_args *a, int argc, VALUE *argv, VALUE io)
|
|
76
77
|
static int read_check(struct io_args *a, long n, const char *msg, int io_wait)
|
77
78
|
{
|
78
79
|
if (n == -1) {
|
79
|
-
if (errno == EINTR)
|
80
|
+
if (errno == EINTR) {
|
81
|
+
a->fd = my_fileno(a->io);
|
80
82
|
return -1;
|
83
|
+
}
|
81
84
|
rb_str_set_len(a->buf, 0);
|
82
85
|
if (errno == EAGAIN) {
|
83
86
|
if (io_wait) {
|
84
87
|
(void)kgio_call_wait_readable(a->io);
|
85
88
|
|
86
89
|
/* buf may be modified in other thread/fiber */
|
90
|
+
rb_str_modify(a->buf);
|
87
91
|
rb_str_resize(a->buf, a->len);
|
88
92
|
a->ptr = RSTRING_PTR(a->buf);
|
89
93
|
return -1;
|
@@ -307,8 +311,10 @@ static int write_check(struct io_args *a, long n, const char *msg, int io_wait)
|
|
307
311
|
done:
|
308
312
|
a->buf = Qnil;
|
309
313
|
} else if (n == -1) {
|
310
|
-
if (errno == EINTR)
|
314
|
+
if (errno == EINTR) {
|
315
|
+
a->fd = my_fileno(a->io);
|
311
316
|
return -1;
|
317
|
+
}
|
312
318
|
if (errno == EAGAIN) {
|
313
319
|
long written = RSTRING_LEN(a->buf) - a->len;
|
314
320
|
|
@@ -370,7 +376,7 @@ static VALUE kgio_write(VALUE io, VALUE str)
|
|
370
376
|
/*
|
371
377
|
* call-seq:
|
372
378
|
*
|
373
|
-
* io.kgio_trywrite(str) -> nil or :wait_writable
|
379
|
+
* io.kgio_trywrite(str) -> nil, String or :wait_writable
|
374
380
|
*
|
375
381
|
* Returns nil if the write was completed in full.
|
376
382
|
*
|
@@ -451,7 +457,7 @@ static VALUE s_tryread(int argc, VALUE *argv, VALUE mod)
|
|
451
457
|
/*
|
452
458
|
* call-seq:
|
453
459
|
*
|
454
|
-
* Kgio.trywrite(io, str) -> nil or :wait_writable
|
460
|
+
* Kgio.trywrite(io, str) -> nil, String or :wait_writable
|
455
461
|
*
|
456
462
|
* Returns nil if the write was completed in full.
|
457
463
|
*
|
data/kgio.gemspec
CHANGED
@@ -15,13 +15,12 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.extra_rdoc_files = extra_rdoc_files(manifest)
|
16
16
|
s.files = manifest
|
17
17
|
s.rdoc_options = rdoc_options
|
18
|
-
s.require_paths = %w(lib ext)
|
19
18
|
s.rubyforge_project = %q{rainbows}
|
20
19
|
s.summary = summary
|
21
20
|
s.test_files = Dir['test/test_*.rb']
|
22
21
|
s.extensions = %w(ext/kgio/extconf.rb)
|
23
22
|
|
24
|
-
s.add_development_dependency('wrongdoc', '~> 1.
|
23
|
+
s.add_development_dependency('wrongdoc', '~> 1.5')
|
25
24
|
s.add_development_dependency('strace_me', '~> 1.0')
|
26
25
|
|
27
26
|
# s.license = %w(LGPL) # disabled for compatibility with older RubyGems
|
data/test/lib_read_write.rb
CHANGED
@@ -30,6 +30,24 @@ module LibReadWriteTest
|
|
30
30
|
assert_equal "", buf
|
31
31
|
end
|
32
32
|
|
33
|
+
def test_read_shared
|
34
|
+
a = "." * 0x1000
|
35
|
+
b = a.dup
|
36
|
+
@wr.syswrite "a"
|
37
|
+
assert_equal "a", @rd.kgio_read(0x1000, a)
|
38
|
+
assert_equal "a", a
|
39
|
+
assert_equal "." * 0x1000, b
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_read_shared_2
|
43
|
+
a = "." * 0x1000
|
44
|
+
b = a.dup
|
45
|
+
@wr.syswrite "a"
|
46
|
+
assert_equal "a", @rd.kgio_read(0x1000, b)
|
47
|
+
assert_equal "a", b
|
48
|
+
assert_equal "." * 0x1000, a
|
49
|
+
end
|
50
|
+
|
33
51
|
def test_tryread_zero
|
34
52
|
assert_equal "", @rd.kgio_tryread(0)
|
35
53
|
buf = "foo"
|
@@ -37,6 +55,24 @@ module LibReadWriteTest
|
|
37
55
|
assert_equal "", buf
|
38
56
|
end
|
39
57
|
|
58
|
+
def test_tryread_shared
|
59
|
+
a = "." * 0x1000
|
60
|
+
b = a.dup
|
61
|
+
@wr.syswrite("a")
|
62
|
+
assert_equal "a", @rd.kgio_tryread(0x1000, b)
|
63
|
+
assert_equal "a", b
|
64
|
+
assert_equal "." * 0x1000, a
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_tryread_shared_2
|
68
|
+
a = "." * 0x1000
|
69
|
+
b = a.dup
|
70
|
+
@wr.syswrite("a")
|
71
|
+
assert_equal "a", @rd.kgio_tryread(0x1000, a)
|
72
|
+
assert_equal "a", a
|
73
|
+
assert_equal "." * 0x1000, b
|
74
|
+
end
|
75
|
+
|
40
76
|
def test_read_eof
|
41
77
|
@wr.close
|
42
78
|
assert_nil @rd.kgio_read(5)
|
data/test/lib_server_accept.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
require 'fcntl'
|
2
3
|
require 'io/nonblock'
|
3
4
|
$-w = true
|
4
5
|
require 'kgio'
|
@@ -19,6 +20,24 @@ module LibServerAccept
|
|
19
20
|
assert_equal @host, b.kgio_addr
|
20
21
|
end
|
21
22
|
|
23
|
+
def test_tryaccept_flags
|
24
|
+
a = client_connect
|
25
|
+
IO.select([@srv])
|
26
|
+
b = @srv.kgio_tryaccept nil, 0
|
27
|
+
assert_kind_of Kgio::Socket, b
|
28
|
+
assert_equal false, b.nonblock?
|
29
|
+
assert_equal 0, b.fcntl(Fcntl::F_GETFD)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_blocking_accept_flags
|
33
|
+
a = client_connect
|
34
|
+
IO.select([@srv])
|
35
|
+
b = @srv.kgio_accept nil, 0
|
36
|
+
assert_kind_of Kgio::Socket, b
|
37
|
+
assert_equal false, b.nonblock?
|
38
|
+
assert_equal 0, b.fcntl(Fcntl::F_GETFD)
|
39
|
+
end
|
40
|
+
|
22
41
|
def test_tryaccept_fail
|
23
42
|
assert_equal nil, @srv.kgio_tryaccept
|
24
43
|
end
|
data/test/test_autopush.rb
CHANGED
@@ -94,7 +94,7 @@ class TestAutopush < Test::Unit::TestCase
|
|
94
94
|
@rd.kgio_write "HI\n"
|
95
95
|
@wr.kgio_read(3, rbuf)
|
96
96
|
diff = Time.now - t0
|
97
|
-
assert(diff >= 0.
|
97
|
+
assert(diff >= 0.190, "nopush broken? diff=#{diff} > 200ms")
|
98
98
|
assert_equal "HI\n", rbuf
|
99
99
|
end
|
100
100
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
$-w = true
|
3
|
+
require 'kgio'
|
4
|
+
|
5
|
+
class TestCrossThreadClose < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_cross_thread_close
|
8
|
+
host = ENV["TEST_HOST"] || '127.0.0.1'
|
9
|
+
srv = Kgio::TCPServer.new(host, 0)
|
10
|
+
thr = Thread.new do
|
11
|
+
begin
|
12
|
+
srv.kgio_accept
|
13
|
+
rescue => e
|
14
|
+
e
|
15
|
+
end
|
16
|
+
end
|
17
|
+
sleep(0.1) until thr.stop?
|
18
|
+
srv.close
|
19
|
+
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" &&
|
20
|
+
RUBY_VERSION == "1.9.3"
|
21
|
+
thr.run rescue nil
|
22
|
+
end
|
23
|
+
thr.join
|
24
|
+
assert_kind_of IOError, thr.value
|
25
|
+
end
|
26
|
+
end if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
|
data/test/test_poll.rb
CHANGED
@@ -42,27 +42,52 @@ class TestPoll < Test::Unit::TestCase
|
|
42
42
|
assert_nil res
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
45
|
+
def test_poll_close
|
46
46
|
foo = nil
|
47
|
-
|
48
|
-
thr = Thread.new { sleep 0.100; Process.kill(:QUIT, $$) }
|
47
|
+
thr = Thread.new { sleep 0.100; @wr.close }
|
49
48
|
t0 = Time.now
|
50
|
-
|
49
|
+
res = Kgio.poll({@rd => Kgio::POLLIN})
|
51
50
|
diff = Time.now - t0
|
52
51
|
thr.join
|
52
|
+
assert_equal([ @rd ], res.keys)
|
53
53
|
assert diff >= 0.010, "diff=#{diff}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_poll_EINTR
|
57
|
+
ok = false
|
58
|
+
orig = trap(:USR1) { ok = true }
|
59
|
+
thr = Thread.new do
|
60
|
+
sleep 0.100
|
61
|
+
Process.kill(:USR1, $$)
|
62
|
+
end
|
63
|
+
t0 = Time.now
|
64
|
+
res = Kgio.poll({@rd => Kgio::POLLIN}, 1000)
|
65
|
+
diff = Time.now - t0
|
66
|
+
thr.join
|
67
|
+
assert_nil res
|
68
|
+
assert diff >= 1.0, "diff=#{diff}"
|
69
|
+
assert ok
|
54
70
|
ensure
|
55
|
-
trap(:
|
71
|
+
trap(:USR1, orig)
|
56
72
|
end
|
57
73
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
74
|
+
def test_poll_EINTR_changed
|
75
|
+
ok = false
|
76
|
+
orig = trap(:USR1) { ok = true }
|
77
|
+
pollset = { @rd => Kgio::POLLIN }
|
78
|
+
thr = Thread.new do
|
79
|
+
sleep 0.100
|
80
|
+
pollset[@wr] = Kgio::POLLOUT
|
81
|
+
Process.kill(:USR1, $$)
|
82
|
+
end
|
61
83
|
t0 = Time.now
|
62
|
-
res = Kgio.poll(
|
84
|
+
res = Kgio.poll(pollset, 1000)
|
63
85
|
diff = Time.now - t0
|
64
86
|
thr.join
|
65
|
-
assert_equal(
|
66
|
-
assert diff
|
87
|
+
assert_equal({@wr => Kgio::POLLOUT}, res)
|
88
|
+
assert diff < 1.0, "diff=#{diff}"
|
89
|
+
assert ok
|
90
|
+
ensure
|
91
|
+
trap(:USR1, orig)
|
67
92
|
end
|
68
93
|
end if Kgio.respond_to?(:poll)
|
data/test/test_tcp_connect.rb
CHANGED
@@ -66,7 +66,7 @@ class TestKgioTcpConnect < Test::Unit::TestCase
|
|
66
66
|
|
67
67
|
def test_wait_writable_set
|
68
68
|
sock = SubSocket.new(@addr)
|
69
|
-
assert_equal "waited", sock.foo
|
69
|
+
assert_equal "waited", sock.foo if RUBY_PLATFORM =~ /linux/
|
70
70
|
assert_equal nil, sock.kgio_write("HELLO")
|
71
71
|
end
|
72
72
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kgio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 2.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 2.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- kgio hackers
|
@@ -15,8 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
19
|
-
default_executable:
|
18
|
+
date: 2011-05-05 00:00:00 Z
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
22
21
|
name: wrongdoc
|
@@ -26,11 +25,11 @@ dependencies:
|
|
26
25
|
requirements:
|
27
26
|
- - ~>
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
28
|
+
hash: 5
|
30
29
|
segments:
|
31
30
|
- 1
|
32
|
-
-
|
33
|
-
version: "1.
|
31
|
+
- 5
|
32
|
+
version: "1.5"
|
34
33
|
type: :development
|
35
34
|
version_requirements: *id001
|
36
35
|
- !ruby/object:Gem::Dependency
|
@@ -97,6 +96,7 @@ files:
|
|
97
96
|
- ext/kgio/ancient_ruby.h
|
98
97
|
- ext/kgio/autopush.c
|
99
98
|
- ext/kgio/blocking_io_region.h
|
99
|
+
- ext/kgio/broken_system_compat.h
|
100
100
|
- ext/kgio/connect.c
|
101
101
|
- ext/kgio/extconf.rb
|
102
102
|
- ext/kgio/kgio.h
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- test/test_accept_class.rb
|
118
118
|
- test/test_autopush.rb
|
119
119
|
- test/test_connect_fd_leak.rb
|
120
|
+
- test/test_cross_thread_close.rb
|
120
121
|
- test/test_default_wait.rb
|
121
122
|
- test/test_kgio_addr.rb
|
122
123
|
- test/test_no_dns_on_tcp_connect.rb
|
@@ -135,7 +136,6 @@ files:
|
|
135
136
|
- test/test_unix_connect.rb
|
136
137
|
- test/test_unix_server.rb
|
137
138
|
- test/test_unix_server_read_client_write.rb
|
138
|
-
has_rdoc: true
|
139
139
|
homepage: http://bogomips.org/kgio/
|
140
140
|
licenses: []
|
141
141
|
|
@@ -147,7 +147,6 @@ rdoc_options:
|
|
147
147
|
- http://bogomips.org/kgio.git/tree/%s
|
148
148
|
require_paths:
|
149
149
|
- lib
|
150
|
-
- ext
|
151
150
|
required_ruby_version: !ruby/object:Gem::Requirement
|
152
151
|
none: false
|
153
152
|
requirements:
|
@@ -169,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
168
|
requirements: []
|
170
169
|
|
171
170
|
rubyforge_project: rainbows
|
172
|
-
rubygems_version: 1.
|
171
|
+
rubygems_version: 1.7.2
|
173
172
|
signing_key:
|
174
173
|
specification_version: 3
|
175
174
|
summary: kinder, gentler I/O for Ruby
|
@@ -184,6 +183,7 @@ test_files:
|
|
184
183
|
- test/test_socketpair_read_write.rb
|
185
184
|
- test/test_tcp_server.rb
|
186
185
|
- test/test_unix_server_read_client_write.rb
|
186
|
+
- test/test_cross_thread_close.rb
|
187
187
|
- test/test_tcp_connect.rb
|
188
188
|
- test/test_autopush.rb
|
189
189
|
- test/test_connect_fd_leak.rb
|