ione 1.3.0.pre0 → 1.3.0.pre3
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 +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
|