nio4r 1.2.1 → 2.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/workflow.yml +43 -0
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.rubocop.yml +70 -31
- data/CHANGES.md +190 -42
- data/Gemfile +8 -4
- data/Guardfile +10 -0
- data/README.md +102 -147
- data/Rakefile +3 -4
- data/examples/echo_server.rb +3 -2
- data/ext/libev/Changes +44 -13
- data/ext/libev/README +2 -1
- data/ext/libev/ev.c +314 -225
- data/ext/libev/ev.h +90 -88
- data/ext/libev/ev_epoll.c +30 -16
- data/ext/libev/ev_kqueue.c +19 -9
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +19 -11
- data/ext/libev/ev_port.c +13 -6
- data/ext/libev/ev_select.c +4 -2
- data/ext/libev/ev_vars.h +14 -3
- data/ext/libev/ev_wrap.h +16 -0
- data/ext/nio4r/bytebuffer.c +429 -0
- data/ext/nio4r/extconf.rb +17 -30
- data/ext/nio4r/monitor.c +113 -49
- data/ext/nio4r/nio4r.h +11 -13
- data/ext/nio4r/org/nio4r/ByteBuffer.java +293 -0
- data/ext/nio4r/org/nio4r/Monitor.java +175 -0
- data/ext/nio4r/org/nio4r/Nio4r.java +22 -391
- data/ext/nio4r/org/nio4r/Selector.java +299 -0
- data/ext/nio4r/selector.c +155 -68
- data/lib/nio.rb +4 -4
- data/lib/nio/bytebuffer.rb +229 -0
- data/lib/nio/monitor.rb +73 -11
- data/lib/nio/selector.rb +64 -21
- data/lib/nio/version.rb +1 -1
- data/nio4r.gemspec +34 -20
- data/{tasks → rakelib}/extension.rake +4 -0
- data/{tasks → rakelib}/rspec.rake +2 -0
- data/{tasks → rakelib}/rubocop.rake +2 -0
- data/spec/nio/acceptables_spec.rb +5 -5
- data/spec/nio/bytebuffer_spec.rb +354 -0
- data/spec/nio/monitor_spec.rb +128 -79
- data/spec/nio/selectables/pipe_spec.rb +12 -3
- data/spec/nio/selectables/ssl_socket_spec.rb +61 -29
- data/spec/nio/selectables/tcp_socket_spec.rb +47 -34
- data/spec/nio/selectables/udp_socket_spec.rb +24 -7
- data/spec/nio/selector_spec.rb +65 -16
- data/spec/spec_helper.rb +12 -3
- data/spec/support/selectable_examples.rb +45 -18
- metadata +33 -23
- data/.rubocop_todo.yml +0 -35
- data/.travis.yml +0 -27
- data/LICENSE.txt +0 -20
- data/ext/libev/README.embed +0 -3
- data/ext/libev/test_libev_win32.c +0 -123
data/lib/nio.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "thread"
|
4
3
|
require "socket"
|
5
4
|
require "nio/version"
|
6
5
|
|
@@ -18,7 +17,8 @@ end
|
|
18
17
|
if ENV["NIO4R_PURE"] == "true" || (Gem.win_platform? && !defined?(JRUBY_VERSION))
|
19
18
|
require "nio/monitor"
|
20
19
|
require "nio/selector"
|
21
|
-
|
20
|
+
require "nio/bytebuffer"
|
21
|
+
NIO::ENGINE = "ruby"
|
22
22
|
else
|
23
23
|
require "nio4r_ext"
|
24
24
|
|
@@ -26,8 +26,8 @@ else
|
|
26
26
|
require "java"
|
27
27
|
require "jruby"
|
28
28
|
org.nio4r.Nio4r.new.load(JRuby.runtime, false)
|
29
|
-
NIO::ENGINE = "java"
|
29
|
+
NIO::ENGINE = "java"
|
30
30
|
else
|
31
|
-
NIO::ENGINE = "libev"
|
31
|
+
NIO::ENGINE = "libev"
|
32
32
|
end
|
33
33
|
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NIO
|
4
|
+
# Efficient byte buffers for performant I/O operations
|
5
|
+
class ByteBuffer
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :position, :limit, :capacity
|
9
|
+
|
10
|
+
# Insufficient capacity in buffer
|
11
|
+
OverflowError = Class.new(IOError)
|
12
|
+
|
13
|
+
# Not enough data remaining in buffer
|
14
|
+
UnderflowError = Class.new(IOError)
|
15
|
+
|
16
|
+
# Mark has not been set
|
17
|
+
MarkUnsetError = Class.new(IOError)
|
18
|
+
|
19
|
+
# Create a new ByteBuffer, either with a specified capacity or populating
|
20
|
+
# it from a given string
|
21
|
+
#
|
22
|
+
# @param capacity [Integer] size of buffer in bytes
|
23
|
+
#
|
24
|
+
# @return [NIO::ByteBuffer]
|
25
|
+
def initialize(capacity)
|
26
|
+
raise TypeError, "no implicit conversion of #{capacity.class} to Integer" unless capacity.is_a?(Integer)
|
27
|
+
|
28
|
+
@capacity = capacity
|
29
|
+
clear
|
30
|
+
end
|
31
|
+
|
32
|
+
# Clear the buffer, resetting it to the default state
|
33
|
+
def clear
|
34
|
+
@buffer = ("\0" * @capacity).force_encoding(Encoding::BINARY)
|
35
|
+
@position = 0
|
36
|
+
@limit = @capacity
|
37
|
+
@mark = nil
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Set the position to the given value. New position must be less than limit.
|
43
|
+
# Preserves mark if it's less than the new position, otherwise clears it.
|
44
|
+
#
|
45
|
+
# @param new_position [Integer] position in the buffer
|
46
|
+
#
|
47
|
+
# @raise [ArgumentError] new position was invalid
|
48
|
+
def position=(new_position)
|
49
|
+
raise ArgumentError, "negative position given" if new_position < 0
|
50
|
+
raise ArgumentError, "specified position exceeds capacity" if new_position > @capacity
|
51
|
+
|
52
|
+
@mark = nil if @mark && @mark > new_position
|
53
|
+
@position = new_position
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set the limit to the given value. New limit must be less than capacity.
|
57
|
+
# Preserves limit and mark if they're less than the new limit, otherwise
|
58
|
+
# sets position to the new limit and clears the mark.
|
59
|
+
#
|
60
|
+
# @param new_limit [Integer] position in the buffer
|
61
|
+
#
|
62
|
+
# @raise [ArgumentError] new limit was invalid
|
63
|
+
def limit=(new_limit)
|
64
|
+
raise ArgumentError, "negative limit given" if new_limit < 0
|
65
|
+
raise ArgumentError, "specified limit exceeds capacity" if new_limit > @capacity
|
66
|
+
|
67
|
+
@position = new_limit if @position > new_limit
|
68
|
+
@mark = nil if @mark && @mark > new_limit
|
69
|
+
@limit = new_limit
|
70
|
+
end
|
71
|
+
|
72
|
+
# Number of bytes remaining in the buffer before the limit
|
73
|
+
#
|
74
|
+
# @return [Integer] number of bytes remaining
|
75
|
+
def remaining
|
76
|
+
@limit - @position
|
77
|
+
end
|
78
|
+
|
79
|
+
# Does the ByteBuffer have any space remaining?
|
80
|
+
#
|
81
|
+
# @return [true, false]
|
82
|
+
def full?
|
83
|
+
remaining.zero?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Obtain the requested number of bytes from the buffer, advancing the position.
|
87
|
+
# If no length is given, all remaining bytes are consumed.
|
88
|
+
#
|
89
|
+
# @raise [NIO::ByteBuffer::UnderflowError] not enough data remaining in buffer
|
90
|
+
#
|
91
|
+
# @return [String] bytes read from buffer
|
92
|
+
def get(length = remaining)
|
93
|
+
raise ArgumentError, "negative length given" if length < 0
|
94
|
+
raise UnderflowError, "not enough data in buffer" if length > @limit - @position
|
95
|
+
|
96
|
+
result = @buffer[@position...length]
|
97
|
+
@position += length
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
# Obtain the byte at a given index in the buffer as an Integer
|
102
|
+
#
|
103
|
+
# @raise [ArgumentError] index is invalid (either negative or larger than limit)
|
104
|
+
#
|
105
|
+
# @return [Integer] byte at the given index
|
106
|
+
def [](index)
|
107
|
+
raise ArgumentError, "negative index given" if index < 0
|
108
|
+
raise ArgumentError, "specified index exceeds limit" if index >= @limit
|
109
|
+
|
110
|
+
@buffer.bytes[index]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Add a String to the buffer
|
114
|
+
#
|
115
|
+
# @param str [#to_str] data to add to the buffer
|
116
|
+
#
|
117
|
+
# @raise [TypeError] given a non-string type
|
118
|
+
# @raise [NIO::ByteBuffer::OverflowError] buffer is full
|
119
|
+
#
|
120
|
+
# @return [self]
|
121
|
+
def put(str)
|
122
|
+
raise TypeError, "expected String, got #{str.class}" unless str.respond_to?(:to_str)
|
123
|
+
|
124
|
+
str = str.to_str
|
125
|
+
|
126
|
+
raise OverflowError, "buffer is full" if str.length > @limit - @position
|
127
|
+
|
128
|
+
@buffer[@position...str.length] = str
|
129
|
+
@position += str.length
|
130
|
+
self
|
131
|
+
end
|
132
|
+
alias << put
|
133
|
+
|
134
|
+
# Perform a non-blocking read from the given IO object into the buffer
|
135
|
+
# Reads as much data as is immediately available and returns
|
136
|
+
#
|
137
|
+
# @param [IO] Ruby IO object to read from
|
138
|
+
#
|
139
|
+
# @return [Integer] number of bytes read (0 if none were available)
|
140
|
+
def read_from(io)
|
141
|
+
nbytes = @limit - @position
|
142
|
+
raise OverflowError, "buffer is full" if nbytes.zero?
|
143
|
+
|
144
|
+
bytes_read = IO.try_convert(io).read_nonblock(nbytes, exception: false)
|
145
|
+
return 0 if bytes_read == :wait_readable
|
146
|
+
|
147
|
+
self << bytes_read
|
148
|
+
bytes_read.length
|
149
|
+
end
|
150
|
+
|
151
|
+
# Perform a non-blocking write of the buffer's contents to the given I/O object
|
152
|
+
# Writes as much data as is immediately possible and returns
|
153
|
+
#
|
154
|
+
# @param [IO] Ruby IO object to write to
|
155
|
+
#
|
156
|
+
# @return [Integer] number of bytes written (0 if the write would block)
|
157
|
+
def write_to(io)
|
158
|
+
nbytes = @limit - @position
|
159
|
+
raise UnderflowError, "no data remaining in buffer" if nbytes.zero?
|
160
|
+
|
161
|
+
bytes_written = IO.try_convert(io).write_nonblock(@buffer[@position...@limit], exception: false)
|
162
|
+
return 0 if bytes_written == :wait_writable
|
163
|
+
|
164
|
+
@position += bytes_written
|
165
|
+
bytes_written
|
166
|
+
end
|
167
|
+
|
168
|
+
# Set the buffer's current position as the limit and set the position to 0
|
169
|
+
def flip
|
170
|
+
@limit = @position
|
171
|
+
@position = 0
|
172
|
+
@mark = nil
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
# Set the buffer's current position to 0, leaving the limit unchanged
|
177
|
+
def rewind
|
178
|
+
@position = 0
|
179
|
+
@mark = nil
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
# Mark a position to return to using the `#reset` method
|
184
|
+
def mark
|
185
|
+
@mark = @position
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
189
|
+
# Reset position to the previously marked location
|
190
|
+
#
|
191
|
+
# @raise [NIO::ByteBuffer::MarkUnsetError] mark has not been set (call `#mark` first)
|
192
|
+
def reset
|
193
|
+
raise MarkUnsetError, "mark has not been set" unless @mark
|
194
|
+
|
195
|
+
@position = @mark
|
196
|
+
self
|
197
|
+
end
|
198
|
+
|
199
|
+
# Move data between the position and limit to the beginning of the buffer
|
200
|
+
# Sets the position to the end of the moved data, and the limit to the capacity
|
201
|
+
def compact
|
202
|
+
@buffer[0...(@limit - @position)] = @buffer[@position...@limit]
|
203
|
+
@position = @limit - @position
|
204
|
+
@limit = capacity
|
205
|
+
self
|
206
|
+
end
|
207
|
+
|
208
|
+
# Iterate over the bytes in the buffer (as Integers)
|
209
|
+
#
|
210
|
+
# @return [self]
|
211
|
+
def each(&block)
|
212
|
+
@buffer[0...@limit].each_byte(&block)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Inspect the state of the buffer
|
216
|
+
#
|
217
|
+
# @return [String] string describing the state of the buffer
|
218
|
+
def inspect
|
219
|
+
format(
|
220
|
+
"#<%s:0x%x @position=%d @limit=%d @capacity=%d>",
|
221
|
+
self.class,
|
222
|
+
object_id << 1,
|
223
|
+
@position,
|
224
|
+
@limit,
|
225
|
+
@capacity
|
226
|
+
)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
data/lib/nio/monitor.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module NIO
|
2
4
|
# Monitors watch IO objects for specific events
|
3
5
|
class Monitor
|
4
6
|
attr_reader :io, :interests, :selector
|
5
7
|
attr_accessor :value, :readiness
|
6
8
|
|
7
|
-
# :nodoc
|
9
|
+
# :nodoc:
|
8
10
|
def initialize(io, interests, selector)
|
9
|
-
unless io.is_a?(
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
unless defined?(::OpenSSL) && io.is_a?(::OpenSSL::SSL::SSLSocket)
|
12
|
+
unless io.is_a?(IO)
|
13
|
+
if IO.respond_to? :try_convert
|
14
|
+
io = IO.try_convert(io)
|
15
|
+
elsif io.respond_to? :to_io
|
16
|
+
io = io.to_io
|
17
|
+
end
|
15
18
|
|
16
|
-
|
19
|
+
raise TypeError, "can't convert #{io.class} into IO" unless io.is_a? IO
|
20
|
+
end
|
17
21
|
end
|
18
22
|
|
19
23
|
@io = io
|
@@ -22,14 +26,72 @@ module NIO
|
|
22
26
|
@closed = false
|
23
27
|
end
|
24
28
|
|
25
|
-
#
|
29
|
+
# Replace the existing interest set with a new one
|
30
|
+
#
|
31
|
+
# @param interests [:r, :w, :rw, nil] I/O readiness we're interested in (read/write/readwrite)
|
32
|
+
#
|
33
|
+
# @return [Symbol] new interests
|
26
34
|
def interests=(interests)
|
27
|
-
|
28
|
-
|
35
|
+
raise EOFError, "monitor is closed" if closed?
|
36
|
+
raise ArgumentError, "bad interests: #{interests}" unless [:r, :w, :rw, nil].include?(interests)
|
29
37
|
|
30
38
|
@interests = interests
|
31
39
|
end
|
32
40
|
|
41
|
+
# Add new interests to the existing interest set
|
42
|
+
#
|
43
|
+
# @param interests [:r, :w, :rw] new I/O interests (read/write/readwrite)
|
44
|
+
#
|
45
|
+
# @return [self]
|
46
|
+
def add_interest(interest)
|
47
|
+
case interest
|
48
|
+
when :r
|
49
|
+
case @interests
|
50
|
+
when :r then @interests = :r
|
51
|
+
when :w then @interests = :rw
|
52
|
+
when :rw then @interests = :rw
|
53
|
+
when nil then @interests = :r
|
54
|
+
end
|
55
|
+
when :w
|
56
|
+
case @interests
|
57
|
+
when :r then @interests = :rw
|
58
|
+
when :w then @interests = :w
|
59
|
+
when :rw then @interests = :rw
|
60
|
+
when nil then @interests = :w
|
61
|
+
end
|
62
|
+
when :rw
|
63
|
+
@interests = :rw
|
64
|
+
else raise ArgumentError, "bad interests: #{interest}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Remove interests from the existing interest set
|
69
|
+
#
|
70
|
+
# @param interests [:r, :w, :rw] I/O interests to remove (read/write/readwrite)
|
71
|
+
#
|
72
|
+
# @return [self]
|
73
|
+
def remove_interest(interest)
|
74
|
+
case interest
|
75
|
+
when :r
|
76
|
+
case @interests
|
77
|
+
when :r then @interests = nil
|
78
|
+
when :w then @interests = :w
|
79
|
+
when :rw then @interests = :w
|
80
|
+
when nil then @interests = nil
|
81
|
+
end
|
82
|
+
when :w
|
83
|
+
case @interests
|
84
|
+
when :r then @interests = :r
|
85
|
+
when :w then @interests = nil
|
86
|
+
when :rw then @interests = :r
|
87
|
+
when nil then @interests = nil
|
88
|
+
end
|
89
|
+
when :rw
|
90
|
+
@interests = nil
|
91
|
+
else raise ArgumentError, "bad interests: #{interest}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
33
95
|
# Is the IO object readable?
|
34
96
|
def readable?
|
35
97
|
readiness == :r || readiness == :rw
|
data/lib/nio/selector.rb
CHANGED
@@ -1,10 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "set"
|
2
4
|
|
3
5
|
module NIO
|
4
6
|
# Selectors monitor IO objects for events of interest
|
5
7
|
class Selector
|
8
|
+
# Return supported backends as symbols
|
9
|
+
#
|
10
|
+
# See `#backend` method definition for all possible backends
|
11
|
+
def self.backends
|
12
|
+
[:ruby]
|
13
|
+
end
|
14
|
+
|
6
15
|
# Create a new NIO::Selector
|
7
|
-
def initialize
|
16
|
+
def initialize(backend = :ruby)
|
17
|
+
raise ArgumentError, "unsupported backend: #{backend}" unless backend == :ruby
|
18
|
+
|
8
19
|
@selectables = {}
|
9
20
|
@lock = Mutex.new
|
10
21
|
|
@@ -13,17 +24,35 @@ module NIO
|
|
13
24
|
@closed = false
|
14
25
|
end
|
15
26
|
|
27
|
+
# Return a symbol representing the backend I/O multiplexing mechanism used.
|
28
|
+
# Supported backends are:
|
29
|
+
# * :ruby - pure Ruby (i.e IO.select)
|
30
|
+
# * :java - Java NIO on JRuby
|
31
|
+
# * :epoll - libev w\ Linux epoll
|
32
|
+
# * :poll - libev w\ POSIX poll
|
33
|
+
# * :kqueue - libev w\ BSD kqueue
|
34
|
+
# * :select - libev w\ SysV select
|
35
|
+
# * :port - libev w\ I/O completion ports
|
36
|
+
# * :unknown - libev w\ unknown backend
|
37
|
+
def backend
|
38
|
+
:ruby
|
39
|
+
end
|
40
|
+
|
16
41
|
# Register interest in an IO object with the selector for the given types
|
17
42
|
# of events. Valid event types for interest are:
|
18
43
|
# * :r - is the IO readable?
|
19
44
|
# * :w - is the IO writeable?
|
20
45
|
# * :rw - is the IO either readable or writeable?
|
21
46
|
def register(io, interest)
|
47
|
+
unless defined?(::OpenSSL) && io.is_a?(::OpenSSL::SSL::SSLSocket)
|
48
|
+
io = IO.try_convert(io)
|
49
|
+
end
|
50
|
+
|
22
51
|
@lock.synchronize do
|
23
|
-
|
52
|
+
raise IOError, "selector is closed" if closed?
|
24
53
|
|
25
54
|
monitor = @selectables[io]
|
26
|
-
|
55
|
+
raise ArgumentError, "already registered as #{monitor.interests.inspect}" if monitor
|
27
56
|
|
28
57
|
monitor = Monitor.new(io, interest, self)
|
29
58
|
@selectables[monitor.io] = monitor
|
@@ -35,7 +64,7 @@ module NIO
|
|
35
64
|
# Deregister the given IO object from the selector
|
36
65
|
def deregister(io)
|
37
66
|
@lock.synchronize do
|
38
|
-
monitor = @selectables.delete io
|
67
|
+
monitor = @selectables.delete IO.try_convert(io)
|
39
68
|
monitor.close(false) if monitor && !monitor.closed?
|
40
69
|
monitor
|
41
70
|
end
|
@@ -48,6 +77,8 @@ module NIO
|
|
48
77
|
|
49
78
|
# Select which monitors are ready
|
50
79
|
def select(timeout = nil)
|
80
|
+
selected_monitors = Set.new
|
81
|
+
|
51
82
|
@lock.synchronize do
|
52
83
|
readers = [@wakeup]
|
53
84
|
writers = []
|
@@ -58,17 +89,14 @@ module NIO
|
|
58
89
|
monitor.readiness = nil
|
59
90
|
end
|
60
91
|
|
61
|
-
ready_readers, ready_writers = Kernel.select
|
62
|
-
return unless ready_readers # timeout
|
63
|
-
|
64
|
-
selected_monitors = Set.new
|
92
|
+
ready_readers, ready_writers = Kernel.select(readers, writers, [], timeout)
|
93
|
+
return unless ready_readers # timeout
|
65
94
|
|
66
95
|
ready_readers.each do |io|
|
67
96
|
if io == @wakeup
|
68
97
|
# Clear all wakeup signals we've received by reading them
|
69
98
|
# Wakeups should have level triggered behavior
|
70
99
|
@wakeup.read(@wakeup.stat.size)
|
71
|
-
return
|
72
100
|
else
|
73
101
|
monitor = @selectables[io]
|
74
102
|
monitor.readiness = :r
|
@@ -78,18 +106,16 @@ module NIO
|
|
78
106
|
|
79
107
|
ready_writers.each do |io|
|
80
108
|
monitor = @selectables[io]
|
81
|
-
monitor.readiness =
|
109
|
+
monitor.readiness = monitor.readiness == :r ? :rw : :w
|
82
110
|
selected_monitors << monitor
|
83
111
|
end
|
112
|
+
end
|
84
113
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
else
|
91
|
-
selected_monitors
|
92
|
-
end
|
114
|
+
if block_given?
|
115
|
+
selected_monitors.each { |m| yield m }
|
116
|
+
selected_monitors.size
|
117
|
+
else
|
118
|
+
selected_monitors.to_a
|
93
119
|
end
|
94
120
|
end
|
95
121
|
|
@@ -101,7 +127,16 @@ module NIO
|
|
101
127
|
# level-triggered behavior.
|
102
128
|
def wakeup
|
103
129
|
# Send the selector a signal in the form of writing data to a pipe
|
104
|
-
|
130
|
+
begin
|
131
|
+
@waker.write_nonblock "\0"
|
132
|
+
rescue IO::WaitWritable
|
133
|
+
# This indicates the wakeup pipe is full, which means the other thread
|
134
|
+
# has already received many wakeup calls, but not processed them yet.
|
135
|
+
# The other thread will completely drain this pipe when it wakes up,
|
136
|
+
# so it's ok to ignore this exception if it occurs: we know the other
|
137
|
+
# thread has already been signaled to wake up
|
138
|
+
end
|
139
|
+
|
105
140
|
nil
|
106
141
|
end
|
107
142
|
|
@@ -110,8 +145,16 @@ module NIO
|
|
110
145
|
@lock.synchronize do
|
111
146
|
return if @closed
|
112
147
|
|
113
|
-
|
114
|
-
|
148
|
+
begin
|
149
|
+
@wakeup.close
|
150
|
+
rescue IOError
|
151
|
+
end
|
152
|
+
|
153
|
+
begin
|
154
|
+
@waker.close
|
155
|
+
rescue IOError
|
156
|
+
end
|
157
|
+
|
115
158
|
@closed = true
|
116
159
|
end
|
117
160
|
end
|