ione 1.3.0.pre0 → 1.3.0.pre3

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