nio4r 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,5 @@
1
1
  rvm:
2
2
  - 1.8.7
3
- - 1.9.2
4
3
  - 1.9.3
5
4
  - ree
6
5
  - ruby-head
data/CHANGES.md CHANGED
@@ -1,3 +1,7 @@
1
+ 0.4.0
2
+ -----
3
+ * OpenSSL::SSL::SSLSocket support
4
+
1
5
  0.3.3
2
6
  -----
3
7
  * NIO::Selector#select_each removed
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source :rubygems
2
2
 
3
+ gem 'jruby-openssl' if defined? JRUBY_VERSION
4
+
3
5
  # Specify your gem's dependencies in nio4r.gemspec
4
6
  gemspec
@@ -3101,7 +3101,15 @@ other and thus cannot be composed.
3101
3101
  And thus we are left with no choice but to patch the internals of libev in
3102
3102
  order to release a mutex at just the precise moment.
3103
3103
 
3104
- Let this be a lesson to the all: CALLBACKS FUCKING BLOW
3104
+ This is a great example of a situation where granular locking and unlocking
3105
+ of the GVL is practically required. The goal is to get as close to the
3106
+ system call as possible, and to keep the GVL unlocked for the shortest
3107
+ amount of time possible.
3108
+
3109
+ Perhaps Ruby could benefit from such an API, e.g:
3110
+
3111
+ rb_thread_unsafe_dangerous_crazy_blocking_region_begin(...);
3112
+ rb_thread_unsafe_dangerous_crazy_blocking_region_end(...);
3105
3113
 
3106
3114
  #######################################################################
3107
3115
  */
@@ -130,6 +130,12 @@ public class Nio4r implements Library {
130
130
  Ruby runtime = context.getRuntime();
131
131
  return this.selector.isOpen() ? runtime.getFalse() : runtime.getTrue();
132
132
  }
133
+
134
+ @JRubyMethod(name = "empty?")
135
+ public IRubyObject isEmpty(ThreadContext context) {
136
+ Ruby runtime = context.getRuntime();
137
+ return this.selector.keys().isEmpty() ? runtime.getTrue() : runtime.getFalse();
138
+ }
133
139
 
134
140
  @JRubyMethod
135
141
  public IRubyObject register(ThreadContext context, IRubyObject io, IRubyObject interests) {
@@ -28,6 +28,7 @@ static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self);
28
28
  static VALUE NIO_Selector_wakeup(VALUE self);
29
29
  static VALUE NIO_Selector_close(VALUE self);
30
30
  static VALUE NIO_Selector_closed(VALUE self);
31
+ static VALUE NIO_Selector_is_empty(VALUE self);
31
32
 
32
33
  /* Internal functions */
33
34
  static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VALUE *args);
@@ -60,6 +61,7 @@ void Init_NIO_Selector()
60
61
  rb_define_method(cNIO_Selector, "wakeup", NIO_Selector_wakeup, 0);
61
62
  rb_define_method(cNIO_Selector, "close", NIO_Selector_close, 0);
62
63
  rb_define_method(cNIO_Selector, "closed?", NIO_Selector_closed, 0);
64
+ rb_define_method(cNIO_Selector, "empty?", NIO_Selector_is_empty, 0);
63
65
 
64
66
  cNIO_Monitor = rb_define_class_under(mNIO, "Monitor", rb_cObject);
65
67
  }
@@ -393,6 +395,15 @@ static VALUE NIO_Selector_closed(VALUE self)
393
395
  return selector->closed ? Qtrue : Qfalse;
394
396
  }
395
397
 
398
+ /* True if there are monitors on the loop */
399
+ static VALUE NIO_Selector_is_empty(VALUE self)
400
+ {
401
+ VALUE selectables = rb_ivar_get(self, rb_intern("selectables"));
402
+
403
+ return rb_funcall(selectables, rb_intern("empty?"), 0) == Qtrue ? Qtrue : Qfalse;
404
+ }
405
+
406
+
396
407
  /* Called whenever a timeout fires on the event loop */
397
408
  static void NIO_Selector_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
398
409
  {
@@ -49,16 +49,13 @@ module NIO
49
49
  @selectables.each do |io, monitor|
50
50
  readers << io if monitor.interests == :r || monitor.interests == :rw
51
51
  writers << io if monitor.interests == :w || monitor.interests == :rw
52
+ monitor.readiness = nil
52
53
  end
53
54
 
54
55
  ready_readers, ready_writers = Kernel.select readers, writers, [], timeout
55
56
  return unless ready_readers # timeout or wakeup
56
-
57
- if block_given?
58
- result = 0
59
- else
60
- result = []
61
- end
57
+
58
+ selected_monitors = Set.new
62
59
 
63
60
  ready_readers.each do |io|
64
61
  if io == @wakeup
@@ -76,34 +73,29 @@ module NIO
76
73
  else
77
74
  monitor = @selectables[io]
78
75
  monitor.readiness = :r
79
-
80
- if block_given?
81
- yield monitor
82
- result += 1
83
- else
84
- result << monitor
85
- end
76
+ selected_monitors << monitor
86
77
  end
87
78
  end
88
-
89
- ready_readwriters = ready_readers & ready_writers
90
- ready_writers = ready_writers - ready_readwriters
91
-
92
- [[ready_writers, :w], [ready_readwriters, :rw]].each do |ios, readiness|
93
- ios.each do |io|
94
- monitor = @selectables[io]
95
- monitor.readiness = readiness
96
-
97
- if block_given?
98
- yield monitor
99
- result += 1
100
- else
101
- result << monitor
102
- end
79
+
80
+ ready_writers.each do |io|
81
+ monitor = @selectables[io]
82
+ monitor.readiness = case monitor.readiness
83
+ when :r
84
+ :rw
85
+ else
86
+ :w
103
87
  end
88
+ selected_monitors << monitor
89
+ end
90
+
91
+ if block_given?
92
+ selected_monitors.each do |m|
93
+ yield m
94
+ end
95
+ selected_monitors.size
96
+ else
97
+ selected_monitors
104
98
  end
105
-
106
- result
107
99
  end
108
100
  end
109
101
 
@@ -132,5 +124,9 @@ module NIO
132
124
 
133
125
  # Is this selector closed?
134
126
  def closed?; @closed end
127
+
128
+ def empty?
129
+ @selectables.empty?
130
+ end
135
131
  end
136
132
  end
@@ -1,3 +1,3 @@
1
1
  module NIO
2
- VERSION = "0.3.3"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe "IO.pipe" do
4
+ let(:pair) { IO.pipe }
5
+
6
+ let :unreadable_subject do
7
+ pair.first
8
+ end
9
+ let :readable_subject do
10
+ pipe, peer = pair
11
+ peer << "data"
12
+ pipe
13
+ end
14
+
15
+ let :writable_subject do
16
+ pair.last
17
+ end
18
+ let :unwritable_subject do
19
+ reader, pipe = IO.pipe
20
+
21
+ begin
22
+ pipe.write_nonblock "JUNK IN THE TUBES"
23
+ _, writers = select [], [pipe], [], 0
24
+ rescue Errno::EPIPE
25
+ break
26
+ end while writers and writers.include? pipe
27
+
28
+ pipe
29
+ end
30
+
31
+ it_behaves_like "an NIO selectable"
32
+ it_behaves_like "an NIO selectable stream"
33
+ end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+ require 'openssl'
3
+
4
+ describe OpenSSL::SSL::SSLSocket, :if => RUBY_VERSION >= "1.9.0" do
5
+ let(:tcp_port) { 34567 }
6
+
7
+ let(:ssl_key) { OpenSSL::PKey::RSA.new(1024) }
8
+
9
+ let(:ssl_cert) do
10
+ name = OpenSSL::X509::Name.new([%w[CN localhost]])
11
+ OpenSSL::X509::Certificate.new.tap do |cert|
12
+ cert.version = 2
13
+ cert.serial = 1
14
+ cert.issuer = name
15
+ cert.subject = name
16
+ cert.not_before = Time.now
17
+ cert.not_after = Time.now + (365 * 24 *60 *60)
18
+ cert.public_key = ssl_key.public_key
19
+
20
+ cert.sign(ssl_key, OpenSSL::Digest::SHA1.new)
21
+ end
22
+ end
23
+
24
+ let(:ssl_server_context) do
25
+ OpenSSL::SSL::SSLContext.new.tap do |ctx|
26
+ ctx.cert = ssl_cert
27
+ ctx.key = ssl_key
28
+ end
29
+ end
30
+
31
+ let :readable_subject do
32
+ server = TCPServer.new("localhost", tcp_port)
33
+ client = TCPSocket.open("localhost", tcp_port)
34
+ peer = server.accept
35
+
36
+ ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
37
+ ssl_peer.sync_close = true
38
+
39
+ ssl_client = OpenSSL::SSL::SSLSocket.new(client)
40
+ ssl_client.sync_close = true
41
+
42
+ # SSLSocket#connect and #accept are blocking calls.
43
+ thread = Thread.new { ssl_client.connect }
44
+
45
+ ssl_peer.accept
46
+ ssl_peer << "data"
47
+
48
+ thread.join
49
+ pending "Failed to produce a readable SSL socket" unless select([ssl_client], [], [], 0)
50
+
51
+ ssl_client
52
+ end
53
+
54
+ let :unreadable_subject do
55
+ server = TCPServer.new("localhost", tcp_port + 1)
56
+ client = TCPSocket.new("localhost", tcp_port + 1)
57
+ peer = server.accept
58
+
59
+ ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
60
+ ssl_peer.sync_close = true
61
+
62
+ ssl_client = OpenSSL::SSL::SSLSocket.new(client)
63
+ ssl_client.sync_close = true
64
+
65
+ # SSLSocket#connect and #accept are blocking calls.
66
+ thread = Thread.new { ssl_client.connect }
67
+ ssl_peer.accept
68
+ thread.join
69
+
70
+ pending "Failed to produce an unreadable socket" if select([ssl_client], [], [], 0)
71
+ ssl_client
72
+ end
73
+
74
+ let :writable_subject do
75
+ server = TCPServer.new("localhost", tcp_port + 2)
76
+ client = TCPSocket.new("localhost", tcp_port + 2)
77
+ peer = server.accept
78
+
79
+ ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
80
+ ssl_peer.sync_close = true
81
+
82
+ ssl_client = OpenSSL::SSL::SSLSocket.new(client)
83
+ ssl_client.sync_close = true
84
+
85
+ # SSLSocket#connect and #accept are blocking calls.
86
+ thread = Thread.new { ssl_client.connect }
87
+
88
+ ssl_peer.accept
89
+ thread.join
90
+
91
+ ssl_client
92
+ end
93
+
94
+ let :unwritable_subject do
95
+ server = TCPServer.new("localhost", tcp_port + 3)
96
+ client = TCPSocket.open("localhost", tcp_port + 3)
97
+ peer = server.accept
98
+
99
+ ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
100
+ ssl_peer.sync_close = true
101
+
102
+ ssl_client = OpenSSL::SSL::SSLSocket.new(client)
103
+ ssl_client.sync_close = true
104
+
105
+ # SSLSocket#connect and #accept are blocking calls.
106
+ thread = Thread.new { ssl_client.connect }
107
+
108
+ ssl_peer.accept
109
+ thread.join
110
+
111
+ begin
112
+ _, writers = select [], [ssl_client], [], 0
113
+ count = ssl_client.write_nonblock "X" * 1024
114
+ count.should_not == 0
115
+ rescue IO::WaitReadable, IO::WaitWritable
116
+ pending "SSL will report writable but not accept writes"
117
+ raise if(writers.include? ssl_client)
118
+ end while writers and writers.include? ssl_client
119
+
120
+ # I think the kernel might manage to drain its buffer a bit even after
121
+ # the socket first goes unwritable. Attempt to sleep past this and then
122
+ # attempt to write again
123
+ sleep 0.1
124
+
125
+ # Once more for good measure!
126
+ begin
127
+ # ssl_client.write_nonblock "X" * 1024
128
+ loop { ssl_client.write_nonblock "X" * 1024 }
129
+ rescue OpenSSL::SSL::SSLError
130
+ end
131
+
132
+ # Sanity check to make sure we actually produced an unwritable socket
133
+ # if select([], [ssl_client], [], 0)
134
+ # pending "Failed to produce an unwritable socket"
135
+ # end
136
+
137
+ ssl_client
138
+ end
139
+
140
+ let :pair do
141
+ pending "figure out why newly created sockets are selecting readable immediately"
142
+
143
+ server = TCPServer.new("localhost", tcp_port + 4)
144
+ client = TCPSocket.open("localhost", tcp_port + 4)
145
+ peer = server.accept
146
+
147
+ ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
148
+ ssl_peer.sync_close = true
149
+
150
+ ssl_client = OpenSSL::SSL::SSLSocket.new(client)
151
+ ssl_client.sync_close = true
152
+
153
+ # SSLSocket#connect and #accept are blocking calls.
154
+ thread = Thread.new { ssl_client.connect }
155
+ ssl_peer.accept
156
+
157
+ [thread.value, ssl_peer]
158
+ end
159
+
160
+ it_behaves_like "an NIO selectable"
161
+ it_behaves_like "an NIO selectable stream"
162
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe TCPSocket do
4
+ port_offset = 0
5
+ let(:tcp_port) { 12345 + (port_offset += 1) }
6
+
7
+ let :readable_subject do
8
+ server = TCPServer.new("localhost", tcp_port)
9
+ sock = TCPSocket.open("localhost", tcp_port)
10
+ peer = server.accept
11
+ peer << "data"
12
+ sock
13
+ end
14
+
15
+ let :unreadable_subject do
16
+ TCPServer.new("localhost", tcp_port)
17
+ sock = TCPSocket.new("localhost", tcp_port)
18
+
19
+ # Sanity check to make sure we actually produced an unreadable socket
20
+ if select([sock], [], [], 0)
21
+ pending "Failed to produce an unreadable socket"
22
+ end
23
+
24
+ sock
25
+ end
26
+
27
+ let :writable_subject do
28
+ TCPServer.new("localhost", tcp_port)
29
+ TCPSocket.new("localhost", tcp_port)
30
+ end
31
+
32
+ let :unwritable_subject do
33
+ server = TCPServer.new("localhost", tcp_port)
34
+ sock = TCPSocket.open("localhost", tcp_port)
35
+ peer = server.accept
36
+
37
+ begin
38
+ sock.write_nonblock "X" * 1024
39
+ _, writers = select [], [sock], [], 0
40
+ end while writers and writers.include? sock
41
+
42
+ # I think the kernel might manage to drain its buffer a bit even after
43
+ # the socket first goes unwritable. Attempt to sleep past this and then
44
+ # attempt to write again
45
+ sleep 0.1
46
+
47
+ # Once more for good measure!
48
+ begin
49
+ sock.write_nonblock "X" * 1024
50
+ rescue Errno::EWOULDBLOCK
51
+ end
52
+
53
+ # Sanity check to make sure we actually produced an unwritable socket
54
+ if select([], [sock], [], 0)
55
+ pending "Failed to produce an unwritable socket"
56
+ end
57
+
58
+ sock
59
+ end
60
+
61
+ let :pair do
62
+ server = TCPServer.new("localhost", tcp_port)
63
+ client = TCPSocket.open("localhost", tcp_port)
64
+ [client, server.accept]
65
+ end
66
+
67
+ it_behaves_like "an NIO selectable"
68
+ it_behaves_like "an NIO selectable stream"
69
+ it_behaves_like "an NIO bidirectional stream"
70
+
71
+ context :connect do
72
+ it "selects writable when connected" do
73
+ selector = NIO::Selector.new
74
+ server = TCPServer.new('127.0.0.1', tcp_port)
75
+
76
+ client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
77
+ monitor = selector.register(client, :w)
78
+
79
+ expect do
80
+ client.connect_nonblock Socket.sockaddr_in(tcp_port, '127.0.0.1')
81
+ end.to raise_exception Errno::EINPROGRESS
82
+
83
+ selector.select(0).should include monitor
84
+ result = client.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR)
85
+ result.unpack('i').first.should be_zero
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe UDPSocket do
4
+ let(:udp_port) { 23456 }
5
+
6
+ let :readable_subject do
7
+ sock = UDPSocket.new
8
+ sock.bind('localhost', udp_port)
9
+
10
+ peer = UDPSocket.new
11
+ peer.send("hi there", 0, 'localhost', udp_port)
12
+
13
+ sock
14
+ end
15
+
16
+ let :unreadable_subject do
17
+ sock = UDPSocket.new
18
+ sock.bind('localhost', udp_port + 1)
19
+ sock
20
+ end
21
+
22
+ let :writable_subject do
23
+ pending "come up with a writable UDPSocket example"
24
+ end
25
+
26
+ let :unwritable_subject do
27
+ pending "come up with a UDPSocket that's blocked on writing"
28
+ end
29
+
30
+ it_behaves_like "an NIO selectable"
31
+ end
@@ -34,6 +34,14 @@ describe NIO::Selector do
34
34
  subject.should_not be_registered(reader)
35
35
  monitor.should be_closed
36
36
  end
37
+
38
+ it "reports if it is empty" do
39
+ subject.should be_empty
40
+
41
+ monitor = subject.register(reader, :r)
42
+
43
+ subject.should_not be_empty
44
+ end
37
45
 
38
46
  # This spec might seem a bit silly, but this actually something the
39
47
  # Java NIO API specifically precludes that we need to work around
@@ -1,3 +1,4 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
- require 'nio'
3
+ require 'nio'
4
+ require 'support/selectable_examples'
@@ -0,0 +1,56 @@
1
+ shared_context "an NIO selectable" do
2
+ let(:selector) { NIO::Selector.new }
3
+
4
+ it "selects readable objects" do
5
+ monitor = selector.register(readable_subject, :r)
6
+ ready = selector.select(0)
7
+ ready.should be_an Enumerable
8
+ ready.should include monitor
9
+ end
10
+
11
+ it "does not select unreadable objects" do
12
+ monitor = selector.register(unreadable_subject, :r)
13
+ selector.select(0).should be_nil
14
+ end
15
+
16
+ it "selects writable objects" do
17
+ monitor = selector.register(writable_subject, :w)
18
+ ready = selector.select(0)
19
+ ready.should be_an Enumerable
20
+ ready.should include monitor
21
+ end
22
+
23
+ it "does not select unwritable objects" do
24
+ monitor = selector.register(unwritable_subject, :w)
25
+ selector.select(0).should be_nil
26
+ end
27
+ end
28
+
29
+ shared_context "an NIO selectable stream" do
30
+ let(:selector) { NIO::Selector.new }
31
+ let(:stream) { pair.first }
32
+ let(:peer) { pair.last }
33
+
34
+ it "selects readable when the other end closes" do
35
+ monitor = selector.register(stream, :r)
36
+ selector.select(0).should be_nil
37
+
38
+ peer.close
39
+ #Wait and give the TCP session time to close
40
+ selector.select(0.1).should include monitor
41
+ end
42
+ end
43
+
44
+ shared_context "an NIO bidirectional stream" do
45
+ let(:selector) { NIO::Selector.new }
46
+ let(:stream) { pair.first }
47
+ let(:peer) { pair.last }
48
+
49
+ it "selects readable and writable" do
50
+ monitor = selector.register(readable_subject, :rw)
51
+ selector.select(0) do |m|
52
+ m.readiness.should == :rw
53
+ end
54
+ end
55
+
56
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nio4r
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-08 00:00:00.000000000 Z
12
+ date: 2012-06-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake-compiler
16
- requirement: &70222409202860 !ruby/object:Gem::Requirement
16
+ requirement: &70312140700780 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70222409202860
24
+ version_requirements: *70312140700780
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70222409202280 !ruby/object:Gem::Requirement
27
+ requirement: &70312140700220 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70222409202280
35
+ version_requirements: *70312140700220
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70222409201720 !ruby/object:Gem::Requirement
38
+ requirement: &70312140699800 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70222409201720
46
+ version_requirements: *70312140699800
47
47
  description: New IO for Ruby
48
48
  email:
49
49
  - tony.arcieri@gmail.com
@@ -90,9 +90,13 @@ files:
90
90
  - nio4r.gemspec
91
91
  - spec/nio/acceptables_spec.rb
92
92
  - spec/nio/monitor_spec.rb
93
- - spec/nio/selectables_spec.rb
93
+ - spec/nio/selectables/pipe_spec.rb
94
+ - spec/nio/selectables/ssl_socket_spec.rb
95
+ - spec/nio/selectables/tcp_socket_spec.rb
96
+ - spec/nio/selectables/udp_socket_spec.rb
94
97
  - spec/nio/selector_spec.rb
95
98
  - spec/spec_helper.rb
99
+ - spec/support/selectable_examples.rb
96
100
  - tasks/extension.rake
97
101
  - tasks/rspec.rake
98
102
  homepage: https://github.com/tarcieri/nio4r
@@ -122,6 +126,10 @@ summary: NIO provides a high performance selector API for monitoring IO objects
122
126
  test_files:
123
127
  - spec/nio/acceptables_spec.rb
124
128
  - spec/nio/monitor_spec.rb
125
- - spec/nio/selectables_spec.rb
129
+ - spec/nio/selectables/pipe_spec.rb
130
+ - spec/nio/selectables/ssl_socket_spec.rb
131
+ - spec/nio/selectables/tcp_socket_spec.rb
132
+ - spec/nio/selectables/udp_socket_spec.rb
126
133
  - spec/nio/selector_spec.rb
127
134
  - spec/spec_helper.rb
135
+ - spec/support/selectable_examples.rb
@@ -1,182 +0,0 @@
1
- describe "NIO selectables" do
2
- let(:selector) { NIO::Selector.new }
3
-
4
- shared_context "an NIO selectable" do
5
- it "selects readable objects" do
6
- monitor = selector.register(readable_subject, :r)
7
- selector.select(0).should include monitor
8
- end
9
-
10
- it "does not select unreadable objects" do
11
- monitor = selector.register(unreadable_subject, :r)
12
- selector.select(0).should be_nil
13
- end
14
-
15
- it "selects writable objects" do
16
- monitor = selector.register(writable_subject, :w)
17
- selector.select(0).should include monitor
18
- end
19
-
20
- it "does not select unwritable objects" do
21
- monitor = selector.register(unwritable_subject, :w)
22
- selector.select(0).should be_nil
23
- end
24
- end
25
-
26
- shared_context "an NIO selectable stream" do
27
- let(:stream) { pair.first }
28
- let(:peer) { pair.last }
29
-
30
- it "selects readable when the other end closes" do
31
- monitor = selector.register(stream, :r)
32
- selector.select(0).should be_nil
33
-
34
- peer.close
35
- selector.select(0).should include monitor
36
- end
37
- end
38
-
39
- describe "IO.pipe" do
40
- let(:pair) { IO.pipe }
41
-
42
- let :unreadable_subject do
43
- pair.first
44
- end
45
- let :readable_subject do
46
- pipe, peer = pair
47
- peer << "data"
48
- pipe
49
- end
50
-
51
- let :writable_subject do
52
- pair.last
53
- end
54
- let :unwritable_subject do
55
- reader, pipe = IO.pipe
56
-
57
- begin
58
- pipe.write_nonblock "JUNK IN THE TUBES"
59
- _, writers = select [], [pipe], [], 0
60
- rescue Errno::EPIPE
61
- break
62
- end while writers and writers.include? pipe
63
-
64
- pipe
65
- end
66
-
67
- it_behaves_like "an NIO selectable"
68
- it_behaves_like "an NIO selectable stream"
69
- end
70
-
71
- describe TCPSocket do
72
- let(:tcp_port) { 12345 }
73
-
74
- let :readable_subject do
75
- server = TCPServer.new("localhost", tcp_port)
76
- sock = TCPSocket.open("localhost", tcp_port)
77
- peer = server.accept
78
- peer << "data"
79
- sock
80
- end
81
-
82
- let :unreadable_subject do
83
- TCPServer.new("localhost", tcp_port + 1)
84
- sock = TCPSocket.new("localhost", tcp_port + 1)
85
-
86
- # Sanity check to make sure we actually produced an unreadable socket
87
- if select([sock], [], [], 0)
88
- pending "Failed to produce an unreadable socket"
89
- end
90
-
91
- sock
92
- end
93
-
94
- let :writable_subject do
95
- TCPServer.new("localhost", tcp_port + 2)
96
- TCPSocket.new("localhost", tcp_port + 2)
97
- end
98
-
99
- let :unwritable_subject do
100
- server = TCPServer.new("localhost", tcp_port + 3)
101
- sock = TCPSocket.open("localhost", tcp_port + 3)
102
- peer = server.accept
103
-
104
- begin
105
- sock.write_nonblock "X" * 1024
106
- _, writers = select [], [sock], [], 0
107
- end while writers and writers.include? sock
108
-
109
- # I think the kernel might manage to drain its buffer a bit even after
110
- # the socket first goes unwritable. Attempt to sleep past this and then
111
- # attempt to write again
112
- sleep 0.1
113
-
114
- # Once more for good measure!
115
- begin
116
- sock.write_nonblock "X" * 1024
117
- rescue Errno::EWOULDBLOCK
118
- end
119
-
120
- # Sanity check to make sure we actually produced an unwritable socket
121
- if select([], [sock], [], 0)
122
- pending "Failed to produce an unwritable socket"
123
- end
124
-
125
- sock
126
- end
127
-
128
- let :pair do
129
- server = TCPServer.new("localhost", tcp_port + 4)
130
- client = TCPSocket.open("localhost", tcp_port + 4)
131
- [client, server.accept]
132
- end
133
-
134
- it_behaves_like "an NIO selectable"
135
- it_behaves_like "an NIO selectable stream"
136
-
137
- it "selects writable when connected" do
138
- server = TCPServer.new('127.0.0.1', tcp_port + 5)
139
-
140
- client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
141
- monitor = selector.register(client, :w)
142
-
143
- expect do
144
- client.connect_nonblock Socket.sockaddr_in(tcp_port + 5, '127.0.0.1')
145
- end.to raise_exception Errno::EINPROGRESS
146
-
147
- selector.select(0).should include monitor
148
- result = client.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR)
149
- result.unpack('i').first.should be_zero
150
- end
151
- end
152
-
153
- describe UDPSocket do
154
- let(:udp_port) { 23456 }
155
-
156
- let :readable_subject do
157
- sock = UDPSocket.new
158
- sock.bind('localhost', udp_port)
159
-
160
- peer = UDPSocket.new
161
- peer.send("hi there", 0, 'localhost', udp_port)
162
-
163
- sock
164
- end
165
-
166
- let :unreadable_subject do
167
- sock = UDPSocket.new
168
- sock.bind('localhost', udp_port + 1)
169
- sock
170
- end
171
-
172
- let :writable_subject do
173
- pending "come up with a writable UDPSocket example"
174
- end
175
-
176
- let :unwritable_subject do
177
- pending "come up with a UDPSocket that's blocked on writing"
178
- end
179
-
180
- it_behaves_like "an NIO selectable"
181
- end
182
- end