nio4r 1.2.1 → 2.0.0.pre
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/.travis.yml +27 -9
- data/Gemfile +4 -7
- data/README.md +43 -43
- data/Rakefile +0 -1
- data/examples/echo_server.rb +0 -0
- data/ext/libev/ev.c +1 -0
- data/ext/nio4r/bytebuffer.c +413 -0
- data/ext/nio4r/nio4r.h +8 -0
- data/ext/nio4r/nio4r_ext.c +2 -0
- data/ext/nio4r/org/nio4r/ByteBuffer.java +311 -0
- data/ext/nio4r/org/nio4r/Nio4r.java +9 -0
- data/ext/nio4r/selector.c +3 -1
- data/lib/nio/bytebuffer.rb +175 -0
- data/lib/nio/selector.rb +19 -10
- data/lib/nio/version.rb +1 -1
- data/lib/nio.rb +1 -0
- data/spec/nio/bytebuffer_spec.rb +77 -0
- metadata +9 -4
@@ -0,0 +1,311 @@
|
|
1
|
+
package org.nio4r;
|
2
|
+
|
3
|
+
import org.jruby.*;
|
4
|
+
import org.jruby.anno.JRubyMethod;
|
5
|
+
import org.jruby.javasupport.JavaUtil;
|
6
|
+
import org.jruby.runtime.ThreadContext;
|
7
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
8
|
+
|
9
|
+
import java.io.File;
|
10
|
+
import java.io.FileInputStream;
|
11
|
+
import java.io.FileOutputStream;
|
12
|
+
import java.nio.channels.FileChannel;
|
13
|
+
import java.util.ArrayList;
|
14
|
+
|
15
|
+
/*
|
16
|
+
created by Upekshej
|
17
|
+
*/
|
18
|
+
public class ByteBuffer extends RubyObject {
|
19
|
+
|
20
|
+
private java.nio.ByteBuffer byteBuffer;
|
21
|
+
private String currentWritePath = "";
|
22
|
+
private String currentReadPath = "";
|
23
|
+
|
24
|
+
private FileChannel currentWriteFileChannel;
|
25
|
+
private FileOutputStream fileOutputStream;
|
26
|
+
|
27
|
+
private FileInputStream currentReadChannel;
|
28
|
+
private FileChannel inChannel;
|
29
|
+
|
30
|
+
public ByteBuffer(final Ruby ruby, RubyClass rubyClass) {
|
31
|
+
super(ruby, rubyClass);
|
32
|
+
}
|
33
|
+
|
34
|
+
@JRubyMethod
|
35
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject value, IRubyObject offset, IRubyObject length) {
|
36
|
+
Ruby ruby = context.getRuntime();
|
37
|
+
|
38
|
+
if (value == ruby.getNil()) {
|
39
|
+
throw ruby.newTypeError("expected String or Integer for value, got NilClass");
|
40
|
+
}
|
41
|
+
|
42
|
+
if (value instanceof RubyString) {
|
43
|
+
if (offset != ruby.getNil() && length != ruby.getNil()) {
|
44
|
+
int arrayOffset = RubyNumeric.num2int(offset);
|
45
|
+
int arrayLimit = RubyNumeric.num2int(length);
|
46
|
+
byteBuffer = java.nio.ByteBuffer.wrap(value.asJavaString().getBytes(), arrayOffset, arrayLimit);
|
47
|
+
} else {
|
48
|
+
byteBuffer = java.nio.ByteBuffer.wrap(value.asJavaString().getBytes());
|
49
|
+
}
|
50
|
+
} else if (value instanceof RubyInteger) {
|
51
|
+
int allocationSize = RubyNumeric.num2int(value);
|
52
|
+
byteBuffer = java.nio.ByteBuffer.allocate(allocationSize);
|
53
|
+
} else {
|
54
|
+
throw ruby.newTypeError("expected String or Integer for value");
|
55
|
+
}
|
56
|
+
|
57
|
+
return this;
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Currently assuming only strings will come..
|
62
|
+
*
|
63
|
+
* @param context
|
64
|
+
* @return
|
65
|
+
*/
|
66
|
+
@JRubyMethod(name = "<<")
|
67
|
+
public IRubyObject put(ThreadContext context, IRubyObject str) {
|
68
|
+
String string = str.asJavaString();
|
69
|
+
|
70
|
+
if (byteBuffer == null) {
|
71
|
+
byteBuffer = java.nio.ByteBuffer.wrap(string.getBytes());
|
72
|
+
}
|
73
|
+
|
74
|
+
byteBuffer.put(string.getBytes());
|
75
|
+
return this;
|
76
|
+
}
|
77
|
+
|
78
|
+
//https://www.ruby-forum.com/topic/3731325
|
79
|
+
@JRubyMethod(name = "get")
|
80
|
+
public IRubyObject get(ThreadContext context) {
|
81
|
+
ArrayList<Byte> temp = new ArrayList<Byte>();
|
82
|
+
|
83
|
+
while (byteBuffer.hasRemaining()) {
|
84
|
+
temp.add(byteBuffer.get());
|
85
|
+
}
|
86
|
+
|
87
|
+
return JavaUtil.convertJavaToRuby(context.getRuntime(), new String(toPrimitives(temp)));
|
88
|
+
}
|
89
|
+
|
90
|
+
@JRubyMethod(name = "read_next")
|
91
|
+
public IRubyObject readNext(ThreadContext context, IRubyObject count) {
|
92
|
+
int c = RubyNumeric.num2int(count);
|
93
|
+
|
94
|
+
if (c < 1) {
|
95
|
+
throw new IllegalArgumentException();
|
96
|
+
}
|
97
|
+
|
98
|
+
if (c <= byteBuffer.remaining()) {
|
99
|
+
org.jruby.util.ByteList temp = new org.jruby.util.ByteList(c);
|
100
|
+
|
101
|
+
while (c > 0) {
|
102
|
+
temp.append(byteBuffer.get());
|
103
|
+
c = c - 1;
|
104
|
+
}
|
105
|
+
|
106
|
+
return context.runtime.newString(temp);
|
107
|
+
}
|
108
|
+
|
109
|
+
return RubyString.newEmptyString(context.runtime);
|
110
|
+
}
|
111
|
+
|
112
|
+
private byte[] toPrimitives(ArrayList<Byte> oBytes) {
|
113
|
+
byte[] bytes = new byte[oBytes.size()];
|
114
|
+
|
115
|
+
for (int i = 0; i < oBytes.size(); i++) {
|
116
|
+
bytes[i] = (oBytes.get(i) == null) ? " ".getBytes()[0] : oBytes.get(i);
|
117
|
+
}
|
118
|
+
|
119
|
+
return bytes;
|
120
|
+
}
|
121
|
+
|
122
|
+
@JRubyMethod(name = "write_to")
|
123
|
+
public IRubyObject writeTo(ThreadContext context, IRubyObject f) {
|
124
|
+
try {
|
125
|
+
File file = (File) JavaUtil.unwrapJavaObject(f);
|
126
|
+
|
127
|
+
if (!isTheSameFile(file, false)) {
|
128
|
+
currentWritePath = file.getAbsolutePath();
|
129
|
+
if (currentWriteFileChannel != null) currentWriteFileChannel.close();
|
130
|
+
if (fileOutputStream != null) fileOutputStream.close();
|
131
|
+
|
132
|
+
fileOutputStream = new FileOutputStream(file, true);
|
133
|
+
currentWriteFileChannel = fileOutputStream.getChannel();
|
134
|
+
}
|
135
|
+
|
136
|
+
currentWriteFileChannel.write(byteBuffer);
|
137
|
+
} catch (Exception e) {
|
138
|
+
throw new IllegalArgumentException("write error: " + e.getLocalizedMessage());
|
139
|
+
}
|
140
|
+
|
141
|
+
return this;
|
142
|
+
}
|
143
|
+
|
144
|
+
@JRubyMethod(name = "read_from")
|
145
|
+
public IRubyObject readFrom(ThreadContext context, IRubyObject f) {
|
146
|
+
try {
|
147
|
+
File file = (File) JavaUtil.unwrapJavaObject(f);
|
148
|
+
|
149
|
+
if (!isTheSameFile(file, true)) {
|
150
|
+
inChannel.close();
|
151
|
+
currentReadChannel.close();
|
152
|
+
currentReadPath = file.getAbsolutePath();
|
153
|
+
currentReadChannel = new FileInputStream(file);
|
154
|
+
inChannel = currentReadChannel.getChannel();
|
155
|
+
}
|
156
|
+
|
157
|
+
inChannel.read(byteBuffer);
|
158
|
+
} catch (Exception e) {
|
159
|
+
throw new IllegalArgumentException("read error: " + e.getLocalizedMessage());
|
160
|
+
}
|
161
|
+
|
162
|
+
return this;
|
163
|
+
}
|
164
|
+
|
165
|
+
private boolean isTheSameFile(File f, boolean read) {
|
166
|
+
if (read) {
|
167
|
+
return (currentReadPath == f.getAbsolutePath());
|
168
|
+
}
|
169
|
+
|
170
|
+
return currentWritePath == f.getAbsolutePath();
|
171
|
+
}
|
172
|
+
|
173
|
+
@JRubyMethod(name = "remaining")
|
174
|
+
public IRubyObject remainingPositions(ThreadContext context) {
|
175
|
+
int count = byteBuffer.remaining();
|
176
|
+
return context.getRuntime().newFixnum(count);
|
177
|
+
}
|
178
|
+
|
179
|
+
@JRubyMethod(name = "remaining?")
|
180
|
+
public IRubyObject hasRemaining(ThreadContext context) {
|
181
|
+
if (byteBuffer.hasRemaining()) {
|
182
|
+
return context.getRuntime().getTrue();
|
183
|
+
}
|
184
|
+
|
185
|
+
return context.getRuntime().getFalse();
|
186
|
+
}
|
187
|
+
|
188
|
+
@JRubyMethod(name = "offset?")
|
189
|
+
public IRubyObject getOffset(ThreadContext context) {
|
190
|
+
int offset = byteBuffer.arrayOffset();
|
191
|
+
return context.getRuntime().newFixnum(offset);
|
192
|
+
}
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Check whether the two ByteBuffers are the same.
|
196
|
+
*
|
197
|
+
* @param context
|
198
|
+
* @param ob : The RubyObject which needs to be check
|
199
|
+
* @return
|
200
|
+
*/
|
201
|
+
@JRubyMethod(name = "equals?")
|
202
|
+
public IRubyObject equals(ThreadContext context, IRubyObject obj) {
|
203
|
+
Object o = JavaUtil.convertRubyToJava(obj);
|
204
|
+
|
205
|
+
if(!(o instanceof ByteBuffer)) {
|
206
|
+
return context.getRuntime().getFalse();
|
207
|
+
}
|
208
|
+
|
209
|
+
if(this.byteBuffer.equals(((ByteBuffer)o).getBuffer())) {
|
210
|
+
return context.getRuntime().getTrue();
|
211
|
+
} else {
|
212
|
+
return context.getRuntime().getFalse();
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Flip capability provided by the java nio.ByteBuffer
|
218
|
+
* buf.put(magic); // Prepend header
|
219
|
+
* in.read(buf); // Read data into rest of buffer
|
220
|
+
* buf.flip(); // Flip buffer
|
221
|
+
* out.write(buf); // Write header + data to channel
|
222
|
+
*
|
223
|
+
* @param context
|
224
|
+
* @return
|
225
|
+
*/
|
226
|
+
@JRubyMethod
|
227
|
+
public IRubyObject flip(ThreadContext context) {
|
228
|
+
byteBuffer.flip();
|
229
|
+
return this;
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* Rewinds the buffer. Usage in java is like
|
234
|
+
* out.write(buf); // Write remaining data
|
235
|
+
* buf.rewind(); // Rewind buffer
|
236
|
+
* buf.get(array); // Copy data into array
|
237
|
+
*
|
238
|
+
* @param context
|
239
|
+
* @return
|
240
|
+
*/
|
241
|
+
@JRubyMethod
|
242
|
+
public IRubyObject rewind(ThreadContext context) {
|
243
|
+
byteBuffer.rewind();
|
244
|
+
return this;
|
245
|
+
}
|
246
|
+
|
247
|
+
@JRubyMethod
|
248
|
+
public IRubyObject reset(ThreadContext context) {
|
249
|
+
byteBuffer.reset();
|
250
|
+
return this;
|
251
|
+
}
|
252
|
+
|
253
|
+
@JRubyMethod
|
254
|
+
public IRubyObject mark(ThreadContext context) {
|
255
|
+
byteBuffer.mark();
|
256
|
+
return this;
|
257
|
+
}
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Removes all the content in the byteBuffer
|
261
|
+
*
|
262
|
+
* @param context
|
263
|
+
* @return
|
264
|
+
*/
|
265
|
+
@JRubyMethod
|
266
|
+
public IRubyObject clear(ThreadContext context) {
|
267
|
+
byteBuffer.clear();
|
268
|
+
return this;
|
269
|
+
}
|
270
|
+
|
271
|
+
@JRubyMethod
|
272
|
+
public IRubyObject compact(ThreadContext context) {
|
273
|
+
byteBuffer.compact();
|
274
|
+
return this;
|
275
|
+
}
|
276
|
+
|
277
|
+
@JRubyMethod(name = "capacity")
|
278
|
+
public IRubyObject capacity(ThreadContext context) {
|
279
|
+
int cap = byteBuffer.capacity();
|
280
|
+
return context.getRuntime().newFixnum(cap);
|
281
|
+
}
|
282
|
+
|
283
|
+
@JRubyMethod
|
284
|
+
public IRubyObject position(ThreadContext context, IRubyObject newPosition) {
|
285
|
+
int position = RubyNumeric.num2int(newPosition);
|
286
|
+
byteBuffer.position(position);
|
287
|
+
return this;
|
288
|
+
}
|
289
|
+
|
290
|
+
@JRubyMethod(name = "limit")
|
291
|
+
public IRubyObject limit(ThreadContext context, IRubyObject newLimit) {
|
292
|
+
int limit = RubyNumeric.num2int(newLimit);
|
293
|
+
byteBuffer.limit(limit);
|
294
|
+
return this;
|
295
|
+
}
|
296
|
+
|
297
|
+
@JRubyMethod(name = "limit?")
|
298
|
+
public IRubyObject limit(ThreadContext context) {
|
299
|
+
int lmt = byteBuffer.limit();
|
300
|
+
return context.getRuntime().newFixnum(lmt);
|
301
|
+
}
|
302
|
+
|
303
|
+
@JRubyMethod(name = "to_s")
|
304
|
+
public IRubyObject to_String(ThreadContext context) {
|
305
|
+
return JavaUtil.convertJavaToRuby(context.getRuntime(), byteBuffer.toString());
|
306
|
+
}
|
307
|
+
|
308
|
+
public java.nio.ByteBuffer getBuffer() {
|
309
|
+
return byteBuffer;
|
310
|
+
}
|
311
|
+
}
|
@@ -21,6 +21,7 @@ import org.jruby.runtime.ThreadContext;
|
|
21
21
|
import org.jruby.runtime.load.Library;
|
22
22
|
import org.jruby.runtime.builtin.IRubyObject;
|
23
23
|
import org.jruby.runtime.Block;
|
24
|
+
import org.nio4r.ByteBuffer;
|
24
25
|
|
25
26
|
public class Nio4r implements Library {
|
26
27
|
private Ruby ruby;
|
@@ -45,6 +46,14 @@ public class Nio4r implements Library {
|
|
45
46
|
}, nio);
|
46
47
|
|
47
48
|
monitor.defineAnnotatedMethods(Monitor.class);
|
49
|
+
|
50
|
+
RubyClass byteBuffer = ruby.defineClassUnder("ByteBuffer", ruby.getObject(), new ObjectAllocator() {
|
51
|
+
public IRubyObject allocate(Ruby ruby, RubyClass rc) {
|
52
|
+
return new ByteBuffer(ruby, rc);
|
53
|
+
}
|
54
|
+
}, nio);
|
55
|
+
|
56
|
+
byteBuffer.defineAnnotatedMethods(ByteBuffer.class);
|
48
57
|
}
|
49
58
|
|
50
59
|
public static int symbolToInterestOps(Ruby ruby, SelectableChannel channel, IRubyObject interest) {
|
data/ext/nio4r/selector.c
CHANGED
@@ -93,7 +93,9 @@ static VALUE NIO_Selector_allocate(VALUE klass)
|
|
93
93
|
rb_sys_fail("pipe");
|
94
94
|
}
|
95
95
|
|
96
|
-
|
96
|
+
/* Use non-blocking reads/writes during wakeup, in case the buffer is full */
|
97
|
+
if(fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0 ||
|
98
|
+
fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0) {
|
97
99
|
rb_sys_fail("fcntl");
|
98
100
|
}
|
99
101
|
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module NIO
|
2
|
+
# rubocop:disable ClassLength
|
3
|
+
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
|
32
|
+
|
33
|
+
# return the remaining number positions to read/ write
|
34
|
+
def remaining
|
35
|
+
@limit + 1 - @position
|
36
|
+
end
|
37
|
+
|
38
|
+
# has any space remaining
|
39
|
+
def remaining?
|
40
|
+
remaining > 0
|
41
|
+
end
|
42
|
+
|
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
|
48
|
+
end
|
49
|
+
|
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?
|
57
|
+
end
|
58
|
+
|
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
|
65
|
+
end
|
66
|
+
|
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
|
73
|
+
end
|
74
|
+
|
75
|
+
# rewind read mode to write mode. limit stays unchanged
|
76
|
+
def rewind
|
77
|
+
@position = 0
|
78
|
+
@mark = -1
|
79
|
+
end
|
80
|
+
|
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
|
86
|
+
end
|
87
|
+
|
88
|
+
# mark the current position in order to reset later
|
89
|
+
def mark
|
90
|
+
@mark = @position
|
91
|
+
end
|
92
|
+
|
93
|
+
# the current values are considered junk
|
94
|
+
def clear
|
95
|
+
@position = 0
|
96
|
+
@limit = @size - 1
|
97
|
+
@mark = -1
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
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
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
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
|
121
|
+
|
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
|
128
|
+
end
|
129
|
+
|
130
|
+
# return the offset of the buffer
|
131
|
+
def offset?
|
132
|
+
@offset
|
133
|
+
end
|
134
|
+
|
135
|
+
# check whether the obj is the same bytebuffer as this bytebuffer
|
136
|
+
def equals?(obj)
|
137
|
+
self == obj
|
138
|
+
end
|
139
|
+
|
140
|
+
# returns the capacity of the buffer. This value is fixed to the initial size
|
141
|
+
def capacity
|
142
|
+
@size
|
143
|
+
end
|
144
|
+
|
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
|
150
|
+
end
|
151
|
+
|
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
|
157
|
+
end
|
158
|
+
|
159
|
+
def limit?
|
160
|
+
@limit
|
161
|
+
end
|
162
|
+
|
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
|
171
|
+
end
|
172
|
+
|
173
|
+
private :put_byte
|
174
|
+
end
|
175
|
+
end
|
data/lib/nio/selector.rb
CHANGED
@@ -48,6 +48,8 @@ module NIO
|
|
48
48
|
|
49
49
|
# Select which monitors are ready
|
50
50
|
def select(timeout = nil)
|
51
|
+
selected_monitors = Set.new
|
52
|
+
|
51
53
|
@lock.synchronize do
|
52
54
|
readers = [@wakeup]
|
53
55
|
writers = []
|
@@ -61,8 +63,6 @@ module NIO
|
|
61
63
|
ready_readers, ready_writers = Kernel.select readers, writers, [], timeout
|
62
64
|
return unless ready_readers # timeout or wakeup
|
63
65
|
|
64
|
-
selected_monitors = Set.new
|
65
|
-
|
66
66
|
ready_readers.each do |io|
|
67
67
|
if io == @wakeup
|
68
68
|
# Clear all wakeup signals we've received by reading them
|
@@ -81,15 +81,15 @@ module NIO
|
|
81
81
|
monitor.readiness = (monitor.readiness == :r) ? :rw : :w
|
82
82
|
selected_monitors << monitor
|
83
83
|
end
|
84
|
+
end
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
selected_monitors.size
|
90
|
-
else
|
91
|
-
selected_monitors
|
86
|
+
if block_given?
|
87
|
+
selected_monitors.each do |m|
|
88
|
+
yield m
|
92
89
|
end
|
90
|
+
selected_monitors.size
|
91
|
+
else
|
92
|
+
selected_monitors
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -101,7 +101,16 @@ module NIO
|
|
101
101
|
# level-triggered behavior.
|
102
102
|
def wakeup
|
103
103
|
# Send the selector a signal in the form of writing data to a pipe
|
104
|
-
|
104
|
+
begin
|
105
|
+
@waker.write_nonblock "\0"
|
106
|
+
rescue IO::WaitWritable
|
107
|
+
# This indicates the wakeup pipe is full, which means the other thread
|
108
|
+
# has already received many wakeup calls, but not processed them yet.
|
109
|
+
# The other thread will completely drain this pipe when it wakes up,
|
110
|
+
# so it's ok to ignore this exception if it occurs: we know the other
|
111
|
+
# thread has already been signaled to wake up
|
112
|
+
end
|
113
|
+
|
105
114
|
nil
|
106
115
|
end
|
107
116
|
|
data/lib/nio/version.rb
CHANGED
data/lib/nio.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe NIO::ByteBuffer do
|
4
|
+
describe "#Behaviour of ByteBuffer" do
|
5
|
+
subject { bytebuffer }
|
6
|
+
|
7
|
+
context "allocates a given size buffer" do
|
8
|
+
let(:bytebuffer) { NIO::ByteBuffer.new(256, nil, nil) }
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
bytebuffer.clear
|
12
|
+
end
|
13
|
+
|
14
|
+
it "Checks the allocation" do
|
15
|
+
expect(bytebuffer.capacity).to eql(256)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "checks remaining" do
|
19
|
+
expect(bytebuffer.remaining).to eql(256)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "puts a given string to buffer" do
|
23
|
+
bytebuffer << "Song of Ice & Fire"
|
24
|
+
expect(bytebuffer.remaining).to eql(238)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "reads the content added" do
|
28
|
+
bytebuffer << "Test"
|
29
|
+
bytebuffer << "Text"
|
30
|
+
bytebuffer << "Dumb"
|
31
|
+
bytebuffer.flip
|
32
|
+
expect(bytebuffer.read_next(5)).to eql "TestT"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "rewinds the buffer" do
|
36
|
+
end
|
37
|
+
|
38
|
+
it "compacts the buffer" do
|
39
|
+
bytebuffer << "Test"
|
40
|
+
bytebuffer << " Text"
|
41
|
+
bytebuffer << "Dumb"
|
42
|
+
bytebuffer.flip
|
43
|
+
bytebuffer.read_next 5
|
44
|
+
bytebuffer.compact
|
45
|
+
bytebuffer << " RRMARTIN"
|
46
|
+
bytebuffer.flip
|
47
|
+
# expect(bytebuffer.limit?).to eql(10)
|
48
|
+
expect(bytebuffer.get).to eql("TextDumb RRMARTIN")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "flips the bytebuffer" do
|
52
|
+
bytebuffer << "Test"
|
53
|
+
bytebuffer.flip
|
54
|
+
expect(bytebuffer.get).to eql("Test")
|
55
|
+
end
|
56
|
+
|
57
|
+
it "reads the next items" do
|
58
|
+
bytebuffer << "John Snow"
|
59
|
+
bytebuffer.flip
|
60
|
+
bytebuffer.read_next 5
|
61
|
+
expect(bytebuffer.read_next(4)).to eql("Snow")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "clears the buffer" do
|
65
|
+
bytebuffer << "Game of Thrones"
|
66
|
+
bytebuffer.clear
|
67
|
+
expect(bytebuffer.remaining).to eql(256)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "gets the content of the bytebuffer" do
|
71
|
+
bytebuffer << "Test"
|
72
|
+
bytebuffer.flip
|
73
|
+
expect(bytebuffer.get).to eql("Test")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|