io-extra 1.2.1 → 1.2.2

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/CHANGES CHANGED
@@ -1,3 +1,9 @@
1
+ == 1.2.2 - 1-May-2010
2
+ * Added the IO.writev method which allows you to write arrays without
3
+ requiring an Array#join call, i.e. "gather write". Thanks go to Eric
4
+ Wong for supplying the code to support this.
5
+ * Some Rakefile updates.
6
+
1
7
  == 1.2.1 - 16-Jan-2010
2
8
  * Some README updates
3
9
  * Updates to the gemspec.
data/README CHANGED
@@ -60,15 +60,18 @@
60
60
  documentation that was generated by RDoc (if you did a gem install).
61
61
 
62
62
  = Known Issues
63
- None that I'm aware of (beyond those documented above). Please file any bug
64
- reports on the project page at http://www.rubyforge.org/projects/shards.
63
+ The IO.writev tests fail on Solaris. We are not sure why yet.
64
+
65
+ Please file any bug reports on the project page at
66
+ http://www.rubyforge.org/projects/shards.
65
67
 
66
68
  = Future Plans
67
69
  * I may add the File::O_DIRECT open constant on platforms that support it.
68
70
  * Switch from C extension to FFI.
69
71
 
70
72
  = Acknowledgements
71
- Eric Wong for some great work on Linux compatibility and other fixes.
73
+ Eric Wong for some great work on Linux compatibility and other fixes, as
74
+ well as the code for the IO.writev method.
72
75
 
73
76
  = License
74
77
  Artistic 2.0
data/Rakefile CHANGED
@@ -19,6 +19,7 @@ task :clean do
19
19
  build_file = File.join(Dir.pwd, 'io', 'extra.' + Config::CONFIG['DLEXT'])
20
20
  File.delete(build_file) if File.exists?(build_file)
21
21
  end
22
+ Dir['*.gem'].each{ |f| File.delete(f) }
22
23
  end
23
24
 
24
25
  desc "Build the io-extra library (but don't install it)"
@@ -40,14 +41,14 @@ task :install => [:build] do
40
41
  end
41
42
 
42
43
  namespace :gem do
43
- desc 'Build the io-extra gem'
44
- task :build do
44
+ desc 'Create the io-extra gem'
45
+ task :create do
45
46
  spec = eval(IO.read('io-extra.gemspec'))
46
47
  Gem::Builder.new(spec).build
47
48
  end
48
49
 
49
50
  desc "Install the io-extra library as a gem"
50
- task :install => [:build] do
51
+ task :install => [:create] do
51
52
  file = Dir["io-extra*.gem"].last
52
53
  sh "gem install #{file}"
53
54
  end
data/ext/extconf.rb CHANGED
@@ -6,12 +6,15 @@ dir_config('io')
6
6
 
7
7
  have_header('stdint.h')
8
8
  have_header('sys/resource.h')
9
+ have_header('sys/uio.h')
9
10
  have_func('closefrom')
10
11
  have_func('fdwalk')
11
12
  have_func('directio')
12
13
  have_func('pread')
13
14
  have_func('pwrite')
15
+ have_func('writev')
14
16
  have_func('rb_str_set_len', 'ruby.h')
17
+ have_func('rb_thread_blocking_region')
15
18
 
16
19
  case Config::CONFIG['host_os']
17
20
  when /darwin/i
data/ext/io/extra.c CHANGED
@@ -14,6 +14,49 @@
14
14
  #include <stdint.h>
15
15
  #endif
16
16
 
17
+ #ifdef HAVE_SYS_UIO_H
18
+ #include <sys/uio.h>
19
+ #endif
20
+
21
+ #ifdef HAVE_LIMITS_H
22
+ #include <limits.h>
23
+ #endif
24
+
25
+ #if !defined(IOV_MAX)
26
+ #if defined(_SC_IOV_MAX)
27
+ #define IOV_MAX (sysconf(_SC_IOV_MAX))
28
+ #else
29
+ /* assume infinity, or let the syscall return with error ... */
30
+ #define IOV_MAX INT_MAX
31
+ #endif
32
+ #endif
33
+
34
+ #ifndef HAVE_RB_THREAD_BLOCKING_REGION
35
+ /*
36
+ * partial emulation of the 1.9 rb_thread_blocking_region under 1.8,
37
+ * this is enough to ensure signals are processed safely when doing I/O
38
+ * to a slow device, but doesn't actually ensure threads can be
39
+ * scheduled fairly in 1.8
40
+ */
41
+ #include <rubysig.h>
42
+ #define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
43
+ typedef void rb_unblock_function_t(void *);
44
+ typedef VALUE rb_blocking_function_t(void *);
45
+ static VALUE
46
+ rb_thread_blocking_region(
47
+ rb_blocking_function_t *fn, void *data1,
48
+ rb_unblock_function_t *ubf, void *data2)
49
+ {
50
+ VALUE rv;
51
+
52
+ TRAP_BEG;
53
+ rv = fn(data1);
54
+ TRAP_END;
55
+
56
+ return rv;
57
+ }
58
+ #endif
59
+
17
60
  #ifndef RSTRING_PTR
18
61
  #define RSTRING_PTR(v) (RSTRING(v)->ptr)
19
62
  #define RSTRING_LEN(v) (RSTRING(v)->len)
@@ -287,7 +330,7 @@ static VALUE s_io_pread(VALUE klass, VALUE v_fd, VALUE v_nbyte, VALUE v_offset){
287
330
 
288
331
  if (nread < 0)
289
332
  rb_sys_fail("pread");
290
- if (nread != nbyte)
333
+ if ((size_t)nread != nbyte)
291
334
  rb_str_set_len(str, nread);
292
335
 
293
336
  return str;
@@ -345,6 +388,121 @@ static VALUE s_io_pwrite(VALUE klass, VALUE v_fd, VALUE v_buf, VALUE v_offset){
345
388
  }
346
389
  #endif
347
390
 
391
+ /* this can't be a function since we use alloca() */
392
+ #define ARY2IOVEC(iov,iovcnt,expect,ary) \
393
+ do { \
394
+ VALUE *cur; \
395
+ struct iovec *tmp; \
396
+ long n; \
397
+ if (TYPE(ary) != T_ARRAY) \
398
+ rb_raise(rb_eArgError, "must be an array of strings"); \
399
+ cur = RARRAY_PTR(ary); \
400
+ n = RARRAY_LEN(ary); \
401
+ if (n > IOV_MAX) \
402
+ rb_raise(rb_eArgError, "array is larger than IOV_MAX"); \
403
+ iov = tmp = alloca(sizeof(struct iovec) * n); \
404
+ expect = 0; \
405
+ iovcnt = (int)n; \
406
+ for (; --n >= 0; tmp++, cur++) { \
407
+ if (TYPE(*cur) != T_STRING) \
408
+ rb_raise(rb_eArgError, "must be an array of strings"); \
409
+ tmp->iov_base = RSTRING_PTR(*cur); \
410
+ tmp->iov_len = RSTRING_LEN(*cur); \
411
+ expect += tmp->iov_len; \
412
+ } \
413
+ } while (0)
414
+
415
+ #if defined(HAVE_WRITEV)
416
+ struct writev_args {
417
+ int fd;
418
+ struct iovec *iov;
419
+ int iovcnt;
420
+ };
421
+
422
+ static VALUE nogvl_writev(void *ptr)
423
+ {
424
+ struct writev_args *args = ptr;
425
+
426
+ return (VALUE)writev(args->fd, args->iov, args->iovcnt);
427
+ }
428
+
429
+ /*
430
+ * IO.writev(fd, %w(hello world))
431
+ *
432
+ * This method writes the contents of an array of strings to the given +fd+.
433
+ * It can be useful to avoid generating a temporary string via Array#join
434
+ * when writing out large arrays of strings.
435
+ *
436
+ * The given array should have fewer elements than the IO::IOV_MAX constant.
437
+ *
438
+ * Returns the number of bytes written.
439
+ */
440
+ static VALUE s_io_writev(VALUE klass, VALUE fd, VALUE ary) {
441
+ ssize_t result = 0;
442
+ ssize_t left;
443
+ struct writev_args args;
444
+
445
+ args.fd = NUM2INT(fd);
446
+ ARY2IOVEC(args.iov, args.iovcnt, left, ary);
447
+
448
+ for(;;) {
449
+ ssize_t w = (ssize_t)rb_thread_blocking_region(nogvl_writev, &args,
450
+ RUBY_UBF_IO, 0);
451
+
452
+ if(w == -1) {
453
+ if (rb_io_wait_writable(args.fd)) {
454
+ continue;
455
+ } else {
456
+ if (result > 0) {
457
+ /*
458
+ * unlikely to hit this case, return the already written bytes,
459
+ * we'll let the next write (or close) fail instead
460
+ */
461
+ break;
462
+ }
463
+ rb_sys_fail("writev");
464
+ }
465
+ }
466
+
467
+ result += w;
468
+ if(w == left) {
469
+ break;
470
+ } else { /* partial write, this can get tricky */
471
+ int i;
472
+ struct iovec *new_iov = args.iov;
473
+
474
+ left -= w;
475
+
476
+ /* skip over iovecs we've already written completely */
477
+ for (i = 0; i < args.iovcnt; i++, new_iov++) {
478
+ if (w == 0)
479
+ break;
480
+
481
+ /*
482
+ * partially written iov,
483
+ * modify and retry with current iovec in front
484
+ */
485
+ if (new_iov->iov_len > (size_t)w) {
486
+ VALUE base = (VALUE)new_iov->iov_base;
487
+
488
+ new_iov->iov_len -= w;
489
+ new_iov->iov_base = (void *)(base + w);
490
+ break;
491
+ }
492
+
493
+ w -= new_iov->iov_len;
494
+ }
495
+
496
+ /* retry without the already-written iovecs */
497
+ args.iovcnt -= i;
498
+ args.iov = new_iov;
499
+ }
500
+ }
501
+
502
+ return LONG2NUM(result);
503
+ }
504
+ #endif
505
+
348
506
  /* Adds the IO.closefrom, IO.fdwalk class methods, as well as the IO#directio
349
507
  * and IO#directio? instance methods (if supported on your platform).
350
508
  */
@@ -371,6 +529,8 @@ void Init_extra(){
371
529
  rb_define_const(rb_cIO, "DIRECT", UINT2NUM(O_DIRECT));
372
530
  #endif
373
531
 
532
+ rb_define_const(rb_cIO, "IOV_MAX", LONG2NUM(IOV_MAX));
533
+
374
534
  #ifdef HAVE_PREAD
375
535
  rb_define_singleton_method(rb_cIO, "pread", s_io_pread, 3);
376
536
  rb_define_singleton_method(rb_cIO, "pread_ptr", s_io_pread_ptr, 3);
@@ -380,6 +540,10 @@ void Init_extra(){
380
540
  rb_define_singleton_method(rb_cIO, "pwrite", s_io_pwrite, 3);
381
541
  #endif
382
542
 
383
- /* 1.2.1: The version of this library. This a string. */
384
- rb_define_const(rb_cIO, "EXTRA_VERSION", rb_str_new2("1.2.1"));
543
+ #ifdef HAVE_WRITEV
544
+ rb_define_singleton_method(rb_cIO, "writev", s_io_writev, 2);
545
+ #endif
546
+
547
+ /* 1.2.2: The version of this library. This a string. */
548
+ rb_define_const(rb_cIO, "EXTRA_VERSION", rb_str_new2("1.2.2"));
385
549
  }
data/io-extra.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  end
9
9
 
10
10
  spec.name = 'io-extra'
11
- spec.version = '1.2.1'
11
+ spec.version = '1.2.2'
12
12
  spec.author = 'Daniel J. Berger'
13
13
  spec.license = 'Artistic 2.0'
14
14
  spec.email = 'djberg96@gmail.com'
@@ -9,103 +9,147 @@ gem 'test-unit'
9
9
 
10
10
  require 'test/unit'
11
11
  require 'rbconfig'
12
+ require 'io/nonblock'
12
13
  require 'io/extra'
13
14
 
14
15
  class TC_IO_Extra < Test::Unit::TestCase
15
- def setup
16
- @file = 'delete_this.txt'
17
- @fh = File.open(@file, 'w+')
18
- @fh.puts "The quick brown fox jumped over the lazy dog's back"
19
- end
16
+ def setup
17
+ @file = 'delete_this.txt'
18
+ @fh = File.open(@file, 'w+')
19
+ @fh.puts "The quick brown fox jumped over the lazy dog's back"
20
+ end
20
21
 
21
- def test_version
22
- assert_equal('1.2.1', IO::EXTRA_VERSION)
23
- end
24
-
25
- def test_direct_constant
26
- omit_unless(Config::CONFIG['host_os'] =~ /linux/i, 'Linux-only')
27
- assert_equal(040000, IO::DIRECT)
28
- assert_equal(040000, File::DIRECT)
29
- end
30
-
31
- def test_open_direct
32
- omit_unless(Config::CONFIG['host_os'] =~ /linux/i, 'Linux-only')
33
- assert_nothing_raised do
34
- fh = File.open(@fh.path, IO::RDWR|IO::DIRECT)
35
- fh.close
36
- end
37
- end
38
-
39
- def test_directio
40
- omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
41
- assert_respond_to(@fh, :directio?)
42
- assert_nothing_raised{ @fh.directio? }
43
- end
44
-
45
- def test_directio_set
46
- omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
47
- assert_respond_to(@fh, :directio=)
48
- assert_raises(StandardError){ @fh.directio = 99 }
49
- assert_nothing_raised{ @fh.directio = IO::DIRECTIO_ON }
50
- end
51
-
52
- def test_constants
53
- omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
54
- assert_not_nil(IO::DIRECTIO_ON)
55
- assert_not_nil(IO::DIRECTIO_OFF)
56
- end
57
-
58
- def test_fdwalk
59
- omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
60
- assert_respond_to(IO, :fdwalk)
61
- assert_nothing_raised{ IO.fdwalk(0){ } }
62
- end
63
-
64
- def test_closefrom
65
- assert_respond_to(IO, :closefrom)
66
- assert_nothing_raised{ IO.closefrom(3) }
67
- end
68
-
69
- def test_pread
70
- @fh.close rescue nil
71
- @fh = File.open(@file)
72
- assert_respond_to(IO, :pread)
73
- assert_equal("quick", IO.pread(@fh.fileno, 5, 4))
74
- end
75
-
76
- def test_pread_binary
77
- @fh.close rescue nil
78
- @fh = File.open(@file, "ab")
79
- @fh.binmode
80
- size = @fh.stat.size
81
- assert_nothing_raised { @fh.syswrite("FOO\0HELLO") }
82
- @fh.close rescue nil
83
- @fh = File.open(@file)
84
- assert_equal("O\0H", IO.pread(@fh.fileno, 3, size + 2))
85
- end
86
-
87
- def test_pread_ptr
88
- @fh.close rescue nil
89
- @fh = File.open(@file)
90
- assert_respond_to(IO, :pread_ptr)
91
- assert_kind_of(Integer, IO.pread_ptr(@fh.fileno, 5, 4))
92
- end
93
-
94
- def test_pread_last
95
- @fh.close rescue nil
96
- @fh = File.open(@file)
97
- size = @fh.stat.size
98
- assert_equal("ck\n", IO.pread(@fh.fileno, 5, size - 3))
99
- end
100
-
101
- def test_pwrite
102
- assert_respond_to(IO, :pwrite)
103
- assert_nothing_raised{ IO.pwrite(@fh.fileno, "HAL", 0) }
104
- end
105
-
106
- def teardown
107
- @fh.close rescue nil
108
- @fh = nil
109
- File.delete(@file) if File.exists?(@file)
110
- end
22
+ def test_version
23
+ assert_equal('1.2.2', IO::EXTRA_VERSION)
24
+ end
25
+
26
+ def test_direct_constant
27
+ omit_unless(Config::CONFIG['host_os'] =~ /linux/i, 'Linux-only')
28
+ assert_equal(040000, IO::DIRECT)
29
+ assert_equal(040000, File::DIRECT)
30
+ end
31
+
32
+ def test_open_direct
33
+ omit_unless(Config::CONFIG['host_os'] =~ /linux/i, 'Linux-only')
34
+ assert_nothing_raised do
35
+ fh = File.open(@fh.path, IO::RDWR|IO::DIRECT)
36
+ fh.close
37
+ end
38
+ end
39
+
40
+ def test_directio
41
+ omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
42
+ assert_respond_to(@fh, :directio?)
43
+ assert_nothing_raised{ @fh.directio? }
44
+ end
45
+
46
+ def test_directio_set
47
+ omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
48
+ assert_respond_to(@fh, :directio=)
49
+ assert_raises(StandardError){ @fh.directio = 99 }
50
+ assert_nothing_raised{ @fh.directio = IO::DIRECTIO_ON }
51
+ end
52
+
53
+ def test_constants
54
+ omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
55
+ assert_not_nil(IO::DIRECTIO_ON)
56
+ assert_not_nil(IO::DIRECTIO_OFF)
57
+ end
58
+
59
+ def test_IOV_MAX_constant
60
+ assert_kind_of(Integer, IO::IOV_MAX)
61
+ end
62
+
63
+ def test_fdwalk
64
+ omit_if(Config::CONFIG['host_os'] =~ /darwin/i, 'unsupported')
65
+ assert_respond_to(IO, :fdwalk)
66
+ assert_nothing_raised{ IO.fdwalk(0){ } }
67
+ end
68
+
69
+ def test_closefrom
70
+ assert_respond_to(IO, :closefrom)
71
+ assert_nothing_raised{ IO.closefrom(3) }
72
+ end
73
+
74
+ def test_pread
75
+ @fh.close rescue nil
76
+ @fh = File.open(@file)
77
+ assert_respond_to(IO, :pread)
78
+ assert_equal("quick", IO.pread(@fh.fileno, 5, 4))
79
+ end
80
+
81
+ def test_pread_binary
82
+ @fh.close rescue nil
83
+ @fh = File.open(@file, "ab")
84
+ @fh.binmode
85
+ size = @fh.stat.size
86
+ assert_nothing_raised { @fh.syswrite("FOO\0HELLO") }
87
+ @fh.close rescue nil
88
+ @fh = File.open(@file)
89
+ assert_equal("O\0H", IO.pread(@fh.fileno, 3, size + 2))
90
+ end
91
+
92
+ def test_pread_ptr
93
+ @fh.close rescue nil
94
+ @fh = File.open(@file)
95
+ assert_respond_to(IO, :pread_ptr)
96
+ assert_kind_of(Integer, IO.pread_ptr(@fh.fileno, 5, 4))
97
+ end
98
+
99
+ def test_pread_last
100
+ @fh.close rescue nil
101
+ @fh = File.open(@file)
102
+ size = @fh.stat.size
103
+ assert_equal("ck\n", IO.pread(@fh.fileno, 5, size - 3))
104
+ end
105
+
106
+ def test_pwrite
107
+ assert_respond_to(IO, :pwrite)
108
+ assert_nothing_raised{ IO.pwrite(@fh.fileno, "HAL", 0) }
109
+ end
110
+
111
+ def test_writev
112
+ assert_respond_to(IO, :writev)
113
+ assert_equal(10, IO.writev(@fh.fileno, %w(hello world)))
114
+ end
115
+
116
+ def test_writev_retry
117
+ empty = ""
118
+ if empty.respond_to?(:force_encoding)
119
+ empty.force_encoding(Encoding::BINARY)
120
+ end
121
+
122
+ # bs * count should be > PIPE_BUF
123
+ [ true, false ].each do |nonblock|
124
+ [ [ 512, 512 ], [ 131073, 3 ], [ 4098, 64 ] ].each do |(bs,count)|
125
+ rd, wr = IO.pipe
126
+ wr.nonblock = nonblock
127
+ buf = File.open("/dev/urandom", "rb") { |fp| fp.sysread(bs) }
128
+ vec = (1..count).map { buf }
129
+ pid = fork do
130
+ wr.close
131
+ tmp = []
132
+ sleep 0.1
133
+ begin
134
+ tmp << rd.readpartial(8192, buf)
135
+ rescue EOFError
136
+ break
137
+ end while true
138
+ ok = (vec.join(empty) == tmp.join(empty))
139
+ exit! ok
140
+ end
141
+ assert_nothing_raised { rd.close }
142
+ assert_equal(bs * count, IO.writev(wr.fileno, vec))
143
+ assert_nothing_raised { wr.close }
144
+ _, status = Process.waitpid2(pid)
145
+ assert status.success?
146
+ end
147
+ end
148
+ end
149
+
150
+ def teardown
151
+ @fh.close rescue nil
152
+ @fh = nil
153
+ File.delete(@file) if File.exists?(@file)
154
+ end
111
155
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-extra
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 2
8
+ - 2
9
+ version: 1.2.2
5
10
  platform: ruby
6
11
  authors:
7
12
  - Daniel J. Berger
@@ -9,19 +14,23 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-01-16 00:00:00 -07:00
17
+ date: 2010-05-01 00:00:00 -06:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: test-unit
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 3
23
31
  version: 2.0.3
24
- version:
32
+ type: :development
33
+ version_requirements: *id001
25
34
  description: " Adds the IO.closefrom, IO.fdwalk, IO.pread, IO.pread_ptr, and IO.pwrite\n class methods as well as the IO#directio and IO#directio? instance\n methods (for those platforms that support them).\n"
26
35
  email: djberg96@gmail.com
27
36
  executables: []
@@ -34,16 +43,16 @@ extra_rdoc_files:
34
43
  - MANIFEST
35
44
  - ext/io/extra.c
36
45
  files:
37
- - MANIFEST
38
- - examples/example_io_extra.rb
39
- - README
40
46
  - CHANGES
47
+ - doc/io_extra.txt
48
+ - examples/example_io_extra.rb
49
+ - ext/extconf.rb
50
+ - ext/io/extra.c
41
51
  - io-extra.gemspec
52
+ - MANIFEST
42
53
  - Rakefile
54
+ - README
43
55
  - test/test_io_extra.rb
44
- - doc/io_extra.txt
45
- - ext/io/extra.c
46
- - ext/extconf.rb
47
56
  has_rdoc: true
48
57
  homepage: http://www.rubyforge.org/projects/shards
49
58
  licenses:
@@ -57,18 +66,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
66
  requirements:
58
67
  - - ">="
59
68
  - !ruby/object:Gem::Version
69
+ segments:
70
+ - 1
71
+ - 8
72
+ - 6
60
73
  version: 1.8.6
61
- version:
62
74
  required_rubygems_version: !ruby/object:Gem::Requirement
63
75
  requirements:
64
76
  - - ">="
65
77
  - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
66
80
  version: "0"
67
- version:
68
81
  requirements: []
69
82
 
70
83
  rubyforge_project: shards
71
- rubygems_version: 1.3.5
84
+ rubygems_version: 1.3.6
72
85
  signing_key:
73
86
  specification_version: 3
74
87
  summary: Adds extra methods to the IO class.