io-extra 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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.