nio4r 1.2.1-java → 2.0.0-java

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