ione 1.3.0.pre1 → 1.3.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ione/byte_buffer.rb +20 -7
- data/lib/ione/future.rb +33 -23
- data/lib/ione/io/base_connection.rb +21 -10
- data/lib/ione/io/io_reactor.rb +21 -31
- data/lib/ione/version.rb +1 -1
- data/spec/ione/byte_buffer_spec.rb +22 -0
- data/spec/ione/io/io_reactor_spec.rb +24 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21dcca9fc4413a5b2487e3773e276ef7b3ae0814
|
4
|
+
data.tar.gz: 641d648b3d338383fe12e724afcace063043c18a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39f1108d6069ed37f168d68219ad6aebbe06fda839c7c238eea0cdf30bd27e4c15cf2aedd533e49b08abef170cfa2ef480a72471e9e44ca86b3e30dc491044be
|
7
|
+
data.tar.gz: 8faf34f893c4b913473734c66239145d41c0c1374c4db6571c03207c55d6bd8a5a38fe94724a2193541697f19cbf0c408cd19cacdd0382da87a4664b7be9ea91
|
data/lib/ione/byte_buffer.rb
CHANGED
@@ -176,7 +176,7 @@ module Ione
|
|
176
176
|
|
177
177
|
# Return the nt:h byte of the buffer, without removing it, and decode it as a signed or unsigned integer.
|
178
178
|
#
|
179
|
-
# @param [Integer] the zero-based positive position of the byte to read
|
179
|
+
# @param [Integer] index the zero-based positive position of the byte to read
|
180
180
|
# @return [Integer, nil] the integer interpretation of the byte at the specified position, or nil when beyond buffer size.
|
181
181
|
def getbyte(index, signed=false)
|
182
182
|
if @offset+index >= @read_buffer.bytesize
|
@@ -253,22 +253,35 @@ module Ione
|
|
253
253
|
# in situations where a loop wants to offer some bytes but can't be sure
|
254
254
|
# how many will be accepted — for example when writing to a socket.
|
255
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
|
+
#
|
256
263
|
# @example feeding bytes to a socket
|
257
264
|
# while true
|
258
265
|
# _, writables, _ = IO.select(nil, sockets)
|
259
266
|
# if writables
|
260
267
|
# writables.each do |io|
|
261
|
-
#
|
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)
|
262
273
|
# buffer.discard(n)
|
263
274
|
# end
|
264
275
|
# end
|
265
276
|
#
|
266
|
-
# @
|
267
|
-
|
268
|
-
|
269
|
-
|
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]
|
270
284
|
end
|
271
|
-
@read_buffer[@offset, @read_buffer.bytesize - @offset]
|
272
285
|
end
|
273
286
|
|
274
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
|
|
@@ -674,35 +700,19 @@ module Ione
|
|
674
700
|
# to compose operations asynchronously, or use {#on_value}, {#on_failure}
|
675
701
|
# or {#on_complete} to listen for values and/or failures.
|
676
702
|
#
|
703
|
+
# @deprecated
|
704
|
+
# This method will be removed in the next major release.
|
705
|
+
# Use {Future.await}. Synchronously waiting for the result of a future is
|
706
|
+
# very rarely the right thing to do, and as the only blocking method on
|
707
|
+
# {Future}, {#value} doesn't belong.
|
708
|
+
#
|
677
709
|
# @raise [Error] the error that failed this future
|
678
710
|
# @return [Object] the value of this future
|
679
711
|
# @see Callbacks#on_value
|
680
712
|
# @see Callbacks#on_failure
|
681
713
|
# @see Callbacks#on_complete
|
682
714
|
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
|
715
|
+
Future.await(self)
|
706
716
|
end
|
707
717
|
alias_method :get, :value
|
708
718
|
|
@@ -163,18 +163,29 @@ module Ione
|
|
163
163
|
def flush
|
164
164
|
should_close = false
|
165
165
|
if @state == CONNECTED_STATE || @state == DRAINING_STATE
|
166
|
-
@
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
@
|
166
|
+
bytes = @write_buffer.cheap_peek(true)
|
167
|
+
unless bytes
|
168
|
+
@lock.lock
|
169
|
+
begin
|
170
|
+
if @writable
|
171
|
+
bytes = @write_buffer.cheap_peek
|
172
|
+
end
|
173
|
+
ensure
|
174
|
+
@lock.unlock
|
171
175
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
176
|
+
end
|
177
|
+
if bytes
|
178
|
+
bytes_written = @io.write_nonblock(bytes)
|
179
|
+
@lock.lock
|
180
|
+
begin
|
181
|
+
@write_buffer.discard(bytes_written)
|
182
|
+
@writable = !@write_buffer.empty?
|
183
|
+
if @state == DRAINING_STATE && !@writable
|
184
|
+
should_close = true
|
185
|
+
end
|
186
|
+
ensure
|
187
|
+
@lock.unlock
|
175
188
|
end
|
176
|
-
ensure
|
177
|
-
@lock.unlock
|
178
189
|
end
|
179
190
|
close if should_close
|
180
191
|
end
|
data/lib/ione/io/io_reactor.rb
CHANGED
@@ -95,7 +95,8 @@ module Ione
|
|
95
95
|
@clock = options[:clock] || Time
|
96
96
|
@state = PENDING_STATE
|
97
97
|
@error_listeners = []
|
98
|
-
@
|
98
|
+
@unblocker = Unblocker.new
|
99
|
+
@io_loop = IoLoopBody.new(@unblocker, @options)
|
99
100
|
@scheduler = Scheduler.new
|
100
101
|
@lock = Mutex.new
|
101
102
|
end
|
@@ -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)
|
data/lib/ione/version.rb
CHANGED
@@ -330,6 +330,28 @@ module Ione
|
|
330
330
|
x.bytesize.should be <= buffer.bytesize
|
331
331
|
buffer.to_str.should start_with(x)
|
332
332
|
end
|
333
|
+
|
334
|
+
it 'considers contents in the write when read buffer consumed' do
|
335
|
+
buffer.append('foo')
|
336
|
+
buffer.append('bar')
|
337
|
+
buffer.read_byte
|
338
|
+
buffer.discard(5)
|
339
|
+
buffer.append('hello')
|
340
|
+
x = buffer.cheap_peek
|
341
|
+
x.bytesize.should be > 0
|
342
|
+
x.bytesize.should be <= buffer.bytesize
|
343
|
+
buffer.to_str.should start_with(x)
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'returns nil in readonly mode when read buffer is consumed' do
|
347
|
+
buffer.append('foo')
|
348
|
+
buffer.append('bar')
|
349
|
+
buffer.read_byte
|
350
|
+
buffer.discard(5)
|
351
|
+
buffer.append('hello')
|
352
|
+
x = buffer.cheap_peek(true)
|
353
|
+
x.should be_nil
|
354
|
+
end
|
333
355
|
end
|
334
356
|
|
335
357
|
describe '#getbyte' do
|
@@ -184,18 +184,17 @@ module Ione
|
|
184
184
|
end
|
185
185
|
|
186
186
|
it 'keeps running until stop completes' do
|
187
|
-
|
188
|
-
stop_barrier = Queue.new
|
187
|
+
barrier = Queue.new
|
189
188
|
selector.handler do
|
190
|
-
|
191
|
-
stop_barrier.pop
|
189
|
+
barrier.pop
|
192
190
|
[[], [], []]
|
193
191
|
end
|
194
192
|
reactor.start.value
|
195
193
|
future = reactor.stop
|
196
|
-
|
194
|
+
barrier.push(nil)
|
197
195
|
reactor.should be_running
|
198
|
-
|
196
|
+
barrier.push(nil) until future.completed?
|
197
|
+
reactor.should_not be_running
|
199
198
|
end
|
200
199
|
|
201
200
|
it 'unblocks the reactor' do
|
@@ -713,7 +712,11 @@ module Ione
|
|
713
712
|
|
714
713
|
describe IoLoopBody do
|
715
714
|
let :loop_body do
|
716
|
-
described_class.new(selector: selector, clock: clock)
|
715
|
+
described_class.new(unblocker, selector: selector, clock: clock)
|
716
|
+
end
|
717
|
+
|
718
|
+
let :unblocker do
|
719
|
+
double(:unblocker, connected?: true, connecting?: false, writable?: false, closed?: false)
|
717
720
|
end
|
718
721
|
|
719
722
|
let :selector do
|
@@ -728,14 +731,24 @@ module Ione
|
|
728
731
|
double(:socket, connected?: false, connecting?: false, writable?: false, closed?: false)
|
729
732
|
end
|
730
733
|
|
734
|
+
before do
|
735
|
+
unblocker.stub(:close) { unblocker.stub(:closed?).and_return(true) }
|
736
|
+
end
|
737
|
+
|
731
738
|
describe '#tick' do
|
732
739
|
before do
|
733
740
|
loop_body.add_socket(socket)
|
734
741
|
end
|
735
742
|
|
743
|
+
it 'passes the unblocker to the selector as the first readable' do
|
744
|
+
socket.stub(:connected?).and_return(true)
|
745
|
+
selector.should_receive(:select).with([unblocker, socket], anything, anything, anything).and_return([nil, nil, nil])
|
746
|
+
loop_body.tick
|
747
|
+
end
|
748
|
+
|
736
749
|
it 'passes connected sockets as readables to the selector' do
|
737
750
|
socket.stub(:connected?).and_return(true)
|
738
|
-
selector.should_receive(:select).with([socket], anything, anything, anything).and_return([nil, nil, nil])
|
751
|
+
selector.should_receive(:select).with([unblocker, socket], anything, anything, anything).and_return([nil, nil, nil])
|
739
752
|
loop_body.tick
|
740
753
|
end
|
741
754
|
|
@@ -748,7 +761,7 @@ module Ione
|
|
748
761
|
it 'passes writable sockets as both readable and writable to the selector' do
|
749
762
|
socket.stub(:connected?).and_return(true)
|
750
763
|
socket.stub(:writable?).and_return(true)
|
751
|
-
selector.should_receive(:select).with([socket], [socket], anything, anything).and_return([nil, nil, nil])
|
764
|
+
selector.should_receive(:select).with([unblocker, socket], [socket], anything, anything).and_return([nil, nil, nil])
|
752
765
|
loop_body.tick
|
753
766
|
end
|
754
767
|
|
@@ -761,7 +774,7 @@ module Ione
|
|
761
774
|
|
762
775
|
it 'filters out closed sockets' do
|
763
776
|
socket.stub(:closed?).and_return(true)
|
764
|
-
selector.should_receive(:select).with([], [], anything, anything).and_return([nil, nil, nil])
|
777
|
+
selector.should_receive(:select).with([unblocker], [], anything, anything).and_return([nil, nil, nil])
|
765
778
|
loop_body.tick
|
766
779
|
end
|
767
780
|
|
@@ -805,7 +818,7 @@ module Ione
|
|
805
818
|
end
|
806
819
|
|
807
820
|
it 'allows the caller to specify a custom timeout' do
|
808
|
-
loop_body = described_class.new(selector: selector, clock: clock, tick_resolution: 99)
|
821
|
+
loop_body = described_class.new(unblocker, selector: selector, clock: clock, tick_resolution: 99)
|
809
822
|
selector.should_receive(:select).with(anything, anything, anything, 99).and_return([[], [], []])
|
810
823
|
loop_body.tick
|
811
824
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ione
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.0.
|
4
|
+
version: 1.3.0.pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Theo Hultberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Reactive programming framework for Ruby, painless evented IO, futures
|
14
14
|
and an efficient byte buffer
|