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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6374a6945473a540ef416dec0eaef9f2c5a23913
4
- data.tar.gz: c4b5defcacf2770af407842ee88edc7f9e172a6b
2
+ SHA256:
3
+ metadata.gz: 78ca22da674434afcf1293e1b7d70bb3f13a0080b7c492f5016fb6f4f2428702
4
+ data.tar.gz: a50ef526b8a0163a3c2dd116a75d83b48cb6bbb79f80a4cdb0ba9e66c5c2e4b3
5
5
  SHA512:
6
- metadata.gz: 72ee4392985a601b028b4e6aa170accff6e8e50c27fc2cb7674cb9b3dd5b29091e8f92b3b5e26a515f372036fcaf1727bb866e1e8165a0f9567fd1749be91bac
7
- data.tar.gz: 231ee2abbb3d771d56d0fdab4c26b42f0684254b13056bf898dbce22d3ea2031e34aacc096a04dae6ee08a394ca880f03a362d7bbf9a96598becdbaaeec7e84c
6
+ metadata.gz: 8eb9100fc86fb57bff7a048f2d97411764df9e00fd5ec5afa921470283161a282686b9fdeea6cccd750b3dce5ea7efce666c9f6b98ef65ec3df2a3076e39c73d
7
+ data.tar.gz: be2df17439ad63370e7b980ab99e526080e995d65a25c3547fdfbeca34db25548ff6ba9c7b665cb1f7e4d6603d3a33398a16f9628fc5e9ad53f57f3fe9a8c455
@@ -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
- # 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)
250
273
  # buffer.discard(n)
251
274
  # end
252
275
  # end
253
276
  #
254
- # @return [String] some bytes from the start of the buffer
255
- def cheap_peek
256
- if @offset >= @read_buffer.bytesize
257
- 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]
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
- 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
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 |v, e|
863
- if e || @futures.empty? || !looping || !Thread.current.equal?(outer)
864
- await_next(v, e)
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
@@ -22,6 +22,7 @@ module Ione
22
22
  @backlog = backlog
23
23
  @unblocker = unblocker
24
24
  @reactor = reactor
25
+ @io = nil
25
26
  @socket_impl = socket_impl || ServerSocket
26
27
  @accept_listeners = []
27
28
  @lock = Mutex.new
@@ -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
- @lock.lock
167
- begin
168
- if @writable
169
- bytes_written = @io.write_nonblock(@write_buffer.cheap_peek)
170
- @write_buffer.discard(bytes_written)
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
- @writable = !@write_buffer.empty?
173
- if @state == DRAINING_STATE && !@writable
174
- should_close = true
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
@@ -14,6 +14,7 @@ module Ione
14
14
  @connection_timeout = connection_timeout
15
15
  @clock = clock
16
16
  @socket_impl = socket_impl
17
+ @addrinfos = nil
17
18
  @connected_promise = Promise.new
18
19
  on_closed(&method(:cleanup_on_close))
19
20
  end
@@ -95,8 +95,9 @@ module Ione
95
95
  @clock = options[:clock] || Time
96
96
  @state = PENDING_STATE
97
97
  @error_listeners = []
98
- @io_loop = IoLoopBody.new(@options)
99
- @scheduler = Scheduler.new
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
- 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)
@@ -592,8 +582,8 @@ module Ione
592
582
  ensure
593
583
  @lock.unlock
594
584
  end
595
- timers.each do |timer|
596
- timer.fail(CancelledError.new)
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 |timer|
618
- timer.fulfill
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
@@ -12,6 +12,7 @@ module Ione
12
12
  @socket_impl = socket_impl
13
13
  @ssl_context = ssl_context
14
14
  @raw_io = io
15
+ @io = nil
15
16
  @connected_promise = Promise.new
16
17
  on_closed(&method(:cleanup_on_close))
17
18
  end
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.pre0'.freeze
4
+ VERSION = '1.3.0.pre3'.freeze
5
5
  end
@@ -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 == 'hello world'
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 == 'hello world'
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 == 'HELLO'
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 == 'hello world'
84
- client_received_data.to_s.should == 'dlrow olleh'
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