ione 1.2.0.pre5 → 1.2.0.pre6

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: c9338bdefe6cebb6528e884eed99230b3bd6ef0c
4
- data.tar.gz: 3e28ef46473409fcca687711a9550a4c91539d67
3
+ metadata.gz: 580012a6e7c6d917610532fc3fbd917ab9cd7a50
4
+ data.tar.gz: e5deb87d0deebb5d4cc9d3a1b516b672ef6848a5
5
5
  SHA512:
6
- metadata.gz: 2f28bebc52bf02a2d885d611b26cb68204e026e35c16d29790b320dc936adfa1e8af2f495148910696df8d7f9c5fc4346115424e3dd82ad52506193daf4ace9e
7
- data.tar.gz: 5a466d53f3988533504292269b7494e993ad955be605eccbd9d7230ec2c238fbcb276290a6dc465eaedc6d51e49c814c610304bbb61900048fd45206452ef64a
6
+ metadata.gz: 5760948be8fe0975500e3f673ffc0b4b4be1ed04aaccbfc44bc4535c668c1d7fec678a2fd498e608fd642a9e6e7354df4e01863f632afff471c4e70b57b6d895
7
+ data.tar.gz: e912eadf89022cb82d6f65726d1f48ccd8f17c1b434eb0a70dc783adef9407941331830ddc3898fb206e84b34aa300f0ed1b86115e98b684bc20544d38caa36b
@@ -17,6 +17,7 @@ module Ione
17
17
  @socket_impl = socket_impl || ServerSocket
18
18
  @accept_listeners = []
19
19
  @lock = Mutex.new
20
+ @state = :binding
20
21
  end
21
22
 
22
23
  def on_accept(&listener)
@@ -38,6 +39,7 @@ module Ione
38
39
  retry
39
40
  end
40
41
  end
42
+ @state = :connected
41
43
  Future.resolved(self)
42
44
  rescue => e
43
45
  close
@@ -45,13 +47,19 @@ module Ione
45
47
  end
46
48
 
47
49
  def close
48
- return false unless @io
49
- begin
50
- @io.close
51
- rescue SystemCallError, IOError
52
- # nothing to do, the socket was most likely already closed
50
+ @lock.synchronize do
51
+ return false if @state == :closed
52
+ @state = :closed
53
+ end
54
+ if @io
55
+ begin
56
+ @io.close
57
+ rescue SystemCallError, IOError
58
+ # nothing to do, the socket was most likely already closed
59
+ ensure
60
+ @io = nil
61
+ end
53
62
  end
54
- @io = nil
55
63
  true
56
64
  end
57
65
 
@@ -60,11 +68,11 @@ module Ione
60
68
  end
61
69
 
62
70
  def closed?
63
- @io.nil?
71
+ @state == :closed
64
72
  end
65
73
 
66
74
  def connected?
67
- !closed?
75
+ @state != :closed
68
76
  end
69
77
 
70
78
  def connecting?
@@ -10,6 +10,7 @@ module Ione
10
10
  @port = port
11
11
  @unblocker = unblocker
12
12
  @state = :connecting
13
+ @writable = false
13
14
  @lock = Mutex.new
14
15
  @write_buffer = ByteBuffer.new
15
16
  @closed_promise = Promise.new
@@ -22,6 +23,7 @@ module Ione
22
23
  @lock.synchronize do
23
24
  return false if @state == :closed
24
25
  @state = :closed
26
+ @writable = false
25
27
  end
26
28
  if @io
27
29
  begin
@@ -50,7 +52,7 @@ module Ione
50
52
  # has closed
51
53
  def drain
52
54
  @state = :draining
53
- close if @write_buffer.empty?
55
+ close unless @writable
54
56
  @closed_promise.future
55
57
  end
56
58
 
@@ -71,13 +73,7 @@ module Ione
71
73
 
72
74
  # @private
73
75
  def writable?
74
- @lock.lock
75
- begin
76
- empty_buffer = @write_buffer.empty?
77
- ensure
78
- @lock.unlock
79
- end
80
- !(closed? || empty_buffer)
76
+ @writable && @state != :closed
81
77
  end
82
78
 
83
79
  # Register to receive notifications when new data is read from the socket.
@@ -131,10 +127,11 @@ module Ione
131
127
  elsif bytes
132
128
  @write_buffer.append(bytes)
133
129
  end
130
+ @writable = !@write_buffer.empty?
134
131
  ensure
135
132
  @lock.unlock
136
133
  end
137
- @unblocker.unblock!
134
+ @unblocker.unblock
138
135
  end
139
136
  end
140
137
 
@@ -143,11 +140,12 @@ module Ione
143
140
  if @state == :connected || @state == :draining
144
141
  @lock.lock
145
142
  begin
146
- unless @write_buffer.empty?
143
+ if @writable
147
144
  bytes_written = @io.write_nonblock(@write_buffer.cheap_peek)
148
145
  @write_buffer.discard(bytes_written)
149
146
  end
150
- if @state == :draining && @write_buffer.empty?
147
+ @writable = !@write_buffer.empty?
148
+ if @state == :draining && !@writable
151
149
  close
152
150
  end
153
151
  ensure
@@ -87,6 +87,7 @@ module Ione
87
87
  @unblocker = Unblocker.new
88
88
  @io_loop = IoLoopBody.new(options)
89
89
  @io_loop.add_socket(@unblocker)
90
+ @scheduler = Scheduler.new
90
91
  @running = false
91
92
  @stopped = false
92
93
  @started_promise = Promise.new
@@ -128,10 +129,13 @@ module Ione
128
129
  Thread.start do
129
130
  @started_promise.fulfill(self)
130
131
  begin
131
- @io_loop.tick until @stopped
132
+ until @stopped
133
+ @io_loop.tick
134
+ @scheduler.tick
135
+ end
132
136
  ensure
133
137
  @io_loop.close_sockets
134
- @io_loop.cancel_timers
138
+ @scheduler.cancel_timers
135
139
  @running = false
136
140
  if $!
137
141
  @stopped_promise.fail($!)
@@ -181,7 +185,7 @@ module Ione
181
185
  connection = Connection.new(host, port, timeout, @unblocker, @clock)
182
186
  f = connection.connect
183
187
  @io_loop.add_socket(connection)
184
- @unblocker.unblock!
188
+ @unblocker.unblock
185
189
  if ssl
186
190
  f = f.flat_map do
187
191
  ssl_context = ssl == true ? nil : ssl
@@ -189,7 +193,7 @@ module Ione
189
193
  ff = upgraded_connection.connect
190
194
  @io_loop.remove_socket(connection)
191
195
  @io_loop.add_socket(upgraded_connection)
192
- @unblocker.unblock!
196
+ @unblocker.unblock
193
197
  ff
194
198
  end
195
199
  end
@@ -212,7 +216,7 @@ module Ione
212
216
  end
213
217
  f = server.bind
214
218
  @io_loop.add_socket(server)
215
- @unblocker.unblock!
219
+ @unblocker.unblock
216
220
  f = f.map(&block) if block_given?
217
221
  f
218
222
  end
@@ -220,7 +224,7 @@ module Ione
220
224
  # @private
221
225
  def accept(socket)
222
226
  @io_loop.add_socket(socket)
223
- @unblocker.unblock!
227
+ @unblocker.unblock
224
228
  end
225
229
 
226
230
  # Returns a future that completes after the specified number of seconds.
@@ -229,7 +233,7 @@ module Ione
229
233
  # future is completed
230
234
  # @return [Ione::Future] a future that completes when the timer expires
231
235
  def schedule_timer(timeout)
232
- @io_loop.schedule_timer(timeout)
236
+ @scheduler.schedule_timer(timeout)
233
237
  end
234
238
 
235
239
  # Cancels a previously scheduled timer.
@@ -238,7 +242,7 @@ module Ione
238
242
  #
239
243
  # @param timer_future [Ione::Future] the future returned by {#schedule_timer}
240
244
  def cancel_timer(timer_future)
241
- @io_loop.cancel_timer(timer_future)
245
+ @scheduler.cancel_timer(timer_future)
242
246
  end
243
247
 
244
248
  def to_s
@@ -269,7 +273,7 @@ module Ione
269
273
  @in.nil?
270
274
  end
271
275
 
272
- def unblock!
276
+ def unblock
273
277
  @lock.lock
274
278
  @in.write(PING_BYTE)
275
279
  ensure
@@ -322,7 +326,7 @@ module Ione
322
326
  end
323
327
 
324
328
  def to_s
325
- "#<Timer #{@time}>"
329
+ "#<Timer @time=#{@time}>"
326
330
  end
327
331
  alias_method :inspect, :to_s
328
332
  end
@@ -332,10 +336,9 @@ module Ione
332
336
  def initialize(options={})
333
337
  @selector = options[:selector] || IO
334
338
  @clock = options[:clock] || Time
339
+ @timeout = options[:tick_resolution] || 1
335
340
  @lock = Mutex.new
336
341
  @sockets = []
337
- @timer_queue = Heap.new
338
- @pending_timers = {}
339
342
  end
340
343
 
341
344
  def add_socket(socket)
@@ -353,6 +356,53 @@ module Ione
353
356
  end
354
357
  end
355
358
 
359
+ def close_sockets
360
+ @sockets.each do |s|
361
+ begin
362
+ s.close
363
+ rescue
364
+ # the socket had most likely already closed due to an error
365
+ end
366
+ end
367
+ end
368
+
369
+ def tick
370
+ readables = []
371
+ writables = []
372
+ connecting = []
373
+ @sockets.each do |s|
374
+ if s.connected?
375
+ readables << s
376
+ elsif s.connecting?
377
+ connecting << s
378
+ end
379
+ if s.connecting? || s.writable?
380
+ writables << s
381
+ end
382
+ end
383
+ begin
384
+ r, w, _ = @selector.select(readables, writables, nil, @timeout)
385
+ connecting.each { |s| s.connect }
386
+ r && r.each { |s| s.read }
387
+ w && w.each { |s| s.flush }
388
+ rescue IOError, Errno::EBADF
389
+ end
390
+ end
391
+
392
+ def to_s
393
+ %(#<#{IoReactor.name} @connections=[#{@sockets.map(&:to_s).join(', ')}]>)
394
+ end
395
+ end
396
+
397
+ # @private
398
+ class Scheduler
399
+ def initialize(options={})
400
+ @clock = options[:clock] || Time
401
+ @lock = Mutex.new
402
+ @timer_queue = Heap.new
403
+ @pending_timers = {}
404
+ end
405
+
356
406
  def schedule_timer(timeout)
357
407
  @lock.lock
358
408
  timer = Timer.new(@clock.now + timeout)
@@ -378,16 +428,6 @@ module Ione
378
428
  end
379
429
  end
380
430
 
381
- def close_sockets
382
- @sockets.each do |s|
383
- begin
384
- s.close unless s.closed?
385
- rescue
386
- # the socket had most likely already closed due to an error
387
- end
388
- end
389
- end
390
-
391
431
  def cancel_timers
392
432
  while (timer = @timer_queue.pop)
393
433
  @pending_timers.delete(timer.future)
@@ -395,36 +435,7 @@ module Ione
395
435
  end
396
436
  end
397
437
 
398
- def tick(timeout=1)
399
- check_sockets!(timeout)
400
- check_timers!
401
- end
402
-
403
- def to_s
404
- %(#<#{IoReactor.name} @connections=[#{@sockets.map(&:to_s).join(', ')}]>)
405
- end
406
-
407
- private
408
-
409
- def check_sockets!(timeout)
410
- readables, writables, connecting = [], [], []
411
- sockets = @sockets
412
- sockets.each do |s|
413
- next if s.closed?
414
- readables << s if s.connected?
415
- writables << s if s.connecting? || s.writable?
416
- connecting << s if s.connecting?
417
- end
418
- begin
419
- r, w, _ = @selector.select(readables, writables, nil, timeout)
420
- connecting.each(&:connect)
421
- r && r.each(&:read)
422
- w && w.each(&:flush)
423
- rescue IOError, Errno::EBADF
424
- end
425
- end
426
-
427
- def check_timers!
438
+ def tick
428
439
  now = @clock.now
429
440
  first_timer = @timer_queue.peek
430
441
  if first_timer && first_timer.time <= now
@@ -444,6 +455,10 @@ module Ione
444
455
  end
445
456
  end
446
457
  end
458
+
459
+ def to_s
460
+ %(#<#{self.class.name} @timers=[#{@pending_timers.values.map(&:to_s).join(', ')}]>)
461
+ end
447
462
  end
448
463
  end
449
464
  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.2.0.pre5'.freeze
4
+ VERSION = '1.2.0.pre6'.freeze
5
5
  end
@@ -18,32 +18,32 @@ describe 'An IO reactor' do
18
18
  end
19
19
 
20
20
  before do
21
- fake_server.start!
21
+ fake_server.start
22
22
  io_reactor.start
23
23
  end
24
24
 
25
25
  after do
26
26
  io_reactor.stop
27
- fake_server.stop!
27
+ fake_server.stop
28
28
  end
29
29
 
30
30
  it 'connects to the server' do
31
31
  io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1, &protocol_handler_factory)
32
- fake_server.await_connects!(1)
32
+ fake_server.await_connects(1)
33
33
  end
34
34
 
35
35
  it 'receives data' do
36
36
  protocol_handler = io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1, &protocol_handler_factory).value
37
- fake_server.await_connects!(1)
38
- fake_server.broadcast!('hello world')
37
+ fake_server.await_connects(1)
38
+ fake_server.broadcast('hello world')
39
39
  await { protocol_handler.data.bytesize > 0 }
40
40
  protocol_handler.data.should == 'hello world'
41
41
  end
42
42
 
43
43
  it 'receives data on multiple connections' do
44
44
  protocol_handlers = Array.new(10) { io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1, &protocol_handler_factory).value }
45
- fake_server.await_connects!(10)
46
- fake_server.broadcast!('hello world')
45
+ fake_server.await_connects(10)
46
+ fake_server.broadcast('hello world')
47
47
  await { protocol_handlers.all? { |c| c.data.bytesize > 0 } }
48
48
  protocol_handlers.sample.data.should == 'hello world'
49
49
  end
@@ -179,11 +179,11 @@ module Ione
179
179
  end
180
180
 
181
181
  it 'passes the unblocker along to the connection handler' do
182
- unblocker.stub(:unblock!)
182
+ unblocker.stub(:unblock)
183
183
  acceptor.bind
184
184
  acceptor.read
185
185
  accepted_handlers.first.write('foo')
186
- unblocker.should have_received(:unblock!)
186
+ unblocker.should have_received(:unblock)
187
187
  end
188
188
  end
189
189
 
@@ -95,7 +95,7 @@ shared_examples_for 'a connection' do |options|
95
95
  describe '#write/#flush' do
96
96
  before do
97
97
  socket.stub(:write_nonblock)
98
- unblocker.stub(:unblock!)
98
+ unblocker.stub(:unblock)
99
99
  end
100
100
 
101
101
  it 'appends to its buffer when #write is called' do
@@ -103,7 +103,7 @@ shared_examples_for 'a connection' do |options|
103
103
  end
104
104
 
105
105
  it 'unblocks the reactor' do
106
- unblocker.should_receive(:unblock!)
106
+ unblocker.should_receive(:unblock)
107
107
  handler.write('hello world')
108
108
  end
109
109
 
@@ -190,7 +190,7 @@ shared_examples_for 'a connection' do |options|
190
190
  handler.close
191
191
  handler.write('hello world')
192
192
  handler.flush
193
- unblocker.should_not have_received(:unblock!)
193
+ unblocker.should_not have_received(:unblock)
194
194
  end
195
195
  end
196
196
 
@@ -214,7 +214,7 @@ shared_examples_for 'a connection' do |options|
214
214
  handler.drain
215
215
  handler.write('hello world')
216
216
  handler.flush
217
- unblocker.should_not have_received(:unblock!)
217
+ unblocker.should_not have_received(:unblock)
218
218
  end
219
219
  end
220
220
  end
@@ -12,7 +12,7 @@ module Ione
12
12
  end
13
13
 
14
14
  let :unblocker do
15
- double(:unblocker, unblock!: nil)
15
+ double(:unblocker, unblock: nil)
16
16
  end
17
17
 
18
18
  let :socket_impl do
@@ -372,6 +372,13 @@ module Ione
372
372
  loop_body.tick
373
373
  end
374
374
 
375
+ it 'passes writable sockets as both readable and writable to the selector' do
376
+ socket.stub(:connected?).and_return(true)
377
+ socket.stub(:writable?).and_return(true)
378
+ selector.should_receive(:select).with([socket], [socket], anything, anything).and_return([nil, nil, nil])
379
+ loop_body.tick
380
+ end
381
+
375
382
  it 'passes connecting sockets as writable to the selector' do
376
383
  socket.stub(:connecting?).and_return(true)
377
384
  socket.stub(:connect)
@@ -383,9 +390,6 @@ module Ione
383
390
  socket.stub(:closed?).and_return(true)
384
391
  selector.should_receive(:select).with([], [], anything, anything).and_return([nil, nil, nil])
385
392
  loop_body.tick
386
- socket.stub(:connected?).and_return(true)
387
- selector.should_receive(:select).with([], [], anything, anything).and_return([nil, nil, nil])
388
- loop_body.tick
389
393
  end
390
394
 
391
395
  it 'does nothing when IO.select raises Errno::EBADF' do
@@ -428,29 +432,9 @@ module Ione
428
432
  end
429
433
 
430
434
  it 'allows the caller to specify a custom timeout' do
435
+ loop_body = described_class.new(selector: selector, clock: clock, tick_resolution: 99)
431
436
  selector.should_receive(:select).with(anything, anything, anything, 99).and_return([[], [], []])
432
- loop_body.tick(99)
433
- end
434
-
435
- it 'completes timers that have expired' do
436
- selector.stub(:select).and_return([nil, nil, nil])
437
- clock.stub(:now).and_return(1)
438
- future = loop_body.schedule_timer(1)
439
437
  loop_body.tick
440
- future.should_not be_completed
441
- clock.stub(:now).and_return(2)
442
- loop_body.tick
443
- future.should be_completed
444
- end
445
-
446
- it 'clears out timers that have expired' do
447
- selector.stub(:select).and_return([nil, nil, nil])
448
- clock.stub(:now).and_return(1)
449
- future = loop_body.schedule_timer(1)
450
- clock.stub(:now).and_return(2)
451
- loop_body.tick
452
- future.should be_completed
453
- expect { loop_body.tick }.to_not raise_error
454
438
  end
455
439
  end
456
440
 
@@ -474,28 +458,48 @@ module Ione
474
458
  loop_body.add_socket(socket2)
475
459
  loop_body.close_sockets
476
460
  end
461
+ end
462
+ end
477
463
 
478
- it 'does not close already closed sockets' do
479
- socket.stub(:closed?).and_return(true)
480
- socket.should_not_receive(:close)
481
- loop_body.add_socket(socket)
482
- loop_body.close_sockets
483
- end
464
+ describe Scheduler do
465
+ let :scheduler do
466
+ described_class.new(clock: clock)
484
467
  end
485
468
 
486
- describe '#cancel_timers' do
487
- before do
488
- selector.stub(:select).and_return([nil, nil, nil])
469
+ let :clock do
470
+ double(:clock, now: 0)
471
+ end
472
+
473
+ describe '#tick' do
474
+ it 'completes timers that have expired' do
475
+ clock.stub(:now).and_return(1)
476
+ future = scheduler.schedule_timer(1)
477
+ scheduler.tick
478
+ future.should_not be_completed
479
+ clock.stub(:now).and_return(2)
480
+ scheduler.tick
481
+ future.should be_completed
489
482
  end
490
483
 
484
+ it 'clears out timers that have expired' do
485
+ clock.stub(:now).and_return(1)
486
+ future = scheduler.schedule_timer(1)
487
+ clock.stub(:now).and_return(2)
488
+ scheduler.tick
489
+ future.should be_completed
490
+ expect { scheduler.tick }.to_not raise_error
491
+ end
492
+ end
493
+
494
+ describe '#cancel_timers' do
491
495
  it 'fails all active timers with a CancelledError' do
492
496
  clock.stub(:now).and_return(1)
493
- f1 = loop_body.schedule_timer(1)
494
- f2 = loop_body.schedule_timer(3)
495
- f3 = loop_body.schedule_timer(3)
497
+ f1 = scheduler.schedule_timer(1)
498
+ f2 = scheduler.schedule_timer(3)
499
+ f3 = scheduler.schedule_timer(3)
496
500
  clock.stub(:now).and_return(2)
497
- loop_body.tick
498
- loop_body.cancel_timers
501
+ scheduler.tick
502
+ scheduler.cancel_timers
499
503
  f1.should be_completed
500
504
  f2.should be_failed
501
505
  f3.should be_failed
@@ -16,7 +16,7 @@ module Ione
16
16
  end
17
17
 
18
18
  let :unblocker do
19
- double(:unblocker, unblock!: nil)
19
+ double(:unblocker, unblock: nil)
20
20
  end
21
21
 
22
22
  it_behaves_like 'a connection'
@@ -92,15 +92,15 @@ module Ione
92
92
  end
93
93
 
94
94
  it 'has a reference to the unblocker' do
95
- unblocker.stub(:unblock!)
95
+ unblocker.stub(:unblock)
96
96
  acceptor.bind
97
97
  acceptor.read
98
98
  accepted_handlers.first.write('foo')
99
- unblocker.should have_received(:unblock!)
99
+ unblocker.should have_received(:unblock)
100
100
  end
101
101
 
102
102
  it 'writes to the SSL socket' do
103
- unblocker.stub(:unblock!)
103
+ unblocker.stub(:unblock)
104
104
  ssl_socket.stub(:write_nonblock) { |b| b.bytesize }
105
105
  acceptor.bind
106
106
  acceptor.read
@@ -24,7 +24,7 @@ module Ione
24
24
  end
25
25
 
26
26
  let :unblocker do
27
- double(:unblocker, unblock!: nil)
27
+ double(:unblocker, unblock: nil)
28
28
  end
29
29
 
30
30
  let :ssl_context do
@@ -13,7 +13,7 @@ class FakeServer
13
13
  @received_bytes = ''
14
14
  end
15
15
 
16
- def start!(options={})
16
+ def start(options={})
17
17
  @lock.synchronize do
18
18
  return if @running
19
19
  @running = true
@@ -30,7 +30,7 @@ class FakeServer
30
30
  self
31
31
  end
32
32
 
33
- def stop!
33
+ def stop
34
34
  @lock.synchronize do
35
35
  return unless @running
36
36
  @running = false
@@ -41,13 +41,13 @@ class FakeServer
41
41
  end
42
42
  end
43
43
 
44
- def broadcast!(bytes)
44
+ def broadcast(bytes)
45
45
  @lock.synchronize do
46
46
  @connections.each { |c| c.write_nonblock(bytes) }
47
47
  end
48
48
  end
49
49
 
50
- def await_connects!(n=1)
50
+ def await_connects(n=1)
51
51
  started_at = Time.now
52
52
  until @connects >= n
53
53
  sleep(0.01)
@@ -55,7 +55,7 @@ class FakeServer
55
55
  end
56
56
  end
57
57
 
58
- def await_disconnects!(n=1)
58
+ def await_disconnects(n=1)
59
59
  started_at = Time.now
60
60
  until @disconnects >= n
61
61
  sleep(0.01)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ione
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0.pre5
4
+ version: 1.2.0.pre6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Hultberg