nio4r 2.0.0.pre → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +31 -38
- data/.ruby-version +1 -0
- data/.travis.yml +4 -21
- data/CHANGES.md +75 -42
- data/Gemfile +11 -3
- data/Guardfile +10 -0
- data/LICENSE.txt +1 -1
- data/README.md +32 -136
- data/Rakefile +2 -0
- data/examples/echo_server.rb +1 -0
- data/ext/libev/Changes +4 -13
- data/ext/libev/ev.c +100 -74
- data/ext/libev/ev.h +3 -3
- 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 +2 -10
- data/ext/nio4r/monitor.c +93 -46
- data/ext/nio4r/nio4r.h +5 -15
- 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 +52 -52
- 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 +15 -11
- 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
|