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.
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v2.7.4.GIT
4
+ DEF_VER=v2.8.0
5
5
 
6
6
  LF='
7
7
  '
@@ -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
- set_blocking_or_block(a->fd);
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:
@@ -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
- static VALUE
30
- my_connect(VALUE klass, int io_wait, int domain, void *addr, socklen_t addrlen)
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 VALUE tcp_connect(VALUE klass, VALUE ip, VALUE port, int io_wait)
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(&hints, 0, sizeof(hints));
97
- hints.ai_family = AF_UNSPEC;
98
- hints.ai_socktype = SOCK_STREAM;
99
- hints.ai_protocol = IPPROTO_TCP;
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.ai_flags = AI_NUMERICHOST;
111
+ hints->ai_flags = AI_NUMERICHOST;
102
112
 
103
- rc = getaddrinfo(ipname, ipport, &hints, &res);
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.ai_family = res->ai_family;
110
- hints.ai_addrlen = res->ai_addrlen;
111
- memcpy(&addr, res->ai_addr, res->ai_addrlen);
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", kgio_connect, 1);
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
  *
@@ -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')
@@ -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 */
@@ -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();
@@ -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 = rb_str_new(a->ptr, a->len);
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
  }
@@ -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 rb_thread_blocking_region(
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
  /*
@@ -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
@@ -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(:TERM, pid)
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(:TERM, pid)
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(:TERM, pid)
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(:TERM, pid)
83
+ Process.kill(:KILL, pid)
84
84
  Process.waitpid(pid)
85
85
  assert elapsed >= 1, "elapsed: #{elapsed}"
86
86
  end
@@ -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
- rescue Object => err
130
- p [ :err, err ]
131
- ensure
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)
@@ -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
@@ -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
@@ -1,7 +1,7 @@
1
1
  require './test/lib_read_write'
2
2
  require 'tempfile'
3
3
 
4
- class TestUnixServerReadClientWrite < Test::Unit::TestCase
4
+ class TestUnixClientReadServerWrite < Test::Unit::TestCase
5
5
  def setup
6
6
  tmp = Tempfile.new('kgio_unix')
7
7
  @path = tmp.path
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.7.4
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: 2012-03-24 00:00:00.000000000 Z
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.21
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