kgio 2.3.3 → 2.4.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/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
|