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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +31 -38
- data/.ruby-version +1 -0
- data/.travis.yml +9 -19
- data/CHANGES.md +94 -42
- data/Gemfile +11 -3
- data/Guardfile +10 -0
- data/LICENSE.txt +1 -1
- data/README.md +43 -136
- data/Rakefile +2 -0
- data/examples/echo_server.rb +1 -0
- data/ext/libev/Changes +9 -13
- data/ext/libev/ev.c +100 -74
- data/ext/libev/ev.h +4 -9
- data/ext/libev/ev_epoll.c +6 -3
- data/ext/libev/ev_kqueue.c +8 -4
- data/ext/libev/ev_poll.c +6 -3
- data/ext/libev/ev_port.c +8 -4
- data/ext/libev/ev_select.c +4 -2
- data/ext/nio4r/bytebuffer.c +265 -257
- data/ext/nio4r/extconf.rb +3 -9
- data/ext/nio4r/monitor.c +93 -46
- data/ext/nio4r/nio4r.h +6 -16
- data/ext/nio4r/org/nio4r/ByteBuffer.java +193 -209
- data/ext/nio4r/org/nio4r/Monitor.java +164 -0
- data/ext/nio4r/org/nio4r/Nio4r.java +13 -391
- data/ext/nio4r/org/nio4r/Selector.java +278 -0
- data/ext/nio4r/selector.c +72 -64
- data/lib/nio.rb +3 -3
- data/lib/nio/bytebuffer.rb +179 -132
- data/lib/nio/monitor.rb +64 -4
- data/lib/nio/selector.rb +36 -13
- data/lib/nio/version.rb +1 -1
- data/nio4r.gemspec +25 -19
- data/spec/nio/acceptables_spec.rb +6 -4
- data/spec/nio/bytebuffer_spec.rb +323 -51
- data/spec/nio/monitor_spec.rb +122 -79
- data/spec/nio/selectables/pipe_spec.rb +5 -1
- data/spec/nio/selectables/ssl_socket_spec.rb +15 -12
- data/spec/nio/selectables/tcp_socket_spec.rb +42 -31
- data/spec/nio/selectables/udp_socket_spec.rb +2 -0
- data/spec/nio/selector_spec.rb +10 -4
- data/spec/spec_helper.rb +24 -3
- data/spec/support/selectable_examples.rb +7 -5
- data/tasks/extension.rake +2 -0
- data/tasks/rspec.rake +2 -0
- data/tasks/rubocop.rake +2 -0
- metadata +18 -15
- 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"
|
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"
|
30
|
+
NIO::ENGINE = "java"
|
31
31
|
else
|
32
|
-
NIO::ENGINE = "libev"
|
32
|
+
NIO::ENGINE = "libev"
|
33
33
|
end
|
34
34
|
end
|
data/lib/nio/bytebuffer.rb
CHANGED
@@ -1,175 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module NIO
|
2
|
-
#
|
4
|
+
# Efficient byte buffers for performant I/O operations
|
3
5
|
class ByteBuffer
|
4
|
-
|
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
|
-
|
34
|
-
def remaining
|
35
|
-
@limit + 1 - @position
|
36
|
-
end
|
8
|
+
attr_reader :position, :limit, :capacity
|
37
9
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@
|
56
|
-
|
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
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
#
|
89
|
-
|
90
|
-
|
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
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
@
|
108
|
-
@
|
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
|
-
#
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
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
|
-
#
|
131
|
-
|
132
|
-
|
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
|
-
#
|
136
|
-
def
|
137
|
-
|
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
|
-
#
|
141
|
-
def
|
142
|
-
@
|
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
|
-
#
|
146
|
-
def
|
147
|
-
|
148
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
160
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
28
|
-
|
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
|