ione 1.2.0.pre5 → 1.2.0.pre6

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