io_splice 3.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v3.1.0.GIT
4
+ DEF_VER=v4.0.0.GIT
5
5
 
6
6
  LF='
7
7
  '
@@ -13,6 +13,7 @@
13
13
  #include <sys/utsname.h>
14
14
 
15
15
  static VALUE sym_EAGAIN;
16
+ #define WAITALL 0x4000000
16
17
 
17
18
  #ifndef F_LINUX_SPECIFIC_BASE
18
19
  # define F_LINUX_SPECIFIC_BASE 1024
@@ -37,13 +38,26 @@ static VALUE sym_EAGAIN;
37
38
  # endif
38
39
  #endif
39
40
 
41
+ #ifndef SSIZET2NUM
42
+ # define SSIZET2NUM(x) LONG2NUM(x)
43
+ #endif
44
+ #ifndef NUM2SSIZET
45
+ # define NUM2SSIZET(x) NUM2LONG(x)
46
+ #endif
47
+ #ifndef SIZET2NUM
48
+ # define SIZET2NUM(x) ULONG2NUM(x)
49
+ #endif
50
+ #ifndef NUM2SIZET
51
+ # define NUM2SIZET(x) NUM2ULONG(x)
52
+ #endif
53
+
40
54
  static int my_fileno(VALUE io)
41
55
  {
42
56
  rb_io_t *fptr;
43
57
 
44
58
  for (;;) {
45
59
  switch (TYPE(io)) {
46
- case T_FIXNUM: return NUM2INT(io);
60
+ case T_FIXNUM: return FIX2INT(io);
47
61
  case T_FILE: {
48
62
  GetOpenFile(io, fptr);
49
63
  return FPTR_TO_FD(fptr);
@@ -54,6 +68,14 @@ static int my_fileno(VALUE io)
54
68
  }
55
69
  }
56
70
  }
71
+
72
+ static int check_fileno(VALUE io)
73
+ {
74
+ int saved_errno = errno;
75
+ int fd = my_fileno(io);
76
+ errno = saved_errno;
77
+ return fd;
78
+ }
57
79
  #ifndef HAVE_RB_THREAD_BLOCKING_REGION
58
80
  /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
59
81
  # include <rubysig.h>
@@ -95,21 +117,6 @@ static VALUE io_run(rb_blocking_function_t *fn, void *data)
95
117
  return rb_thread_blocking_region(fn, data, RUBY_UBF_IO, 0);
96
118
  }
97
119
 
98
- /*
99
- * Releases GVL only iff blocking I/O is used.
100
- * Only use this if all file descriptors in data are pipes.
101
- * We'll trust programmers who use non-blocking I/O explicitly to
102
- * want the fastest possible performance without resorting to threads,
103
- * so releasing and them immediately reacquiring the GVL would be
104
- * a waste of time.
105
- */
106
- static VALUE nb_io_run(rb_blocking_function_t *fn, void *data, unsigned flags)
107
- {
108
- if (flags & SPLICE_F_NONBLOCK)
109
- return fn(data);
110
- return io_run(fn, data);
111
- }
112
-
113
120
  struct splice_args {
114
121
  int fd_in;
115
122
  off_t *off_in;
@@ -127,32 +134,65 @@ static VALUE nogvl_splice(void *ptr)
127
134
  a->len, a->flags);
128
135
  }
129
136
 
130
- static long do_splice(int argc, VALUE *argv, unsigned dflags)
137
+ static ssize_t do_splice(int argc, VALUE *argv, unsigned dflags)
131
138
  {
132
- off_t i, o;
133
- VALUE fd_in, off_in, fd_out, off_out, len, flags;
139
+ off_t i = 0, o = 0;
140
+ VALUE io_in, off_in, io_out, off_out, len, flags;
134
141
  struct splice_args a;
142
+ ssize_t bytes;
143
+ ssize_t total = 0;
144
+ unsigned waitall;
135
145
 
136
146
  rb_scan_args(argc, argv, "51",
137
- &fd_in, &off_in, &fd_out, &off_out, &len, &flags);
147
+ &io_in, &off_in, &io_out, &off_out, &len, &flags);
138
148
 
139
149
  a.off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i);
140
150
  a.off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o);
141
- a.fd_in = my_fileno(fd_in);
142
- a.fd_out = my_fileno(fd_out);
143
- a.len = (size_t)NUM2ULONG(len);
151
+ a.len = NUM2SIZET(len);
144
152
  a.flags = NIL_P(flags) ? dflags : NUM2UINT(flags) | dflags;
153
+ waitall = a.flags & WAITALL;
154
+ if (waitall)
155
+ a.flags ^= WAITALL;
145
156
 
146
- return (long)io_run(nogvl_splice, &a);
157
+ for (;;) {
158
+ a.fd_in = check_fileno(io_in);
159
+ a.fd_out = check_fileno(io_out);
160
+ bytes = (ssize_t)io_run(nogvl_splice, &a);
161
+ if (bytes == -1) {
162
+ if (errno == EINTR)
163
+ continue;
164
+ if (waitall && errno == EAGAIN) {
165
+ rb_io_wait_readable(check_fileno(io_in));
166
+ errno = EAGAIN;
167
+ rb_io_wait_writable(check_fileno(io_out));
168
+ continue;
169
+ }
170
+ if (total > 0)
171
+ return total;
172
+ return bytes;
173
+ } else if (bytes == 0) {
174
+ break;
175
+ } else if (waitall) {
176
+ total += bytes;
177
+ if ((a.len -= bytes) == 0)
178
+ return total;
179
+ i += bytes;
180
+ o += bytes;
181
+ } else {
182
+ return bytes;
183
+ }
184
+ }
185
+
186
+ return total;
147
187
  }
148
188
 
149
189
  /*
150
190
  * call-seq:
151
- * IO.splice(fd_in, off_in, fd_out, off_out, len) => integer
152
- * IO.splice(fd_in, off_in, fd_out, off_out, len, flags) => integer
191
+ * IO.splice(io_in, off_in, io_out, off_out, len) => integer
192
+ * IO.splice(io_in, off_in, io_out, off_out, len, flags) => integer
153
193
  *
154
- * Splice +len+ bytes from/to a pipe. Either +fd_in+ or +fd_out+
155
- * MUST be a pipe. +fd_in+ and +fd_out+ may BOTH be pipes as of
194
+ * Splice +len+ bytes from/to a pipe. Either +io_in+ or +io_out+
195
+ * MUST be a pipe. +io_in+ and +io_out+ may BOTH be pipes as of
156
196
  * Linux 2.6.31 or later.
157
197
  *
158
198
  * +off_in+ and +off_out+ if non-nil may be used to
@@ -161,22 +201,18 @@ static long do_splice(int argc, VALUE *argv, unsigned dflags)
161
201
  * +flags+ defaults to zero if unspecified.
162
202
  * +flags+ may be a bitmask of the following flags:
163
203
  *
164
- * IO::Splice::F_MOVE, IO::Splice::F_NONBLOCK, IO::Splice::F_MORE
204
+ * * IO::Splice::F_MOVE
205
+ * * IO::Splice::F_NONBLOCK
206
+ * * IO::Splice::F_MORE
207
+ * * IO::Splice::WAITALL
165
208
  *
166
209
  * Returns the number of bytes spliced.
167
- * Raises EOFError when +fd_in+ has reached end of file.
210
+ * Raises EOFError when +io_in+ has reached end of file.
168
211
  * Raises Errno::EAGAIN if the IO::Splice::F_NONBLOCK flag is set
169
212
  * and the pipe has no data to read from or space to write to. May
170
213
  * also raise Errno::EAGAIN if the non-pipe descriptor has no data
171
214
  * to read from or space to write to.
172
215
  *
173
- * rd, wr = (pipe = IO.pipe).map { |io| io.fileno }
174
- * src_io, dst_io = File.open("/path/to/src"), File.open("/path/to/dst")
175
- * src, dst = src_io.fileno, dst_io.fileno
176
- *
177
- * nr = IO.splice(src, nil, wr, nil, IO::Splice::PIPE_CAPA, 0)
178
- * IO.splice(rd, nil, dst, nil, nr, 0)
179
- *
180
216
  * As splice never exposes buffers to userspace, it will not take
181
217
  * into account userspace buffering done by Ruby or stdio. It is
182
218
  * also not subject to encoding/decoding filters under Ruby 1.9.
@@ -190,19 +226,19 @@ static long do_splice(int argc, VALUE *argv, unsigned dflags)
190
226
  */
191
227
  static VALUE my_splice(int argc, VALUE *argv, VALUE self)
192
228
  {
193
- long n = do_splice(argc, argv, 0);
229
+ ssize_t n = do_splice(argc, argv, 0);
194
230
 
195
231
  if (n == 0)
196
232
  rb_eof_error();
197
- if (n < 0)
233
+ if (n == -1)
198
234
  rb_sys_fail("splice");
199
- return LONG2NUM(n);
235
+ return SSIZET2NUM(n);
200
236
  }
201
237
 
202
238
  /*
203
239
  * call-seq:
204
- * IO.trysplice(fd_in, off_in, fd_out, off_out, len) => integer
205
- * IO.trysplice(fd_in, off_in, fd_out, off_out, len, flags) => integer
240
+ * IO.trysplice(io_in, off_in, io_out, off_out, len) => integer
241
+ * IO.trysplice(io_in, off_in, io_out, off_out, len, flags) => integer
206
242
  *
207
243
  * Exactly like IO.splice, except +:EAGAIN+ is returned when either
208
244
  * the read or write end would block instead of raising Errno::EAGAIN.
@@ -214,16 +250,16 @@ static VALUE my_splice(int argc, VALUE *argv, VALUE self)
214
250
  */
215
251
  static VALUE trysplice(int argc, VALUE *argv, VALUE self)
216
252
  {
217
- long n = do_splice(argc, argv, SPLICE_F_NONBLOCK);
253
+ ssize_t n = do_splice(argc, argv, SPLICE_F_NONBLOCK);
218
254
 
219
255
  if (n == 0)
220
256
  return Qnil;
221
- if (n < 0) {
257
+ if (n == -1) {
222
258
  if (errno == EAGAIN)
223
259
  return sym_EAGAIN;
224
260
  rb_sys_fail("splice");
225
261
  }
226
- return LONG2NUM(n);
262
+ return SSIZET2NUM(n);
227
263
  }
228
264
 
229
265
  struct tee_args {
@@ -241,35 +277,69 @@ static VALUE nogvl_tee(void *ptr)
241
277
  return (VALUE)tee(a->fd_in, a->fd_out, a->len, a->flags);
242
278
  }
243
279
 
244
- static long do_tee(int argc, VALUE *argv, unsigned dflags)
280
+ static ssize_t do_tee(int argc, VALUE *argv, unsigned dflags)
245
281
  {
246
- VALUE fd_in, fd_out, len, flags;
282
+ VALUE io_in, io_out, len, flags;
247
283
  struct tee_args a;
284
+ ssize_t bytes;
285
+ ssize_t total = 0;
286
+ unsigned waitall;
248
287
 
249
- rb_scan_args(argc, argv, "31", &fd_in, &fd_out, &len, &flags);
250
- a.fd_in = my_fileno(fd_in);
251
- a.fd_out = my_fileno(fd_out);
252
- a.len = (size_t)NUM2ULONG(len);
288
+ rb_scan_args(argc, argv, "31", &io_in, &io_out, &len, &flags);
289
+ a.len = (size_t)NUM2SIZET(len);
253
290
  a.flags = NIL_P(flags) ? dflags : NUM2UINT(flags) | dflags;
291
+ waitall = a.flags & WAITALL;
292
+ if (waitall)
293
+ a.flags ^= WAITALL;
294
+
295
+ for (;;) {
296
+ a.fd_in = check_fileno(io_in);
297
+ a.fd_out = check_fileno(io_out);
298
+ bytes = (ssize_t)io_run(nogvl_tee, &a);
299
+ if (bytes == -1) {
300
+ if (errno == EINTR)
301
+ continue;
302
+ if (waitall && errno == EAGAIN) {
303
+ rb_io_wait_readable(check_fileno(io_in));
304
+ errno = EAGAIN;
305
+ rb_io_wait_writable(check_fileno(io_out));
306
+ continue;
307
+ }
308
+ if (total > 0)
309
+ return total;
310
+ return bytes;
311
+ } else if (bytes == 0) {
312
+ break;
313
+ } else if (waitall) {
314
+ total += bytes;
315
+ if ((a.len -= bytes) == 0)
316
+ return total;
317
+ } else {
318
+ return bytes;
319
+ }
320
+ }
254
321
 
255
- return (long)nb_io_run(nogvl_tee, &a, a.flags);
322
+ return total;
256
323
  }
257
324
 
258
325
  /*
259
326
  * call-seq:
260
- * IO.tee(fd_in, fd_out, len) => integer
261
- * IO.tee(fd_in, fd_out, len, flags) => integer
327
+ * IO.tee(io_in, io_out, len) => integer
328
+ * IO.tee(io_in, io_out, len, flags) => integer
262
329
  *
263
- * Copies up to +len+ bytes of data from +fd_in+ to +fd_out+. +fd_in+
264
- * and +fd_out+ must both refer to pipe descriptors. +fd_in+ and +fd_out+
330
+ * Copies up to +len+ bytes of data from +io_in+ to +io_out+. +io_in+
331
+ * and +io_out+ must both refer to pipe descriptors. +io_in+ and +io_out+
265
332
  * may not be endpoints of the same pipe.
266
333
  *
267
- * +flags+ may be zero (the default) or IO::Splice::F_NONBLOCK
334
+ * +flags+ may be zero (the default) or a combination of:
335
+ * * IO::Splice::F_NONBLOCK
336
+ * * IO::Splice::WAITALL
337
+ *
268
338
  * Other IO::Splice flags are currently unimplemented or have no effect.
269
339
  *
270
340
  * Returns the number of bytes duplicated if successful.
271
- * Raises EOFError when +fd_in+ is closed and emptied.
272
- * Raises Errno::EAGAIN when +fd_in+ is empty and/or +fd_out+ is full
341
+ * Raises EOFError when +io_in+ is closed and emptied.
342
+ * Raises Errno::EAGAIN when +io_in+ is empty and/or +io_out+ is full
273
343
  * and +flags+ contains IO::Splice::F_NONBLOCK
274
344
  *
275
345
  * Consider using IO.trytee if you are using IO::Splice::F_NONBLOCK
@@ -280,20 +350,20 @@ static long do_tee(int argc, VALUE *argv, unsigned dflags)
280
350
  */
281
351
  static VALUE my_tee(int argc, VALUE *argv, VALUE self)
282
352
  {
283
- long n = do_tee(argc, argv, 0);
353
+ ssize_t n = do_tee(argc, argv, 0);
284
354
 
285
355
  if (n == 0)
286
356
  rb_eof_error();
287
- if (n < 0)
357
+ if (n == -1)
288
358
  rb_sys_fail("tee");
289
359
 
290
- return LONG2NUM(n);
360
+ return SSIZET2NUM(n);
291
361
  }
292
362
 
293
363
  /*
294
364
  * call-seq:
295
- * IO.trytee(fd_in, fd_out, len) => integer
296
- * IO.trytee(fd_in, fd_out, len, flags) => integer
365
+ * IO.trytee(io_in, io_out, len) => integer
366
+ * IO.trytee(io_in, io_out, len, flags) => integer
297
367
  *
298
368
  * Exactly like IO.tee, except +:EAGAIN+ is returned when either
299
369
  * the read or write end would block instead of raising Errno::EAGAIN.
@@ -305,17 +375,17 @@ static VALUE my_tee(int argc, VALUE *argv, VALUE self)
305
375
  */
306
376
  static VALUE trytee(int argc, VALUE *argv, VALUE self)
307
377
  {
308
- long n = do_tee(argc, argv, SPLICE_F_NONBLOCK);
378
+ ssize_t n = do_tee(argc, argv, SPLICE_F_NONBLOCK);
309
379
 
310
380
  if (n == 0)
311
381
  return Qnil;
312
- if (n < 0) {
382
+ if (n == -1) {
313
383
  if (errno == EAGAIN)
314
384
  return sym_EAGAIN;
315
385
  rb_sys_fail("tee");
316
386
  }
317
387
 
318
- return LONG2NUM(n);
388
+ return SSIZET2NUM(n);
319
389
  }
320
390
 
321
391
  struct vmsplice_args {
@@ -385,13 +455,13 @@ static void advance_vmsplice_args(struct vmsplice_args *a, long n)
385
455
 
386
456
  /*
387
457
  * call-seq:
388
- * IO.vmsplice(fd, string_array) => integer
389
- * IO.vmsplice(fd, string_array, flags) => integer
390
- * IO.vmsplice(fd, string) => integer
391
- * IO.vmsplice(fd, string, flags) => integer
458
+ * IO.vmsplice(io, string_array) => integer
459
+ * IO.vmsplice(io, string_array, flags) => integer
460
+ * IO.vmsplice(io, string) => integer
461
+ * IO.vmsplice(io, string, flags) => integer
392
462
  *
393
- * Transfers an array of strings into the pipe descriptor given by fd.
394
- * +fd+ must be the writable end of a pipe.
463
+ * Transfers an array of strings into the pipe descriptor given by io.
464
+ * +io+ must be the writable end of a pipe.
395
465
  *
396
466
  * This may allow the kernel to avoid data copies in some cases.
397
467
  * but is (probably) of limited usefulness in Ruby. If you have
@@ -408,12 +478,12 @@ static void advance_vmsplice_args(struct vmsplice_args *a, long n)
408
478
  */
409
479
  static VALUE my_vmsplice(int argc, VALUE * argv, VALUE self)
410
480
  {
411
- long rv = 0;
481
+ ssize_t rv = 0;
412
482
  ssize_t left;
413
483
  struct vmsplice_args a;
414
- VALUE fd, data, flags;
484
+ VALUE io, data, flags;
415
485
 
416
- rb_scan_args(argc, argv, "21", &fd, &data, &flags);
486
+ rb_scan_args(argc, argv, "21", &io, &data, &flags);
417
487
 
418
488
  switch (TYPE(data)) {
419
489
  case T_STRING: {
@@ -433,18 +503,21 @@ static VALUE my_vmsplice(int argc, VALUE * argv, VALUE self)
433
503
  "(expected a String or Array of strings)",
434
504
  rb_obj_classname(data));
435
505
  }
436
- a.fd = my_fileno(fd);
506
+ a.fd = my_fileno(io);
437
507
  a.flags = NIL_P(flags) ? 0 : NUM2UINT(flags);
438
508
 
439
509
  for (;;) {
440
- long n = (long)nb_io_run(nogvl_vmsplice, &a, a.flags);
510
+ ssize_t n = (ssize_t)io_run(nogvl_vmsplice, &a);
441
511
 
442
- if (n < 0) {
512
+ if (n == -1) {
443
513
  if (errno == EAGAIN) {
444
- if (a.flags & SPLICE_F_NONBLOCK)
514
+ if (a.flags & SPLICE_F_NONBLOCK) {
445
515
  rb_sys_fail("vmsplice");
446
- else if (rb_io_wait_writable(a.fd))
447
- continue;
516
+ } else {
517
+ a.fd = check_fileno(io);
518
+ if (rb_io_wait_writable(a.fd))
519
+ continue;
520
+ }
448
521
  /* fall through on error */
449
522
  }
450
523
  /*
@@ -454,6 +527,10 @@ static VALUE my_vmsplice(int argc, VALUE * argv, VALUE self)
454
527
  */
455
528
  if (rv > 0)
456
529
  break;
530
+ if (errno == EINTR) {
531
+ a.fd = check_fileno(io);
532
+ continue;
533
+ }
457
534
  rb_sys_fail("vmsplice");
458
535
  }
459
536
 
@@ -464,7 +541,7 @@ static VALUE my_vmsplice(int argc, VALUE * argv, VALUE self)
464
541
  advance_vmsplice_args(&a, n);
465
542
  }
466
543
 
467
- return LONG2NUM(rv);
544
+ return SSIZET2NUM(rv);
468
545
  }
469
546
 
470
547
  /*
@@ -550,6 +627,7 @@ void Init_io_splice_ext(void)
550
627
  * re-added for FUSE filesystems only in Linux 2.6.35.
551
628
  */
552
629
  rb_define_const(mSplice, "F_MOVE", UINT2NUM(SPLICE_F_MOVE));
630
+ assert(WAITALL != SPLICE_F_MOVE && "WAITALL == F_MOVE");
553
631
 
554
632
  /*
555
633
  * Do not block on pipe I/O. This flag only affects the pipe(s)
@@ -563,6 +641,7 @@ void Init_io_splice_ext(void)
563
641
  * out of them.
564
642
  */
565
643
  rb_define_const(mSplice, "F_NONBLOCK", UINT2NUM(SPLICE_F_NONBLOCK));
644
+ assert(WAITALL != SPLICE_F_NONBLOCK && "WAITALL == F_NONBLOCK");
566
645
 
567
646
  /*
568
647
  * Indicate that there may be more data coming into the outbound
@@ -570,12 +649,23 @@ void Init_io_splice_ext(void)
570
649
  * frames from sockets. Currently only used with splice.
571
650
  */
572
651
  rb_define_const(mSplice, "F_MORE", UINT2NUM(SPLICE_F_MORE));
652
+ assert(WAITALL != SPLICE_F_MORE && "WAITALL == F_MORE");
573
653
 
574
654
  /*
575
655
  * Only usable by vmsplice. This flag probably not useful in the
576
656
  * context of Ruby applications which cannot control alignment.
577
657
  */
578
658
  rb_define_const(mSplice, "F_GIFT", UINT2NUM(SPLICE_F_GIFT));
659
+ assert(WAITALL != SPLICE_F_GIFT && "WAITALL == F_GIFT");
660
+
661
+ /*
662
+ * Retry until the requested transfer is complete, this will
663
+ * cause IO.splice/IO.tee to never return less than the requested
664
+ * transfer size unless an error occored.
665
+ *
666
+ * IO.vmsplice always defaults to this behavior.
667
+ */
668
+ rb_define_const(mSplice, "WAITALL", UINT2NUM(WAITALL));
579
669
 
580
670
  /*
581
671
  * The maximum size of an atomic write to a pipe
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'io_splice_ext'
3
+ require 'io/wait'
3
4
 
4
5
  module IO::Splice
5
6
 
@@ -39,28 +40,30 @@ module IO::Splice
39
40
  dst.kind_of?(String) and close << (dst = File.open(dst, "w"))
40
41
  src, dst = src.to_io, dst.to_io
41
42
  rv = len
42
- select_args = selectable(src, dst)
43
43
 
44
44
  if src.stat.pipe? || dst.stat.pipe?
45
45
  if len
46
- len -= full(src, dst, len, src_offset, select_args) until len == 0
46
+ len -= full(src, dst, len, src_offset) until len == 0
47
47
  else
48
48
  rv = 0
49
- while n = partial(src, dst, PIPE_CAPA, src_offset, select_args)
49
+ while n = partial(src, dst, PIPE_CAPA, src_offset)
50
50
  rv += n
51
+ src_offset += n if src_offset
51
52
  end
52
53
  end
53
54
  else
54
55
  r, w = tmp = IO.pipe
55
56
  close.concat(tmp)
56
57
  if len
57
- while len != 0 && n = partial(src, w, len, src_offset, select_args)
58
- len -= full(r, dst, n, nil, select_args)
58
+ while len != 0 && n = partial(src, w, len, src_offset)
59
+ src_offset += n if src_offset
60
+ len -= full(r, dst, n, nil)
59
61
  end
60
62
  else
61
63
  rv = 0
62
- while n = partial(src, w, PIPE_CAPA, src_offset, select_args)
63
- rv += full(r, dst, n, nil, select_args)
64
+ while n = partial(src, w, PIPE_CAPA, src_offset)
65
+ src_offset += n if src_offset
66
+ rv += full(r, dst, n, nil)
64
67
  end
65
68
  end
66
69
  end
@@ -76,17 +79,8 @@ module IO::Splice
76
79
  # This will block and wait for IO completion of +len+
77
80
  # Raises +EOFError+ if end of file is reached.
78
81
  # bytes. Returns the number of bytes actually spliced (always +len+)
79
- # The +_select_args+ parameter is reserved for internal use and
80
- # may be removed in future versions. Do not write code that
81
- # depends on +_select_args+.
82
- def self.full(src, dst, len, src_offset, _select_args = selectable(src, dst))
83
- nr = len
84
- while nr > 0
85
- n = partial(src, dst, nr, src_offset, _select_args) or
86
- raise EOFError, "end of file reached"
87
- nr -= n
88
- end
89
- len
82
+ def self.full(src, dst, len, src_offset)
83
+ IO.splice(src, src_offset, dst, nil, len, F_MOVE | WAITALL)
90
84
  end
91
85
 
92
86
  # splice up to +len+ bytes from +src+ to +dst+.
@@ -94,22 +88,20 @@ module IO::Splice
94
88
  # may BOTH be pipes in Linux 2.6.31 or later.
95
89
  # Returns the number of bytes actually spliced.
96
90
  # Like IO#readpartial, this never returns Errno::EAGAIN
97
- # The +_select_args+ parameter is reserved for internal use and
98
- # may be removed in future versions. Do not write code that
99
- # depends on +_select_args+.
100
- def self.partial(src, dst, len, src_offset,
101
- _select_args = selectable(src, dst))
102
- begin
103
- rv = IO.trysplice(src, src_offset, dst, nil, len, F_MOVE)
104
- end while rv == :EAGAIN and IO.select(*_select_args)
105
- rv
106
- end
107
-
108
- # returns an array suitable for splat-ing to IO.select for blocking I/O
109
- def self.selectable(src, dst) # :nodoc:
110
- rv = []
111
- src.stat.pipe? or rv[0] = [ src ]
112
- dst.stat.pipe? or rv[1] = [ dst ]
113
- rv
91
+ def self.partial(src, dst, len, src_offset)
92
+ IO.splice(src, src_offset, dst, nil, len, F_MOVE)
93
+ rescue EOFError
94
+ nil
95
+ rescue Errno::EAGAIN
96
+ begin
97
+ src.to_io.wait
98
+ IO.select(nil, [dst])
99
+ rv = IO.trysplice(src, src_offset, dst, nil, len, F_MOVE)
100
+ end while rv == :EAGAIN
101
+ rv
114
102
  end
115
103
  end
104
+ if (! defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") &&
105
+ RUBY_VERSION.to_f <= 1.8
106
+ require "io/splice/mri_18"
107
+ end
@@ -0,0 +1,34 @@
1
+ # :enddoc:
2
+ require "io/nonblock"
3
+ module IO::Splice
4
+ class << self
5
+ remove_method :full
6
+ remove_method :partial
7
+ end
8
+ def self.full(src, dst, len, src_offset)
9
+ dst.to_io.nonblock = src.to_io.nonblock = true
10
+ spliced = 0
11
+ case n = IO.trysplice(src, src_offset, dst, nil, len, IO::Splice::F_MOVE)
12
+ when :EAGAIN
13
+ src.to_io.wait
14
+ IO.select(nil, [dst])
15
+ when Integer
16
+ spliced += n
17
+ len -= n
18
+ src_offset += n if src_offset
19
+ when nil
20
+ break
21
+ end while len > 0
22
+ spliced
23
+ end
24
+
25
+ def self.partial(src, dst, len, src_offset)
26
+ dst.to_io.nonblock = src.to_io.nonblock = true
27
+ begin
28
+ src.to_io.wait
29
+ IO.select(nil, [dst])
30
+ rv = IO.trysplice(src, src_offset, dst, nil, len, IO::Splice::F_MOVE)
31
+ end while rv == :EAGAIN
32
+ rv
33
+ end
34
+ end
@@ -0,0 +1,340 @@
1
+ require 'test/unit'
2
+ require 'tmpdir'
3
+ require "fcntl"
4
+ require 'io/nonblock'
5
+ require 'socket'
6
+ require 'timeout'
7
+ require 'tempfile'
8
+ require 'io/splice'
9
+
10
+ class TestIOCopyStreamCompat < Test::Unit::TestCase
11
+ def have_nonblock?
12
+ IO.method_defined?("nonblock=")
13
+ end
14
+
15
+ def pipe(wp, rp)
16
+ re, we = nil, nil
17
+ r, w = IO.pipe
18
+ rt = Thread.new do
19
+ begin
20
+ rp.call(r)
21
+ rescue Exception
22
+ r.close
23
+ re = $!
24
+ end
25
+ end
26
+ wt = Thread.new do
27
+ begin
28
+ wp.call(w)
29
+ rescue Exception
30
+ w.close
31
+ we = $!
32
+ end
33
+ end
34
+ flunk("timeout") unless wt.join(10) && rt.join(10)
35
+ ensure
36
+ w.close unless !w || w.closed?
37
+ r.close unless !r || r.closed?
38
+ (wt.kill; wt.join) if wt
39
+ (rt.kill; rt.join) if rt
40
+ raise we if we
41
+ raise re if re
42
+ end
43
+
44
+ def with_pipe
45
+ r, w = IO.pipe
46
+ begin
47
+ yield r, w
48
+ ensure
49
+ r.close unless r.closed?
50
+ w.close unless w.closed?
51
+ end
52
+ end
53
+
54
+ def with_read_pipe(content)
55
+ pipe(proc do |w|
56
+ w << content
57
+ w.close
58
+ end, proc do |r|
59
+ yield r
60
+ end)
61
+ end
62
+
63
+ def mkcdtmpdir
64
+ Dir.mktmpdir {|d|
65
+ Dir.chdir(d) {
66
+ yield
67
+ }
68
+ }
69
+ end
70
+
71
+ def trapping_usr1
72
+ @usr1_rcvd = 0
73
+ trap(:USR1) { @usr1_rcvd += 1 }
74
+ yield
75
+ ensure
76
+ trap(:USR1, "DEFAULT")
77
+ end
78
+
79
+ def test_copy_stream
80
+ mkcdtmpdir {
81
+ content = "foobar"
82
+ File.open("src", "w") {|f| f << content }
83
+ ret = IO::Splice.copy_stream("src", "dst")
84
+ assert_equal(content.bytesize, ret)
85
+ assert_equal(content, File.read("dst"))
86
+
87
+ # overwrite by smaller file.
88
+ content = "baz"
89
+ File.open("src", "w") {|f| f << content }
90
+ ret = IO::Splice.copy_stream("src", "dst")
91
+ assert_equal(content.bytesize, ret)
92
+ assert_equal(content, File.read("dst"))
93
+
94
+ ret = IO::Splice.copy_stream("src", "dst", 2)
95
+ assert_equal(2, ret)
96
+ assert_equal(content[0,2], File.read("dst"))
97
+
98
+ ret = IO::Splice.copy_stream("src", "dst", 0)
99
+ assert_equal(0, ret)
100
+ assert_equal("", File.read("dst"))
101
+
102
+ ret = IO::Splice.copy_stream("src", "dst", nil, 1)
103
+ assert_equal(content.bytesize-1, ret)
104
+ assert_equal(content[1..-1], File.read("dst"))
105
+
106
+ assert_raise(Errno::ENOENT) {
107
+ IO::Splice.copy_stream("nodir/foo", "dst")
108
+ }
109
+
110
+ assert_raise(Errno::ENOENT) {
111
+ IO::Splice.copy_stream("src", "nodir/bar")
112
+ }
113
+
114
+ pipe(proc do |w|
115
+ ret = IO::Splice.copy_stream("src", w)
116
+ assert_equal(content.bytesize, ret)
117
+ w.close
118
+ end, proc do |r|
119
+ assert_equal(content, r.read)
120
+ end)
121
+
122
+ with_pipe {|r, w|
123
+ w.close
124
+ assert_raise(IOError) { IO::Splice.copy_stream("src", w) }
125
+ }
126
+
127
+ pipe_content = "abc"
128
+ with_read_pipe(pipe_content) {|r|
129
+ ret = IO::Splice.copy_stream(r, "dst")
130
+ assert_equal(pipe_content.bytesize, ret)
131
+ assert_equal(pipe_content, File.read("dst"))
132
+ }
133
+
134
+ pipe(proc do |w|
135
+ ret = IO::Splice.copy_stream("src", w, 1, 1)
136
+ assert_equal(1, ret)
137
+ w.close
138
+ end, proc do |r|
139
+ assert_equal(content[1,1], r.read)
140
+ end)
141
+
142
+ bigcontent = "abc" * 123456
143
+ File.open("bigsrc", "w") {|f| f << bigcontent }
144
+ ret = IO::Splice.copy_stream("bigsrc", "bigdst")
145
+ assert_equal(bigcontent.bytesize, ret)
146
+ assert_equal(bigcontent, File.read("bigdst"))
147
+
148
+ File.unlink("bigdst")
149
+ ret = IO::Splice.copy_stream("bigsrc", "bigdst", nil, 100)
150
+ assert_equal(bigcontent.bytesize-100, ret)
151
+ assert_equal(bigcontent[100..-1], File.read("bigdst"))
152
+
153
+ File.unlink("bigdst")
154
+ ret = IO::Splice.copy_stream("bigsrc", "bigdst", 30000, 100)
155
+ assert_equal(30000, ret)
156
+ assert_equal(bigcontent[100, 30000], File.read("bigdst"))
157
+
158
+ File.open("bigsrc") {|f|
159
+ begin
160
+ assert_equal(0, f.pos)
161
+ ret = IO::Splice.copy_stream(f, "bigdst", nil, 10)
162
+ assert_equal(bigcontent.bytesize-10, ret)
163
+ assert_equal(bigcontent[10..-1], File.read("bigdst"))
164
+ assert_equal(0, f.pos)
165
+ ret = IO::Splice.copy_stream(f, "bigdst", 40, 30)
166
+ assert_equal(40, ret)
167
+ assert_equal(bigcontent[30, 40], File.read("bigdst"))
168
+ assert_equal(0, f.pos)
169
+ rescue NotImplementedError
170
+ #skip "pread(2) is not implemtented."
171
+ end
172
+ }
173
+
174
+ with_pipe {|r, w|
175
+ w.close
176
+ assert_raise(IOError) { IO::Splice.copy_stream("src", w) }
177
+ }
178
+
179
+ megacontent = "abc" * 1234567
180
+ File.open("megasrc", "w") {|f| f << megacontent }
181
+
182
+ if have_nonblock?
183
+ with_pipe {|r1, w1|
184
+ with_pipe {|r2, w2|
185
+ begin
186
+ r1.nonblock = true
187
+ w2.nonblock = true
188
+ rescue Errno::EBADF
189
+ skip "nonblocking IO for pipe is not implemented"
190
+ end
191
+ t1 = Thread.new { w1 << megacontent; w1.close }
192
+ t2 = Thread.new { r2.read }
193
+ ret = IO::Splice.copy_stream(r1, w2)
194
+ assert_equal(megacontent.bytesize, ret)
195
+ w2.close
196
+ t1.join
197
+ assert_equal(megacontent, t2.value)
198
+ }
199
+ }
200
+ end
201
+
202
+ with_pipe {|r1, w1|
203
+ with_pipe {|r2, w2|
204
+ t1 = Thread.new { w1 << megacontent; w1.close }
205
+ t2 = Thread.new { r2.read }
206
+ ret = IO::Splice.copy_stream(r1, w2)
207
+ assert_equal(megacontent.bytesize, ret)
208
+ w2.close
209
+ t1.join
210
+ assert_equal(megacontent, t2.value)
211
+ }
212
+ }
213
+
214
+ with_pipe {|r, w|
215
+ t = Thread.new { r.read }
216
+ ret = IO::Splice.copy_stream("megasrc", w)
217
+ assert_equal(megacontent.bytesize, ret)
218
+ w.close
219
+ assert_equal(megacontent, t.value)
220
+ }
221
+ }
222
+ end
223
+
224
+ def with_socketpair
225
+ s1, s2 = UNIXSocket.pair
226
+ begin
227
+ yield s1, s2
228
+ ensure
229
+ s1.close unless s1.closed?
230
+ s2.close unless s2.closed?
231
+ end
232
+ end
233
+
234
+ def test_copy_stream_socket
235
+ mkcdtmpdir {
236
+
237
+ content = "foobar"
238
+ File.open("src", "w") {|f| f << content }
239
+
240
+ with_socketpair {|s1, s2|
241
+ ret = IO::Splice.copy_stream("src", s1)
242
+ assert_equal(content.bytesize, ret)
243
+ s1.close
244
+ assert_equal(content, s2.read)
245
+ }
246
+
247
+ bigcontent = "abc" * 123456
248
+ File.open("bigsrc", "w") {|f| f << bigcontent }
249
+
250
+ with_socketpair {|s1, s2|
251
+ t = Thread.new { s2.read }
252
+ ret = IO::Splice.copy_stream("bigsrc", s1)
253
+ assert_equal(bigcontent.bytesize, ret)
254
+ s1.close
255
+ result = t.value
256
+ assert_equal(bigcontent, result)
257
+ }
258
+
259
+ with_socketpair {|s1, s2|
260
+ t = Thread.new { s2.read }
261
+ ret = IO::Splice.copy_stream("bigsrc", s1, 10000)
262
+ assert_equal(10000, ret)
263
+ s1.close
264
+ result = t.value
265
+ assert_equal(bigcontent[0,10000], result)
266
+ }
267
+
268
+ File.open("bigsrc") {|f|
269
+ assert_equal(0, f.pos)
270
+ with_socketpair {|s1, s2|
271
+ t = Thread.new { s2.read }
272
+ ret = IO::Splice.copy_stream(f, s1, nil, 100)
273
+ assert_equal(bigcontent.bytesize-100, ret)
274
+ assert_equal(0, f.pos)
275
+ s1.close
276
+ result = t.value
277
+ assert_equal(bigcontent[100..-1], result)
278
+ }
279
+ }
280
+
281
+ File.open("bigsrc") {|f|
282
+ assert_equal(bigcontent[0,100], f.sysread(100))
283
+ assert_equal(100, f.pos)
284
+ with_socketpair {|s1, s2|
285
+ t = Thread.new { s2.read }
286
+ ret = IO::Splice.copy_stream(f, s1)
287
+ assert_equal(bigcontent.bytesize-100, ret)
288
+ assert_equal(bigcontent.length, f.sysseek(0, IO::SEEK_CUR))
289
+ s1.close
290
+ result = t.value
291
+ assert_equal(bigcontent[100..-1], result)
292
+ }
293
+ }
294
+
295
+ megacontent = "abc" * 1234567
296
+ File.open("megasrc", "w") {|f| f << megacontent }
297
+
298
+ if have_nonblock?
299
+ with_socketpair {|s1, s2|
300
+ begin
301
+ s1.nonblock = true
302
+ rescue Errno::EBADF
303
+ skip "nonblocking IO for pipe is not implemented"
304
+ end
305
+ t = Thread.new { s2.read }
306
+ ret = IO::Splice.copy_stream("megasrc", s1)
307
+ assert_equal(megacontent.bytesize, ret)
308
+ s1.close
309
+ result = t.value
310
+ assert_equal(megacontent, result)
311
+ }
312
+ with_socketpair {|s1, s2|
313
+ begin
314
+ s1.nonblock = true
315
+ rescue Errno::EBADF
316
+ skip "nonblocking IO for pipe is not implemented"
317
+ end
318
+ trapping_usr1 do
319
+ nr = 10
320
+ pid = fork do
321
+ s1.close
322
+ IO.select([s2])
323
+ Process.kill(:USR1, Process.ppid)
324
+ s2.read
325
+ end
326
+ s2.close
327
+ nr.times do
328
+ assert_equal megacontent.bytesize,
329
+ IO::Splice.copy_stream("megasrc", s1)
330
+ end
331
+ assert_equal(1, @usr1_rcvd)
332
+ s1.close
333
+ _, status = Process.waitpid2(pid)
334
+ assert status.success?, status.inspect
335
+ end
336
+ }
337
+ end
338
+ }
339
+ end
340
+ end
@@ -9,6 +9,10 @@ require 'io/splice'
9
9
 
10
10
  class Test_IO_Splice < Test::Unit::TestCase
11
11
 
12
+ def self.mri?
13
+ (defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby") || !defined?(RUBY_ENGINE)
14
+ end
15
+
12
16
  def test_splice
13
17
  str = 'abcde'
14
18
  size = 5
@@ -415,7 +419,7 @@ class Test_IO_Splice < Test::Unit::TestCase
415
419
  assert_equal 0, nr
416
420
  rs.close
417
421
  server.close
418
- end
422
+ end if mri?
419
423
 
420
424
  def test_copy_stream_nonblock_dst
421
425
  server = TCPServer.new('127.0.0.1', 0)
@@ -440,7 +444,7 @@ class Test_IO_Splice < Test::Unit::TestCase
440
444
  assert_equal nr, client.read(nr).size
441
445
  rs.close
442
446
  server.close
443
- end
447
+ end if mri?
444
448
 
445
449
  def test_copy_stream_eof
446
450
  r, w = IO.pipe
@@ -466,5 +470,4 @@ class Test_IO_Splice < Test::Unit::TestCase
466
470
  assert_nothing_raised { r.pipe_size = pipe_max_size }
467
471
  assert_raises(Errno::EPERM) { r.pipe_size = pipe_max_size * 2 }
468
472
  end if IO.method_defined?(:pipe_size)
469
-
470
473
  end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: binary -*-
2
+ require 'test/unit'
3
+ require 'tempfile'
4
+ require 'socket'
5
+ require 'io/nonblock'
6
+ require 'timeout'
7
+ $-w = true
8
+ require 'io/splice'
9
+ Thread.abort_on_exception = true
10
+
11
+ class Test_IO_Splice_EINTR < Test::Unit::TestCase
12
+ def setup
13
+ @usr1 = 0
14
+ trap(:USR1) { @usr1 += 1 }
15
+ end
16
+
17
+ def teardown
18
+ trap(:USR1, "DEFAULT")
19
+ end
20
+
21
+ def test_EINTR_splice_read
22
+ rd, wr = IO.pipe
23
+ tmp = Tempfile.new 'splice-read'
24
+ main = Thread.current
25
+ Thread.new do
26
+ sleep 0.01
27
+ Process.kill(:USR1, $$)
28
+ sleep 0.01
29
+ wr.write "HI"
30
+ end
31
+ nr = IO.splice rd, nil, tmp, nil, 666
32
+ assert_equal 2, nr
33
+ assert_equal 1, @usr1
34
+ end
35
+ end if defined?(RUBY_ENGINE)
@@ -0,0 +1,39 @@
1
+ # -*- encoding: binary -*-
2
+ require 'test/unit'
3
+ require 'tempfile'
4
+ $-w = true
5
+ require 'io/splice'
6
+ Thread.abort_on_exception = true
7
+
8
+ class Test_IO_Splice_In_Full < Test::Unit::TestCase
9
+ def test_splice_in_full
10
+ rd, wr = IO.pipe
11
+ tmp = Tempfile.new 'splice-read'
12
+ Thread.new do
13
+ 111.times do
14
+ sleep 0.002
15
+ wr.write "HIHIHI"
16
+ end
17
+ end
18
+ nr = IO.splice rd, nil, tmp, nil, 666, IO::Splice::WAITALL
19
+ assert_equal 666, nr
20
+ tmp.rewind
21
+ assert_equal "HIHIHI" * 111, tmp.read
22
+ end
23
+
24
+ def test_tee_in_full
25
+ rd, wr = IO.pipe
26
+ a, b = IO.pipe
27
+ thr = Thread.new { a.read(666) }
28
+ Thread.new do
29
+ 111.times do
30
+ sleep 0.001
31
+ wr.syswrite "HIHIHI"
32
+ end
33
+ end
34
+ nr = IO.tee rd, b, 666, IO::Splice::WAITALL
35
+ assert_equal 666, nr
36
+ thr.join
37
+ assert_equal "HIHIHI" * 111, thr.value
38
+ end
39
+ end if defined?(RUBY_ENGINE)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io_splice
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
- - 3
8
- - 1
7
+ - 4
9
8
  - 0
10
- version: 3.1.0
9
+ - 0
10
+ version: 4.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ruby io_splice hackers
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-01 00:00:00 Z
18
+ date: 2011-05-13 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: wrongdoc
@@ -48,6 +48,7 @@ extra_rdoc_files:
48
48
  - NEWS
49
49
  - ChangeLog
50
50
  - lib/io/splice.rb
51
+ - lib/io/splice/mri_18.rb
51
52
  - ext/io_splice/io_splice_ext.c
52
53
  - LATEST
53
54
  files:
@@ -71,10 +72,14 @@ files:
71
72
  - ext/io_splice/io_splice_ext.c
72
73
  - io_splice.gemspec
73
74
  - lib/io/splice.rb
75
+ - lib/io/splice/mri_18.rb
74
76
  - local.mk.sample
75
77
  - pkg.mk
76
78
  - setup.rb
79
+ - test/test_copy_stream.rb
77
80
  - test/test_io_splice.rb
81
+ - test/test_io_splice_eintr.rb
82
+ - test/test_io_splice_in_full.rb
78
83
  homepage: http://bogomips.org/ruby_io_splice/
79
84
  licenses: []
80
85
 
@@ -107,9 +112,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
112
  requirements: []
108
113
 
109
114
  rubyforge_project: qrp
110
- rubygems_version: 1.7.2
115
+ rubygems_version: 1.8.1
111
116
  signing_key:
112
117
  specification_version: 3
113
118
  summary: zero-copy pipe I/O for Linux and Ruby
114
119
  test_files:
120
+ - test/test_io_splice_in_full.rb
115
121
  - test/test_io_splice.rb
122
+ - test/test_copy_stream.rb
123
+ - test/test_io_splice_eintr.rb