nio4r 0.3.3 → 0.4.0

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.
@@ -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