kgio 2.8.1 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/ext/kgio/writev.c ADDED
@@ -0,0 +1,317 @@
1
+ /*
2
+ * we're currently too lazy to use rb_ensure to free an allocation, so we
3
+ * the abuse rb_str_* API for a temporary buffer
4
+ */
5
+ #define RSTRING_MODIFIED 1
6
+
7
+ #include "kgio.h"
8
+ #include "my_fileno.h"
9
+ #include "nonblock.h"
10
+ #ifdef HAVE_WRITEV
11
+ # include <sys/uio.h>
12
+ # define USE_WRITEV 1
13
+ #else
14
+ # define USE_WRITEV 0
15
+ static ssize_t assert_writev(int fd, void* iov, int len)
16
+ {
17
+ assert(0 && "you should not try to call writev");
18
+ return -1;
19
+ }
20
+ # define writev assert_writev
21
+ #endif
22
+ static VALUE sym_wait_writable;
23
+
24
+ #ifndef HAVE_WRITEV
25
+ #define iovec my_iovec
26
+ struct my_iovec {
27
+ void *iov_base;
28
+ size_t iov_len;
29
+ };
30
+ #endif
31
+
32
+ /* tests for choosing following constants were done on Linux 3.0 x86_64
33
+ * (Ubuntu 12.04) Core i3 i3-2330M slowed to 1600MHz
34
+ * testing script https://gist.github.com/2850641
35
+ * fill free to make more thorough testing and choose better value
36
+ */
37
+
38
+ /* test shows that its meaningless to set WRITEV_MEMLIMIT more that 1M
39
+ * even when tcp_wmem set to relatively high value (2M) (in fact, it becomes
40
+ * even slower). 512K performs a bit better in average case. */
41
+ #define WRITEV_MEMLIMIT (512*1024)
42
+ /* same test shows that custom_writev is faster than glibc writev when
43
+ * average string is smaller than ~500 bytes and slower when average strings
44
+ * is greater then ~600 bytes. 512 bytes were choosen cause current compilers
45
+ * turns x/512 into x>>9 */
46
+ #define WRITEV_IMPL_THRESHOLD 512
47
+
48
+ static unsigned int iov_max = 1024; /* this could be overriden in init */
49
+
50
+ struct wrv_args {
51
+ VALUE io;
52
+ VALUE buf;
53
+ VALUE vec_buf; /* FIXME: this requires RSTRING_MODIFY for rbx */
54
+ struct iovec *vec;
55
+ unsigned long iov_cnt;
56
+ size_t batch_len;
57
+ int something_written;
58
+ int fd;
59
+ };
60
+
61
+ static ssize_t custom_writev(int fd, const struct iovec *vec, unsigned int iov_cnt, size_t total_len)
62
+ {
63
+ unsigned int i;
64
+ ssize_t result;
65
+ char *buf, *curbuf;
66
+ const struct iovec *curvec = vec;
67
+
68
+ /* we do not want to use ruby's xmalloc because
69
+ * it can fire GC, and we'll free buffer shortly anyway */
70
+ curbuf = buf = malloc(total_len);
71
+ if (buf == NULL) return -1;
72
+
73
+ for (i = 0; i < iov_cnt; i++, curvec++) {
74
+ memcpy(curbuf, curvec->iov_base, curvec->iov_len);
75
+ curbuf += curvec->iov_len;
76
+ }
77
+
78
+ result = write(fd, buf, total_len);
79
+
80
+ /* well, it seems that `free` could not change errno
81
+ * but lets save it anyway */
82
+ i = errno;
83
+ free(buf);
84
+ errno = i;
85
+
86
+ return result;
87
+ }
88
+
89
+ static void prepare_writev(struct wrv_args *a, VALUE io, VALUE ary)
90
+ {
91
+ a->io = io;
92
+ a->fd = my_fileno(io);
93
+ a->something_written = 0;
94
+
95
+ if (TYPE(ary) == T_ARRAY)
96
+ /* rb_ary_subseq will not copy array unless it modified */
97
+ a->buf = rb_ary_subseq(ary, 0, RARRAY_LEN(ary));
98
+ else
99
+ a->buf = rb_Array(ary);
100
+
101
+ a->vec_buf = rb_str_new(0, 0);
102
+ a->vec = NULL;
103
+ }
104
+
105
+ static void fill_iovec(struct wrv_args *a)
106
+ {
107
+ unsigned long i;
108
+ struct iovec *curvec;
109
+
110
+ a->iov_cnt = RARRAY_LEN(a->buf);
111
+ a->batch_len = 0;
112
+ if (a->iov_cnt == 0) return;
113
+ if (a->iov_cnt > iov_max) a->iov_cnt = iov_max;
114
+ rb_str_resize(a->vec_buf, sizeof(struct iovec) * a->iov_cnt);
115
+ curvec = a->vec = (struct iovec*)RSTRING_PTR(a->vec_buf);
116
+
117
+ for (i=0; i < a->iov_cnt; i++, curvec++) {
118
+ VALUE str = rb_ary_entry(a->buf, i);
119
+ long str_len, next_len;
120
+
121
+ if (TYPE(str) != T_STRING) {
122
+ str = rb_obj_as_string(str);
123
+ rb_ary_store(a->buf, i, str);
124
+ }
125
+
126
+ str_len = RSTRING_LEN(str);
127
+
128
+ /* lets limit total memory to write,
129
+ * but always take first string */
130
+ next_len = a->batch_len + str_len;
131
+ if (i && next_len > WRITEV_MEMLIMIT) {
132
+ a->iov_cnt = i;
133
+ break;
134
+ }
135
+ a->batch_len = next_len;
136
+
137
+ curvec->iov_base = RSTRING_PTR(str);
138
+ curvec->iov_len = str_len;
139
+ }
140
+ }
141
+
142
+ static long trim_writev_buffer(struct wrv_args *a, long n)
143
+ {
144
+ long i;
145
+ long ary_len = RARRAY_LEN(a->buf);
146
+
147
+ if (n == (long)a->batch_len) {
148
+ i = a->iov_cnt;
149
+ n = 0;
150
+ } else {
151
+ for (i = 0; n && i < ary_len; i++) {
152
+ VALUE entry = rb_ary_entry(a->buf, i);
153
+ n -= RSTRING_LEN(entry);
154
+ if (n < 0) break;
155
+ }
156
+ }
157
+
158
+ /* all done */
159
+ if (i == ary_len) {
160
+ assert(n == 0 && "writev system call is broken");
161
+ a->buf = Qnil;
162
+ return 0;
163
+ }
164
+
165
+ /* partially done, remove fully-written buffers */
166
+ if (i > 0)
167
+ a->buf = rb_ary_subseq(a->buf, i, ary_len - i);
168
+
169
+ /* setup+replace partially written buffer */
170
+ if (n < 0) {
171
+ VALUE str = rb_ary_entry(a->buf, 0);
172
+ long str_len = RSTRING_LEN(str);
173
+ str = rb_str_subseq(str, str_len + n, -n);
174
+ rb_ary_store(a->buf, 0, str);
175
+ }
176
+ return RARRAY_LEN(a->buf);
177
+ }
178
+
179
+ static int writev_check(struct wrv_args *a, long n, const char *msg, int io_wait)
180
+ {
181
+ if (n >= 0) {
182
+ if (n > 0) a->something_written = 1;
183
+ return trim_writev_buffer(a, n);
184
+ } else if (n < 0) {
185
+ if (errno == EINTR) {
186
+ a->fd = my_fileno(a->io);
187
+ return -1;
188
+ }
189
+ if (errno == EAGAIN) {
190
+ if (io_wait) {
191
+ (void)kgio_call_wait_writable(a->io);
192
+ return -1;
193
+ } else if (!a->something_written) {
194
+ a->buf = sym_wait_writable;
195
+ }
196
+ return 0;
197
+ }
198
+ kgio_wr_sys_fail(msg);
199
+ }
200
+ return 0;
201
+ }
202
+
203
+ static VALUE my_writev(VALUE io, VALUE ary, int io_wait)
204
+ {
205
+ struct wrv_args a;
206
+ long n;
207
+
208
+ prepare_writev(&a, io, ary);
209
+ set_nonblocking(a.fd);
210
+
211
+ do {
212
+ fill_iovec(&a);
213
+ if (a.iov_cnt == 0)
214
+ n = 0;
215
+ else if (a.iov_cnt == 1)
216
+ n = (long)write(a.fd, a.vec[0].iov_base,
217
+ a.vec[0].iov_len);
218
+ /* for big strings use library function */
219
+ else if (USE_WRITEV &&
220
+ ((a.batch_len / WRITEV_IMPL_THRESHOLD) > a.iov_cnt))
221
+ n = (long)writev(a.fd, a.vec, a.iov_cnt);
222
+ else
223
+ n = (long)custom_writev(a.fd, a.vec, a.iov_cnt,
224
+ a.batch_len);
225
+ } while (writev_check(&a, n, "writev", io_wait) != 0);
226
+ rb_str_resize(a.vec_buf, 0);
227
+
228
+ if (TYPE(a.buf) != T_SYMBOL)
229
+ kgio_autopush_write(io);
230
+ return a.buf;
231
+ }
232
+
233
+ /*
234
+ * call-seq:
235
+ *
236
+ * io.kgio_writev(array) -> nil
237
+ *
238
+ * Returns nil when the write completes.
239
+ *
240
+ * This may block and call any method defined to +kgio_wait_writable+
241
+ * for the class.
242
+ *
243
+ * Note: it uses +Array()+ semantic for converting argument, so that
244
+ * it will succeed if you pass something else.
245
+ */
246
+ static VALUE kgio_writev(VALUE io, VALUE ary)
247
+ {
248
+ return my_writev(io, ary, 1);
249
+ }
250
+
251
+ /*
252
+ * call-seq:
253
+ *
254
+ * io.kgio_trywritev(array) -> nil, Array or :wait_writable
255
+ *
256
+ * Returns nil if the write was completed in full.
257
+ *
258
+ * Returns an Array of strings containing the unwritten portion
259
+ * if EAGAIN was encountered, but some portion was successfully written.
260
+ *
261
+ * Returns :wait_writable if EAGAIN is encountered and nothing
262
+ * was written.
263
+ *
264
+ * Note: it uses +Array()+ semantic for converting argument, so that
265
+ * it will succeed if you pass something else.
266
+ */
267
+ static VALUE kgio_trywritev(VALUE io, VALUE ary)
268
+ {
269
+ return my_writev(io, ary, 0);
270
+ }
271
+
272
+ /*
273
+ * call-seq:
274
+ *
275
+ * Kgio.trywritev(io, array) -> nil, Array or :wait_writable
276
+ *
277
+ * Returns nil if the write was completed in full.
278
+ *
279
+ * Returns a Array of strings containing the unwritten portion if EAGAIN
280
+ * was encountered, but some portion was successfully written.
281
+ *
282
+ * Returns :wait_writable if EAGAIN is encountered and nothing
283
+ * was written.
284
+ *
285
+ * Maybe used in place of PipeMethods#kgio_trywritev for non-Kgio objects
286
+ */
287
+ static VALUE s_trywritev(VALUE mod, VALUE io, VALUE ary)
288
+ {
289
+ return kgio_trywritev(io, ary);
290
+ }
291
+
292
+ void init_kgio_writev(void)
293
+ {
294
+ #ifdef IOV_MAX
295
+ unsigned int sys_iov_max = IOV_MAX;
296
+ #else
297
+ unsigned int sys_iov_max = sysconf(_SC_IOV_MAX);
298
+ #endif
299
+
300
+ VALUE mPipeMethods, mSocketMethods;
301
+ VALUE mKgio = rb_define_module("Kgio");
302
+
303
+ if (sys_iov_max < iov_max)
304
+ iov_max = sys_iov_max;
305
+
306
+ sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
307
+
308
+ rb_define_singleton_method(mKgio, "trywritev", s_trywritev, 2);
309
+
310
+ mPipeMethods = rb_define_module_under(mKgio, "PipeMethods");
311
+ rb_define_method(mPipeMethods, "kgio_writev", kgio_writev, 1);
312
+ rb_define_method(mPipeMethods, "kgio_trywritev", kgio_trywritev, 1);
313
+
314
+ mSocketMethods = rb_define_module_under(mKgio, "SocketMethods");
315
+ rb_define_method(mSocketMethods, "kgio_writev", kgio_writev, 1);
316
+ rb_define_method(mSocketMethods, "kgio_trywritev", kgio_trywritev, 1);
317
+ }
data/test/test_poll.rb CHANGED
@@ -87,31 +87,6 @@ class TestPoll < Test::Unit::TestCase
87
87
  trap(:USR1, orig)
88
88
  end
89
89
 
90
- def test_poll_EINTR_changed
91
- ok = false
92
- pollset = { @rd => Kgio::POLLIN }
93
- orig = trap(:USR1) do
94
- pollset[@wr] = Kgio::POLLOUT
95
- ok = true
96
- end
97
- thr = Thread.new do
98
- sleep 0.100
99
- 100.times do
100
- Process.kill(:USR1, $$)
101
- Thread.pass
102
- end
103
- end
104
- t0 = Time.now
105
- res = Kgio.poll(pollset, 1000)
106
- diff = Time.now - t0
107
- thr.join
108
- assert_equal({@wr => Kgio::POLLOUT}, res)
109
- assert diff < 1.0, "diff=#{diff}"
110
- assert ok
111
- ensure
112
- trap(:USR1, orig)
113
- end
114
-
115
90
  def test_poll_signal_torture
116
91
  usr1 = 0
117
92
  empty = 0
@@ -0,0 +1,43 @@
1
+ require 'test/unit'
2
+ require 'kgio'
3
+
4
+ class TestKgioSyssend < Test::Unit::TestCase
5
+ def setup
6
+ @host = '127.0.0.1' || ENV["TEST_HOST"]
7
+ end
8
+
9
+ def test_syssend
10
+ srv = Kgio::TCPServer.new(@host, 0)
11
+ port = srv.addr[1]
12
+ client = TCPSocket.new(@host, port)
13
+ acc = srv.kgio_accept
14
+ th = Thread.new { client.readpartial(4) }
15
+ sleep(0.05)
16
+ assert_nil acc.kgio_syssend("HI", Socket::MSG_DONTWAIT | Socket::MSG_MORE)
17
+ assert_nil acc.kgio_syssend("HI", Socket::MSG_DONTWAIT)
18
+ assert_equal "HIHI", th.value
19
+
20
+ buf = "*" * 123
21
+ res = []
22
+ case rv = acc.kgio_syssend(buf, Socket::MSG_DONTWAIT)
23
+ when nil
24
+ when String
25
+ res << rv
26
+ when Symbol
27
+ res << rv
28
+ break
29
+ end while true
30
+ assert_equal :wait_writable, res.last
31
+ if res.size > 1
32
+ assert_kind_of String, res[-2]
33
+ else
34
+ warn "res too small"
35
+ end
36
+
37
+ # blocking
38
+ th = Thread.new { loop { acc.kgio_syssend("ZZZZ", 0) } }
39
+ assert_nil th.join(0.1)
40
+ ensure
41
+ [ srv, acc, client ].each { |io| io.close if io }
42
+ end
43
+ end if RUBY_PLATFORM =~ /linux/
metadata CHANGED
@@ -1,34 +1,28 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: kgio
3
- version: !ruby/object:Gem::Version
4
- hash: 45
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.9.0
5
5
  prerelease:
6
- segments:
7
- - 2
8
- - 8
9
- - 1
10
- version: 2.8.1
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - kgio hackers
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2013-09-11 00:00:00 Z
12
+ date: 2014-02-04 00:00:00.000000000 Z
19
13
  dependencies: []
14
+ description: ! 'kgio provides non-blocking I/O methods for Ruby without raising
20
15
 
21
- description: |-
22
- kgio provides non-blocking I/O methods for Ruby without raising
23
16
  exceptions on EAGAIN and EINPROGRESS. It is intended for use with the
17
+
24
18
  Unicorn and Rainbows! Rack servers, but may be used by other
25
- applications (that run on Unix-like platforms).
19
+
20
+ applications (that run on Unix-like platforms).'
26
21
  email: kgio@librelist.org
27
22
  executables: []
28
-
29
- extensions:
23
+ extensions:
30
24
  - ext/kgio/extconf.rb
31
- extra_rdoc_files:
25
+ extra_rdoc_files:
32
26
  - LICENSE
33
27
  - README
34
28
  - TODO
@@ -43,10 +37,9 @@ extra_rdoc_files:
43
37
  - ext/kgio/connect.c
44
38
  - ext/kgio/kgio_ext.c
45
39
  - ext/kgio/poll.c
46
- - ext/kgio/read_write.c
47
40
  - ext/kgio/wait.c
48
41
  - ext/kgio/tryopen.c
49
- files:
42
+ files:
50
43
  - .document
51
44
  - .gitignore
52
45
  - .manifest
@@ -77,11 +70,13 @@ files:
77
70
  - ext/kgio/my_fileno.h
78
71
  - ext/kgio/nonblock.h
79
72
  - ext/kgio/poll.c
80
- - ext/kgio/read_write.c
73
+ - ext/kgio/read.c
81
74
  - ext/kgio/set_file_path.h
82
75
  - ext/kgio/sock_for_fd.h
83
76
  - ext/kgio/tryopen.c
84
77
  - ext/kgio/wait.c
78
+ - ext/kgio/write.c
79
+ - ext/kgio/writev.c
85
80
  - kgio.gemspec
86
81
  - lib/kgio.rb
87
82
  - pkg.mk
@@ -103,6 +98,7 @@ files:
103
98
  - test/test_singleton_read_write.rb
104
99
  - test/test_socket.rb
105
100
  - test/test_socketpair_read_write.rb
101
+ - test/test_syssend.rb
106
102
  - test/test_tcp6_client_read_server_write.rb
107
103
  - test/test_tcp_client_read_server_write.rb
108
104
  - test/test_tcp_connect.rb
@@ -116,47 +112,40 @@ files:
116
112
  - test/test_unix_server_read_client_write.rb
117
113
  homepage: http://bogomips.org/kgio/
118
114
  licenses: []
119
-
120
115
  post_install_message:
121
- rdoc_options:
116
+ rdoc_options:
122
117
  - -t
123
118
  - kgio - kinder, gentler I/O for Ruby
124
119
  - -W
125
120
  - http://bogomips.org/kgio.git/tree/%s
126
- require_paths:
121
+ require_paths:
127
122
  - lib
128
- required_ruby_version: !ruby/object:Gem::Requirement
123
+ required_ruby_version: !ruby/object:Gem::Requirement
129
124
  none: false
130
- requirements:
131
- - - ">="
132
- - !ruby/object:Gem::Version
133
- hash: 3
134
- segments:
135
- - 0
136
- version: "0"
137
- required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ! '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
130
  none: false
139
- requirements:
140
- - - ">="
141
- - !ruby/object:Gem::Version
142
- hash: 3
143
- segments:
144
- - 0
145
- version: "0"
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
146
135
  requirements: []
147
-
148
136
  rubyforge_project: rainbows
149
- rubygems_version: 1.8.24
137
+ rubygems_version: 1.8.23
150
138
  signing_key:
151
139
  specification_version: 3
152
140
  summary: kinder, gentler I/O for Ruby
153
- test_files:
141
+ test_files:
154
142
  - test/test_poll.rb
155
143
  - test/test_peek.rb
156
144
  - test/test_socket.rb
157
145
  - test/test_default_wait.rb
158
146
  - test/test_no_dns_on_tcp_connect.rb
159
147
  - test/test_unix_connect.rb
148
+ - test/test_syssend.rb
160
149
  - test/test_pipe_read_write.rb
161
150
  - test/test_unix_server.rb
162
151
  - test/test_accept_flags.rb