ione 1.3.0.pre0 → 1.3.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/ione/byte_buffer.rb +31 -6
- data/lib/ione/future.rb +38 -26
- data/lib/ione/io/acceptor.rb +1 -0
- data/lib/ione/io/base_connection.rb +23 -10
- data/lib/ione/io/connection.rb +1 -0
- data/lib/ione/io/io_reactor.rb +27 -37
- data/lib/ione/io/ssl_connection.rb +1 -0
- data/lib/ione/version.rb +1 -1
- data/spec/integration/io_spec.rb +3 -3
- data/spec/integration/ssl_spec.rb +7 -2
- data/spec/ione/byte_buffer_spec.rb +91 -38
- data/spec/ione/future_spec.rb +64 -65
- data/spec/ione/heap_spec.rb +18 -18
- data/spec/ione/io/acceptor_spec.rb +5 -5
- data/spec/ione/io/connection_common.rb +2 -2
- data/spec/ione/io/io_reactor_spec.rb +43 -30
- data/spec/ione/io/ssl_acceptor_spec.rb +3 -3
- data/spec/ione/io/ssl_connection_spec.rb +3 -3
- data/spec/spec_helper.rb +4 -0
- data/spec/support/fake_server.rb +1 -0
- metadata +16 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 78ca22da674434afcf1293e1b7d70bb3f13a0080b7c492f5016fb6f4f2428702
|
4
|
+
data.tar.gz: a50ef526b8a0163a3c2dd116a75d83b48cb6bbb79f80a4cdb0ba9e66c5c2e4b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8eb9100fc86fb57bff7a048f2d97411764df9e00fd5ec5afa921470283161a282686b9fdeea6cccd750b3dce5ea7efce666c9f6b98ef65ec3df2a3076e39c73d
|
7
|
+
data.tar.gz: be2df17439ad63370e7b980ab99e526080e995d65a25c3547fdfbeca34db25548ff6ba9c7b665cb1f7e4d6603d3a33398a16f9628fc5e9ad53f57f3fe9a8c455
|
data/lib/ione/byte_buffer.rb
CHANGED
@@ -174,6 +174,18 @@ module Ione
|
|
174
174
|
b
|
175
175
|
end
|
176
176
|
|
177
|
+
# Return the nt:h byte of the buffer, without removing it, and decode it as a signed or unsigned integer.
|
178
|
+
#
|
179
|
+
# @param [Integer] index the zero-based positive position of the byte to read
|
180
|
+
# @return [Integer, nil] the integer interpretation of the byte at the specified position, or nil when beyond buffer size.
|
181
|
+
def getbyte(index, signed=false)
|
182
|
+
if @offset+index >= @read_buffer.bytesize
|
183
|
+
swap_buffers
|
184
|
+
end
|
185
|
+
b = @read_buffer.getbyte(@offset+index)
|
186
|
+
(signed && b >= 0x80) ? b - 0x100 : b
|
187
|
+
end
|
188
|
+
|
177
189
|
def index(substring, start_index=0)
|
178
190
|
if @offset >= @read_buffer.bytesize
|
179
191
|
swap_buffers
|
@@ -241,22 +253,35 @@ module Ione
|
|
241
253
|
# in situations where a loop wants to offer some bytes but can't be sure
|
242
254
|
# how many will be accepted — for example when writing to a socket.
|
243
255
|
#
|
256
|
+
# By providing the readonly argument, the peek is even cheaper in that it
|
257
|
+
# only considers the read buffer. In this mode, the method returns nil
|
258
|
+
# to signify that the read buffer is empty. With just a single reader,
|
259
|
+
# like the IO reactor, where reads only happen in one thread, this means
|
260
|
+
# that `cheap_peek(true)` can be called without holding a lock to protect
|
261
|
+
# against concurrent writes
|
262
|
+
#
|
244
263
|
# @example feeding bytes to a socket
|
245
264
|
# while true
|
246
265
|
# _, writables, _ = IO.select(nil, sockets)
|
247
266
|
# if writables
|
248
267
|
# writables.each do |io|
|
249
|
-
#
|
268
|
+
# bytes = buffer.cheap_peek(true)
|
269
|
+
# unless bytes
|
270
|
+
# bytes = buffer_lock.synchronize { buffer.cheap_peak }
|
271
|
+
# end
|
272
|
+
# n = io.write_nonblock(bytes)
|
250
273
|
# buffer.discard(n)
|
251
274
|
# end
|
252
275
|
# end
|
253
276
|
#
|
254
|
-
# @
|
255
|
-
|
256
|
-
|
257
|
-
|
277
|
+
# @param [Boolean] readonly to specify to only look at the read buffer
|
278
|
+
# @return [String, nil] some bytes from the start of the buffer, or nil when read buffer empty in readonly mode
|
279
|
+
def cheap_peek(readonly = false)
|
280
|
+
has_read_buffer = @offset < @read_buffer.bytesize
|
281
|
+
if has_read_buffer || !readonly
|
282
|
+
swap_buffers unless has_read_buffer
|
283
|
+
@read_buffer[@offset, @read_buffer.bytesize - @offset]
|
258
284
|
end
|
259
|
-
@read_buffer[@offset, @read_buffer.bytesize - @offset]
|
260
285
|
end
|
261
286
|
|
262
287
|
def eql?(other)
|
data/lib/ione/future.rb
CHANGED
@@ -590,7 +590,33 @@ module Ione
|
|
590
590
|
end
|
591
591
|
end
|
592
592
|
|
593
|
+
# @since v1.3.0
|
594
|
+
module Blockers
|
595
|
+
# Awaits the completion of a future and either returns the value or raises
|
596
|
+
# an error.
|
597
|
+
#
|
598
|
+
# @note
|
599
|
+
# This is a blocking operation and should be used with caution. You should
|
600
|
+
# never call this method in a block given to any of the other methods
|
601
|
+
# on {Future}. Prefer using combinator methods like {#map} and {#flat_map}
|
602
|
+
# to compose operations asynchronously, or use {#on_value}, {#on_failure}
|
603
|
+
# or {#on_complete} to listen for values and/or failures.
|
604
|
+
#
|
605
|
+
# @raise [Error] the error that failed this future
|
606
|
+
# @return [Object] the value of this future
|
607
|
+
def await(f)
|
608
|
+
semaphore = Queue.new
|
609
|
+
f.on_complete do |value, error|
|
610
|
+
semaphore << [value, error]
|
611
|
+
end
|
612
|
+
value, error = semaphore.pop
|
613
|
+
raise error if error
|
614
|
+
value
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
593
618
|
extend Factories
|
619
|
+
extend Blockers
|
594
620
|
include Combinators
|
595
621
|
include Callbacks
|
596
622
|
|
@@ -599,6 +625,8 @@ module Ione
|
|
599
625
|
@lock = Mutex.new
|
600
626
|
@state = PENDING_STATE
|
601
627
|
@listeners = []
|
628
|
+
@value = nil
|
629
|
+
@error = nil
|
602
630
|
end
|
603
631
|
|
604
632
|
# Registers a listener that will be called when this future completes,
|
@@ -674,35 +702,19 @@ module Ione
|
|
674
702
|
# to compose operations asynchronously, or use {#on_value}, {#on_failure}
|
675
703
|
# or {#on_complete} to listen for values and/or failures.
|
676
704
|
#
|
705
|
+
# @deprecated
|
706
|
+
# This method will be removed in the next major release.
|
707
|
+
# Use {Future.await}. Synchronously waiting for the result of a future is
|
708
|
+
# very rarely the right thing to do, and as the only blocking method on
|
709
|
+
# {Future}, {#value} doesn't belong.
|
710
|
+
#
|
677
711
|
# @raise [Error] the error that failed this future
|
678
712
|
# @return [Object] the value of this future
|
679
713
|
# @see Callbacks#on_value
|
680
714
|
# @see Callbacks#on_failure
|
681
715
|
# @see Callbacks#on_complete
|
682
716
|
def value
|
683
|
-
|
684
|
-
return @value if @state == RESOLVED_STATE
|
685
|
-
semaphore = nil
|
686
|
-
@lock.lock
|
687
|
-
begin
|
688
|
-
raise @error if @state == FAILED_STATE
|
689
|
-
return @value if @state == RESOLVED_STATE
|
690
|
-
semaphore = Queue.new
|
691
|
-
u = proc { semaphore << :unblock }
|
692
|
-
@listeners << u
|
693
|
-
ensure
|
694
|
-
@lock.unlock
|
695
|
-
end
|
696
|
-
while true
|
697
|
-
@lock.lock
|
698
|
-
begin
|
699
|
-
raise @error if @state == FAILED_STATE
|
700
|
-
return @value if @state == RESOLVED_STATE
|
701
|
-
ensure
|
702
|
-
@lock.unlock
|
703
|
-
end
|
704
|
-
semaphore.pop
|
705
|
-
end
|
717
|
+
Future.await(self)
|
706
718
|
end
|
707
719
|
alias_method :get, :value
|
708
720
|
|
@@ -859,9 +871,9 @@ module Ione
|
|
859
871
|
looping = more = true
|
860
872
|
while more
|
861
873
|
more = false
|
862
|
-
@futures.pop.on_complete do |
|
863
|
-
if
|
864
|
-
await_next(
|
874
|
+
@futures.pop.on_complete do |value, error|
|
875
|
+
if error || @futures.empty? || !looping || !Thread.current.equal?(outer)
|
876
|
+
await_next(value, error)
|
865
877
|
else
|
866
878
|
more = true
|
867
879
|
end
|
data/lib/ione/io/acceptor.rb
CHANGED
@@ -15,10 +15,12 @@ module Ione
|
|
15
15
|
def initialize(host, port, unblocker)
|
16
16
|
@host = host
|
17
17
|
@port = port
|
18
|
+
@io = nil
|
18
19
|
@unblocker = unblocker
|
19
20
|
@state = CONNECTING_STATE
|
20
21
|
@writable = false
|
21
22
|
@lock = Mutex.new
|
23
|
+
@data_listener = nil
|
22
24
|
@write_buffer = ByteBuffer.new
|
23
25
|
@closed_promise = Promise.new
|
24
26
|
end
|
@@ -163,18 +165,29 @@ module Ione
|
|
163
165
|
def flush
|
164
166
|
should_close = false
|
165
167
|
if @state == CONNECTED_STATE || @state == DRAINING_STATE
|
166
|
-
@
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
@
|
168
|
+
bytes = @write_buffer.cheap_peek(true)
|
169
|
+
unless bytes
|
170
|
+
@lock.lock
|
171
|
+
begin
|
172
|
+
if @writable
|
173
|
+
bytes = @write_buffer.cheap_peek
|
174
|
+
end
|
175
|
+
ensure
|
176
|
+
@lock.unlock
|
171
177
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
178
|
+
end
|
179
|
+
if bytes
|
180
|
+
bytes_written = @io.write_nonblock(bytes)
|
181
|
+
@lock.lock
|
182
|
+
begin
|
183
|
+
@write_buffer.discard(bytes_written)
|
184
|
+
@writable = !@write_buffer.empty?
|
185
|
+
if @state == DRAINING_STATE && !@writable
|
186
|
+
should_close = true
|
187
|
+
end
|
188
|
+
ensure
|
189
|
+
@lock.unlock
|
175
190
|
end
|
176
|
-
ensure
|
177
|
-
@lock.unlock
|
178
191
|
end
|
179
192
|
close if should_close
|
180
193
|
end
|
data/lib/ione/io/connection.rb
CHANGED
data/lib/ione/io/io_reactor.rb
CHANGED
@@ -95,8 +95,9 @@ module Ione
|
|
95
95
|
@clock = options[:clock] || Time
|
96
96
|
@state = PENDING_STATE
|
97
97
|
@error_listeners = []
|
98
|
-
@
|
99
|
-
@
|
98
|
+
@unblocker = Unblocker.new
|
99
|
+
@io_loop = IoLoopBody.new(@unblocker, @options)
|
100
|
+
@scheduler = Scheduler.new(@options)
|
100
101
|
@lock = Mutex.new
|
101
102
|
end
|
102
103
|
|
@@ -144,8 +145,6 @@ module Ione
|
|
144
145
|
@state = RUNNING_STATE
|
145
146
|
end
|
146
147
|
end
|
147
|
-
@unblocker = Unblocker.new
|
148
|
-
@io_loop.add_socket(@unblocker)
|
149
148
|
@started_promise = Promise.new
|
150
149
|
@stopped_promise = Promise.new
|
151
150
|
@error_listeners.each do |listener|
|
@@ -170,7 +169,6 @@ module Ione
|
|
170
169
|
end
|
171
170
|
@io_loop.close_sockets
|
172
171
|
@scheduler.cancel_timers
|
173
|
-
@unblocker = nil
|
174
172
|
ensure
|
175
173
|
if error
|
176
174
|
@state = CRASHED_STATE
|
@@ -367,14 +365,13 @@ module Ione
|
|
367
365
|
|
368
366
|
# @private
|
369
367
|
class Unblocker
|
370
|
-
|
371
|
-
|
368
|
+
BLOCKABLE_STATE = 0
|
369
|
+
UNBLOCKING_STATE = 1
|
370
|
+
CLOSED_STATE = 2
|
372
371
|
|
373
372
|
def initialize
|
374
373
|
@out, @in = IO.pipe
|
375
|
-
@
|
376
|
-
@state = OPEN_STATE
|
377
|
-
@unblocked = false
|
374
|
+
@state = BLOCKABLE_STATE
|
378
375
|
@writables = [@in]
|
379
376
|
end
|
380
377
|
|
@@ -395,33 +392,26 @@ module Ione
|
|
395
392
|
end
|
396
393
|
|
397
394
|
def unblock
|
398
|
-
if @state
|
399
|
-
@
|
400
|
-
|
401
|
-
if @state != CLOSED_STATE && IO.select(nil, @writables, nil, 0)
|
402
|
-
@in.write_nonblock(PING_BYTE)
|
403
|
-
end
|
404
|
-
ensure
|
405
|
-
@lock.unlock
|
406
|
-
end
|
395
|
+
if @state == BLOCKABLE_STATE
|
396
|
+
@state = UNBLOCKING_STATE
|
397
|
+
@in.write_nonblock(PING_BYTE)
|
407
398
|
end
|
399
|
+
rescue IO::WaitWritable
|
400
|
+
$stderr.puts('Oh noes we got blocked while writing the unblocker')
|
401
|
+
rescue IOError
|
402
|
+
$stderr.puts('Oh noes we wrote to the unblocker after it got closed, probably')
|
408
403
|
end
|
409
404
|
|
410
405
|
def read
|
411
|
-
@
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
end
|
416
|
-
ensure
|
417
|
-
@lock.unlock
|
406
|
+
@out.read_nonblock(65536)
|
407
|
+
@state = BLOCKABLE_STATE
|
408
|
+
rescue IOError
|
409
|
+
$stderr.puts('Oh noes we read from the unblocker after it got closed, probably')
|
418
410
|
end
|
419
411
|
|
420
412
|
def close
|
421
|
-
@
|
422
|
-
|
423
|
-
@state = CLOSED_STATE
|
424
|
-
end
|
413
|
+
return if @state == CLOSED_STATE
|
414
|
+
@state = CLOSED_STATE
|
425
415
|
@in.close
|
426
416
|
@out.close
|
427
417
|
@in = nil
|
@@ -472,13 +462,13 @@ module Ione
|
|
472
462
|
|
473
463
|
# @private
|
474
464
|
class IoLoopBody
|
475
|
-
def initialize(options={})
|
465
|
+
def initialize(unblocker, options={})
|
476
466
|
@selector = options[:selector] || IO
|
477
467
|
@clock = options[:clock] || Time
|
478
468
|
@timeout = options[:tick_resolution] || 1
|
479
469
|
@drain_timeout = options[:drain_timeout] || 5
|
480
470
|
@lock = Mutex.new
|
481
|
-
@sockets = []
|
471
|
+
@sockets = [unblocker]
|
482
472
|
end
|
483
473
|
|
484
474
|
def add_socket(socket)
|
@@ -592,8 +582,8 @@ module Ione
|
|
592
582
|
ensure
|
593
583
|
@lock.unlock
|
594
584
|
end
|
595
|
-
timers.each do |
|
596
|
-
|
585
|
+
timers.each do |t|
|
586
|
+
t.fail(CancelledError.new)
|
597
587
|
end
|
598
588
|
nil
|
599
589
|
end
|
@@ -614,8 +604,8 @@ module Ione
|
|
614
604
|
ensure
|
615
605
|
@lock.unlock
|
616
606
|
end
|
617
|
-
expired_timers.each do |
|
618
|
-
|
607
|
+
expired_timers.each do |t|
|
608
|
+
t.fulfill
|
619
609
|
end
|
620
610
|
end
|
621
611
|
end
|
@@ -626,4 +616,4 @@ module Ione
|
|
626
616
|
end
|
627
617
|
end
|
628
618
|
end
|
629
|
-
end
|
619
|
+
end
|
data/lib/ione/version.rb
CHANGED
data/spec/integration/io_spec.rb
CHANGED
@@ -37,7 +37,7 @@ describe 'An IO reactor' do
|
|
37
37
|
fake_server.await_connects(1)
|
38
38
|
fake_server.broadcast('hello world')
|
39
39
|
await { protocol_handler.data.bytesize > 0 }
|
40
|
-
protocol_handler.data.should
|
40
|
+
protocol_handler.data.should eq('hello world')
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'receives data on multiple connections' do
|
@@ -45,7 +45,7 @@ describe 'An IO reactor' do
|
|
45
45
|
fake_server.await_connects(10)
|
46
46
|
fake_server.broadcast('hello world')
|
47
47
|
await { protocol_handlers.all? { |c| c.data.bytesize > 0 } }
|
48
|
-
protocol_handlers.sample.data.should
|
48
|
+
protocol_handlers.sample.data.should eq('hello world')
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -81,7 +81,7 @@ describe 'An IO reactor' do
|
|
81
81
|
socket = TCPSocket.new(ENV['SERVER_HOST'], port)
|
82
82
|
socket.puts('HELLO')
|
83
83
|
result = socket.read(5)
|
84
|
-
result.should
|
84
|
+
result.should eq('HELLO')
|
85
85
|
socket.close
|
86
86
|
end
|
87
87
|
end
|
@@ -49,6 +49,7 @@ describe 'SSL' do
|
|
49
49
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
50
50
|
ssl_context.key = OpenSSL::PKey::RSA.new(ssl_key)
|
51
51
|
ssl_context.cert = OpenSSL::X509::Certificate.new(ssl_cert)
|
52
|
+
ssl_context.tmp_dh_callback = proc { SslSpec::DH_PARAMS }
|
52
53
|
|
53
54
|
f = io_reactor.start
|
54
55
|
f = f.flat_map do
|
@@ -80,8 +81,8 @@ describe 'SSL' do
|
|
80
81
|
end
|
81
82
|
client.write('hello world')
|
82
83
|
response_received.future.value
|
83
|
-
server_received_data.to_s.should
|
84
|
-
client_received_data.to_s.should
|
84
|
+
server_received_data.to_s.should eq('hello world')
|
85
|
+
client_received_data.to_s.should eq('dlrow olleh')
|
85
86
|
end
|
86
87
|
|
87
88
|
it 'fails to send a message when not using encryption' do
|
@@ -95,3 +96,7 @@ describe 'SSL' do
|
|
95
96
|
client.should be_closed
|
96
97
|
end
|
97
98
|
end
|
99
|
+
|
100
|
+
module SslSpec
|
101
|
+
DH_PARAMS = OpenSSL::PKey::DH.new(File.read(File.expand_path('../../resources/dh.pem', __FILE__)))
|
102
|
+
end
|