nio4r 2.0.0.pre-java → 2.1.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 +9 -19
  6. data/CHANGES.md +94 -42
  7. data/Gemfile +11 -3
  8. data/Guardfile +10 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +43 -136
  11. data/Rakefile +2 -0
  12. data/examples/echo_server.rb +1 -0
  13. data/ext/libev/Changes +9 -13
  14. data/ext/libev/ev.c +100 -74
  15. data/ext/libev/ev.h +4 -9
  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 +265 -257
  22. data/ext/nio4r/extconf.rb +3 -9
  23. data/ext/nio4r/monitor.c +93 -46
  24. data/ext/nio4r/nio4r.h +6 -16
  25. data/ext/nio4r/org/nio4r/ByteBuffer.java +193 -209
  26. data/ext/nio4r/org/nio4r/Monitor.java +164 -0
  27. data/ext/nio4r/org/nio4r/Nio4r.java +13 -391
  28. data/ext/nio4r/org/nio4r/Selector.java +278 -0
  29. data/ext/nio4r/selector.c +72 -64
  30. data/lib/nio.rb +3 -3
  31. data/lib/nio/bytebuffer.rb +179 -132
  32. data/lib/nio/monitor.rb +64 -4
  33. data/lib/nio/selector.rb +36 -13
  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 +323 -51
  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 +18 -15
  50. data/.rubocop_todo.yml +0 -35
data/lib/nio.rb CHANGED
@@ -19,7 +19,7 @@ if ENV["NIO4R_PURE"] == "true" || (Gem.win_platform? && !defined?(JRUBY_VERSION)
19
19
  require "nio/monitor"
20
20
  require "nio/selector"
21
21
  require "nio/bytebuffer"
22
- NIO::ENGINE = "ruby".freeze
22
+ NIO::ENGINE = "ruby"
23
23
  else
24
24
  require "nio4r_ext"
25
25
 
@@ -27,8 +27,8 @@ else
27
27
  require "java"
28
28
  require "jruby"
29
29
  org.nio4r.Nio4r.new.load(JRuby.runtime, false)
30
- NIO::ENGINE = "java".freeze
30
+ NIO::ENGINE = "java"
31
31
  else
32
- NIO::ENGINE = "libev".freeze
32
+ NIO::ENGINE = "libev"
33
33
  end
34
34
  end
@@ -1,175 +1,222 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NIO
2
- # rubocop:disable ClassLength
4
+ # Efficient byte buffers for performant I/O operations
3
5
  class ByteBuffer
4
- def initialize(value, offset = nil, length = nil)
5
- # value can be either STRING or INTEGER
6
- fail "not a valid input" if value.nil?
7
- @position = 0
8
- @mark = -1
9
- if value.is_a? Integer
10
- @size = value
11
- @byte_array = Array.new(value)
12
- elsif value.is_a? String
13
- @byte_array = str.bytes
14
- @size = @byte_array.size
15
- end
16
- @limit = @size - 1
17
- unless offset.nil?
18
- @offset = offset
19
- @position = offset
20
- unless length.nil?
21
- fail "Invalid Arguiments Exception" if offset + length >= value
22
- @limit = offset + length
23
- end
24
- end
25
- end
26
-
27
- # put the provided string to the buffer
28
- def <<(str)
29
- temp_buffer = str.bytes
30
- temp_buffer.each { |x| put_byte x }
31
- end
6
+ include Enumerable
32
7
 
33
- # return the remaining number positions to read/ write
34
- def remaining
35
- @limit + 1 - @position
36
- end
8
+ attr_reader :position, :limit, :capacity
37
9
 
38
- # has any space remaining
39
- def remaining?
40
- remaining > 0
41
- end
10
+ # Insufficient capacity in buffer
11
+ OverflowError = Class.new(IOError)
12
+
13
+ # Not enough data remaining in buffer
14
+ UnderflowError = Class.new(IOError)
42
15
 
43
- # this method is private
44
- def put_byte(byte)
45
- fail "Buffer Overflowed" if @position == @size
46
- @byte_array[@position] = byte
47
- @position += 1
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
48
29
  end
49
30
 
50
- # write content in the buffer to file
51
- # call flip before calling this
52
- # after write operation to the
53
- # buffer
54
- def write_to(file)
55
- @file_to_write = file unless @file_to_write.eql? file
56
- file.write get if remaining?
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
57
39
  end
58
40
 
59
- # Fill the byteBuffer with content of the file
60
- def read_from(file)
61
- @file_to_read = file unless @file_to_read.eql? file
62
- while (s = file.read(1)) && remaining?
63
- put_byte(s)
64
- end
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
65
55
  end
66
56
 
67
- # flip from write to read mode
68
- def flip
69
- # need to avoid @position being negative
70
- @limit = [@position - 1, 0].max
71
- @position = 0
72
- @mark = -1
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
73
  end
74
74
 
75
- # rewind read mode to write mode. limit stays unchanged
76
- def rewind
77
- @position = 0
78
- @mark = -1
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
79
80
  end
80
81
 
81
- # reset the position to the previously marked position
82
- def reset
83
- fail "Invalid Mark Exception" if @mark < 0
84
- @position = @mark
85
- self
82
+ # Does the ByteBuffer have any space remaining?
83
+ #
84
+ # @return [true, false]
85
+ def full?
86
+ remaining.zero?
86
87
  end
87
88
 
88
- # mark the current position in order to reset later
89
- def mark
90
- @mark = @position
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
91
102
  end
92
103
 
93
- # the current values are considered junk
94
- def clear
95
- @position = 0
96
- @limit = @size - 1
97
- @mark = -1
98
- self
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]
99
114
  end
100
115
 
101
- def compact
102
- # compact should be allowed only if there are content remaining in the buffer
103
- return self unless remaining?
104
- temp = @byte_array.slice(@position, @limit)
105
- # if 1 remaining the replaced range should be @byte_array[0..0]
106
- @byte_array[0..remaining - 1] = temp
107
- @position = remaining
108
- @limit = @size - 1
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
109
125
  self
110
126
  end
111
127
 
112
- # get the content of the byteBuffer. need to call rewind before calling get.
113
- # return as a String
114
- def get
115
- return "" if @limit == 0
116
- temp = @byte_array[@position..@limit].pack("c*")
117
- # next position to be read. it should be always less than or equal to size-1
118
- @position = [@limit + 1, @size].min
119
- temp
120
- end
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
121
140
 
122
- def read_next(count)
123
- fail "Illegal Argument" unless count > 0
124
- fail "Less number of elements remaining" if count > remaining
125
- temp = @byte_array[@position..@position + count - 1].pack("c*")
126
- @position += count
127
- temp
141
+ self << bytes_read
142
+ bytes_read.length
128
143
  end
129
144
 
130
- # return the offset of the buffer
131
- def offset?
132
- @offset
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
133
160
  end
134
161
 
135
- # check whether the obj is the same bytebuffer as this bytebuffer
136
- def equals?(obj)
137
- self == obj
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
138
168
  end
139
169
 
140
- # returns the capacity of the buffer. This value is fixed to the initial size
141
- def capacity
142
- @size
170
+ # Set the buffer's current position to 0, leaving the limit unchanged
171
+ def rewind
172
+ @position = 0
173
+ @mark = nil
174
+ self
143
175
  end
144
176
 
145
- # Set the position to a different position
146
- def position(new_position)
147
- fail "Illegal Argument Exception" unless new_position <= @limit && new_position >= 0
148
- @position = new_position
149
- @mark = -1 if @mark > @position
177
+ # Mark a position to return to using the `#reset` method
178
+ def mark
179
+ @mark = @position
180
+ self
150
181
  end
151
182
 
152
- def limit(new_limit)
153
- fail "Illegal Argument Exception" if new_limit > @size || new_limit < 0
154
- @limit = new_limit
155
- @position = @limit if @position > @limit
156
- @mark = -1 if @mark > @limit
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
157
190
  end
158
191
 
159
- def limit?
160
- @limit
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
161
199
  end
162
200
 
163
- def to_s
164
- # convert String in byte form to the visible string
165
- temp = "ByteBuffer "
166
- temp += "[pos=" + @position.to_s
167
- temp += " lim =" + @limit.to_s
168
- temp += " cap=" + @size.to_s
169
- temp += "]"
170
- temp
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)
171
206
  end
172
207
 
173
- private :put_byte
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
174
221
  end
175
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