ione 1.3.0.pre1 → 1.3.0.pre2
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/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
|