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 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