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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a30c2a01cc8a7803d8709c93735b4e860cbc2744
4
- data.tar.gz: 4dbba5e04c9c8ae95d3588d377f645bcc8edef7c
3
+ metadata.gz: 21dcca9fc4413a5b2487e3773e276ef7b3ae0814
4
+ data.tar.gz: 641d648b3d338383fe12e724afcace063043c18a
5
5
  SHA512:
6
- metadata.gz: cef52539a1cc94bf977a83f73417be683cb31c3685f34915176b25ae21fd95e8ed365f5aba48938b257bcf858368e17471a645b8b6dc0a6a08181da3d734fc0e
7
- data.tar.gz: 374274bee30bf02895b893590e1a33fae294e1763ae959d7f2442519e42617dd65b57d715f7db891875b04e9ee612fef437a838dd40175cc0f4cff1e4d23dcb3
6
+ metadata.gz: 39f1108d6069ed37f168d68219ad6aebbe06fda839c7c238eea0cdf30bd27e4c15cf2aedd533e49b08abef170cfa2ef480a72471e9e44ca86b3e30dc491044be
7
+ data.tar.gz: 8faf34f893c4b913473734c66239145d41c0c1374c4db6571c03207c55d6bd8a5a38fe94724a2193541697f19cbf0c408cd19cacdd0382da87a4664b7be9ea91
@@ -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
- # n = io.write_nonblock(buffer.cheap_peek)
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
- # @return [String] some bytes from the start of the buffer
267
- def cheap_peek
268
- if @offset >= @read_buffer.bytesize
269
- swap_buffers
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
- raise @error if @state == FAILED_STATE
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
- @lock.lock
167
- begin
168
- if @writable
169
- bytes_written = @io.write_nonblock(@write_buffer.cheap_peek)
170
- @write_buffer.discard(bytes_written)
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
- @writable = !@write_buffer.empty?
173
- if @state == DRAINING_STATE && !@writable
174
- should_close = true
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
@@ -95,7 +95,8 @@ module Ione
95
95
  @clock = options[:clock] || Time
96
96
  @state = PENDING_STATE
97
97
  @error_listeners = []
98
- @io_loop = IoLoopBody.new(@options)
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
- OPEN_STATE = 0
371
- CLOSED_STATE = 1
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
- @lock = Mutex.new
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 != CLOSED_STATE
399
- @lock.lock
400
- begin
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
- @lock.lock
412
- if @state != CLOSED_STATE
413
- @out.read_nonblock(65536)
414
- @unblocked = false
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
- @lock.synchronize do
422
- return if @state == CLOSED_STATE
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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Ione
4
- VERSION = '1.3.0.pre1'.freeze
4
+ VERSION = '1.3.0.pre2'.freeze
5
5
  end
@@ -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
- running_barrier = Queue.new
188
- stop_barrier = Queue.new
187
+ barrier = Queue.new
189
188
  selector.handler do
190
- running_barrier.push(nil)
191
- stop_barrier.pop
189
+ barrier.pop
192
190
  [[], [], []]
193
191
  end
194
192
  reactor.start.value
195
193
  future = reactor.stop
196
- running_barrier.pop
194
+ barrier.push(nil)
197
195
  reactor.should be_running
198
- stop_barrier.push(nil) until future.completed?
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.pre1
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: 2015-12-17 00:00:00.000000000 Z
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