qpid_proton 0.22.0 → 0.23.0

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/{LICENSE → LICENSE.txt} +0 -0
  3. data/examples/broker.rb +3 -4
  4. data/examples/direct_recv.rb +1 -1
  5. data/examples/direct_send.rb +1 -1
  6. data/examples/example_test.rb +15 -15
  7. data/examples/{ssl_certs → ssl-certs}/README.txt +0 -0
  8. data/examples/{ssl_certs → ssl-certs}/tclient-certificate.p12 +0 -0
  9. data/examples/{ssl_certs → ssl-certs}/tclient-certificate.pem +0 -0
  10. data/examples/{ssl_certs → ssl-certs}/tclient-full.p12 +0 -0
  11. data/examples/{ssl_certs → ssl-certs}/tclient-private-key.pem +0 -0
  12. data/examples/{ssl_certs → ssl-certs}/tserver-certificate.p12 +0 -0
  13. data/examples/{ssl_certs → ssl-certs}/tserver-certificate.pem +0 -0
  14. data/examples/{ssl_certs → ssl-certs}/tserver-full.p12 +0 -0
  15. data/examples/{ssl_certs → ssl-certs}/tserver-private-key.pem +0 -0
  16. data/ext/cproton/cproton.c +42 -1
  17. data/lib/core/container.rb +75 -110
  18. data/lib/core/disposition.rb +24 -6
  19. data/lib/core/exceptions.rb +4 -0
  20. data/lib/core/listener.rb +10 -4
  21. data/lib/core/transfer.rb +1 -20
  22. data/lib/core/work_queue.rb +54 -33
  23. data/lib/util/schedule.rb +21 -37
  24. data/tests/{old_examples → old-examples}/broker.rb +0 -0
  25. data/tests/{old_examples → old-examples}/client.rb +0 -0
  26. data/tests/{old_examples → old-examples}/direct_recv.rb +0 -0
  27. data/tests/{old_examples → old-examples}/direct_send.rb +0 -0
  28. data/tests/{old_examples → old-examples}/helloworld.rb +0 -0
  29. data/tests/{old_examples → old-examples}/helloworld_direct.rb +0 -0
  30. data/tests/{old_examples → old-examples}/lib/debugging.rb +0 -0
  31. data/tests/{old_examples → old-examples}/lib/driver.rb +0 -0
  32. data/tests/{old_examples → old-examples}/lib/qpid_examples.rb +0 -0
  33. data/tests/{old_examples → old-examples}/lib/selectable.rb +0 -0
  34. data/tests/{old_examples → old-examples}/lib/send_and_receive.rb +0 -0
  35. data/tests/{old_examples → old-examples}/old_example_test.rb +0 -0
  36. data/tests/{old_examples → old-examples}/server.rb +0 -0
  37. data/tests/{old_examples → old-examples}/simple_recv.rb +0 -0
  38. data/tests/{old_examples → old-examples}/simple_send.rb +0 -0
  39. data/tests/test_container.rb +97 -29
  40. data/tests/test_delivery.rb +8 -0
  41. data/tests/test_interop.rb +1 -1
  42. data/tests/test_tools.rb +3 -3
  43. data/tests/test_utils.rb +63 -0
  44. metadata +28 -29
  45. data/ChangeLog +0 -185
  46. data/TODO +0 -8
@@ -18,7 +18,6 @@
18
18
 
19
19
  module Qpid::Proton
20
20
 
21
- # @deprecated use {Delivery}
22
21
  class Disposition
23
22
  include Util::Deprecation
24
23
 
@@ -27,12 +26,31 @@ module Qpid::Proton
27
26
  # @private
28
27
  extend Util::SWIGClassHelper
29
28
 
29
+ # States of a message transfer
30
+ module State
31
+ # Message was successfully processed by the receiver
32
+ ACCEPTED = Cproton::PN_ACCEPTED
30
33
 
31
- ACCEPTED = Cproton::PN_ACCEPTED
32
- REJECTED = Cproton::PN_REJECTED
33
- RELEASED = Cproton::PN_RELEASED
34
- MODIFIED = Cproton::PN_MODIFIED
35
- RECEIVED = Cproton::PN_RECEIVED
34
+ # Message rejected as invalid and unprocessable by the receiver.
35
+ REJECTED = Cproton::PN_REJECTED
36
+
37
+ # Message was not (and will not be) processed by the receiver, but may be
38
+ # acceptable if re-delivered to another receiver
39
+ RELEASED = Cproton::PN_RELEASED
40
+ # Like {RELEASED}, but there are modifications (see {Tracker#modifications})
41
+ # that must be applied to the message by the {Sender} before re-delivering it.
42
+ MODIFIED = Cproton::PN_MODIFIED
43
+
44
+ # Partial message data received. Only used during link recovery.
45
+ RECEIVED = Cproton::PN_RECEIVED
46
+
47
+ module ClassMethods
48
+ def name_of(state) Cproton::pn_disposition_type_name(state); end
49
+ end
50
+ extend ClassMethods
51
+ def self.included(klass) klass.extend ClassMethods; end
52
+ end
53
+ include State
36
54
 
37
55
  attr_reader :impl
38
56
 
@@ -126,4 +126,8 @@ module Qpid::Proton
126
126
  # so that the application can delay completing the open/close to a later time.
127
127
  class StopAutoResponse < ProtonError
128
128
  end
129
+
130
+ # Raised when a method is called on an object that has been stopped.
131
+ class StoppedError < StateError
132
+ end
129
133
  end
data/lib/core/listener.rb CHANGED
@@ -63,12 +63,14 @@ module Qpid::Proton
63
63
  # @return [Condition] The error condition if there is one
64
64
  attr_reader :condition
65
65
 
66
- # Close the listener
66
+ # Initiate closing the the listener.
67
+ # It will not be fully {#closed?} until its {Handler#on_close} is called by the {Container}
67
68
  # @param error [Condition] Optional error condition.
68
69
  def close(error=nil)
70
+ return if closed? || @closing
69
71
  @closing = true
70
72
  @condition ||= Condition.convert error
71
- @io.close_read rescue nil # Cause listener to wake out of IO.select
73
+ @io.close_read rescue nil # Force Container IO.select to wake with listener readable.
72
74
  nil
73
75
  end
74
76
 
@@ -78,11 +80,15 @@ module Qpid::Proton
78
80
  # Get the IP port used by the listener
79
81
  def port() to_io.addr[1]; end
80
82
 
83
+ # True if the listening socket is fully closed
84
+ def closed?() @io.closed?; end
85
+
81
86
  private # Called by {Container}
82
87
 
83
- def initialize(io, handler, container)
84
- @io, @handler = io, handler
88
+ def initialize(io, container)
89
+ @io = io
85
90
  @container = container
91
+ @closing = nil
86
92
  end
87
93
  end
88
94
  end
data/lib/core/transfer.rb CHANGED
@@ -42,26 +42,7 @@ module Qpid::Proton
42
42
 
43
43
  public
44
44
 
45
- # AMQP Delivery States describing the outcome of a message transfer
46
- module State
47
- # Message was successfully processed by the receiver
48
- ACCEPTED = Cproton::PN_ACCEPTED
49
-
50
- # Message rejected as invalid and unprocessable by the receiver.
51
- REJECTED = Cproton::PN_REJECTED
52
-
53
- # Message was not (and will not be) processed by the receiver, but may be
54
- # acceptable if re-delivered to another receiver
55
- RELEASED = Cproton::PN_RELEASED
56
-
57
- # Like {RELEASED}, but there are modifications (see {Tracker#modifications})
58
- # that must be applied to the message by the {Sender} before re-delivering it.
59
- MODIFIED = Cproton::PN_MODIFIED
60
-
61
- # Partial message data received. Only used during link recovery.
62
- RECEIVED = Cproton::PN_RECEIVED
63
- end
64
-
45
+ State = Disposition::State
65
46
  include State
66
47
 
67
48
  # @return [String] Unique ID for the transfer in the context of the {#link}
@@ -19,58 +19,79 @@ module Qpid::Proton
19
19
 
20
20
  # A thread-safe queue of work for multi-threaded programs.
21
21
  #
22
- # Instances of {Connection} and objects associated with it ({Session}, {Sender},
23
- # {Receiver}, {Delivery}, {Tracker}) are not thread-safe and must be
24
- # used correctly when multiple threads call {Container#run}
22
+ # A {Container} can have multiple threads calling {Container#run}
23
+ # The container ensures that work associated with a single {Connection} or
24
+ # {Listener} is _serialized_ - two threads will never concurrently call
25
+ # handlers associated with the same object.
25
26
  #
26
- # Calls to {MessagingHandler} methods by the {Container} are automatically
27
- # serialized for each connection instance. Other threads may have code
28
- # similarly serialized by adding it to the {Connection#work_queue} for the
29
- # connection. Each object related to a {Connection} also provides a
30
- # +work_queue+ method.
27
+ # To have your own code serialized in the same, add a block to the connection's
28
+ # {WorkQueue}. The block will be invoked as soon as it is safe to do so.
29
+ #
30
+ # A {Connection} and the objects associated with it ({Session}, {Sender},
31
+ # {Receiver}, {Delivery}, {Tracker}) are not thread safe, so if you have
32
+ # multiple threads calling {Container#run} or if you want to affect objects
33
+ # managed by the container from non-container threads you need to use the
34
+ # {WorkQueue}
31
35
  #
32
36
  class WorkQueue
33
37
 
34
- # Add code to be executed in series with other {Container} operations on the
35
- # work queue's owner. The code will be executed as soon as possible.
38
+ # Error raised if work is added after the queue has been stopped.
39
+ class StoppedError < Qpid::Proton::StoppedError
40
+ def initialize() super("WorkQueue has been stopped"); end
41
+ end
42
+
43
+ # Add a block of code to be invoked in sequence.
36
44
  #
45
+ # @yield [ ] the block will be invoked with no parameters in the appropriate thread context
37
46
  # @note Thread Safe: may be called in any thread.
38
- # @param non_block [Boolean] if true raise {ThreadError} if the operation would block.
39
- # @yield [ ] the block will be invoked with no parameters in the {WorkQueue} context,
40
- # which may be a different thread.
41
47
  # @return [void]
42
- # @raise [ThreadError] if +non_block+ is true and the operation would block
43
- # @raise [EOFError] if the queue is closed and cannot accept more work
44
- def add(non_block=false, &block)
45
- @schedule.add(Time.at(0), non_block, &block)
46
- @container.send :wake
48
+ # @raise [StoppedError] if the queue is closed and cannot accept more work
49
+ def add(&block)
50
+ schedule(0, &block)
47
51
  end
48
52
 
49
- # Schedule code to be executed after +delay+ seconds in series with other
50
- # {Container} operations on the work queue's owner.
51
- #
52
- # Work scheduled for after the {WorkQueue} has closed will be silently dropped.
53
+ # Schedule a block to be invoked at a certain time.
53
54
  #
55
+ # @param at [Time] Invoke block as soon as possible after Time +at+
56
+ # @param at [Numeric] Invoke block after a delay of +at+ seconds from now
57
+ # @yield [ ] (see #add)
54
58
  # @note (see #add)
55
- # @param delay delay in seconds until the block is added to the queue.
56
- # @param (see #add)
57
- # @yield (see #add)
58
- # @return [void]
59
+ # @return (see #add)
59
60
  # @raise (see #add)
60
- def schedule(delay, non_block=false, &block)
61
- @schedule.add(Time.now + delay, non_block, &block)
61
+ def schedule(at, &block)
62
+ raise ArgumentError, "no block" unless block_given?
63
+ @lock.synchronize do
64
+ raise @closed if @closed
65
+ @schedule.insert(at, block)
66
+ end
62
67
  @container.send :wake
63
68
  end
64
69
 
65
- private
66
-
70
+ # @private
67
71
  def initialize(container)
72
+ @lock = Mutex.new
68
73
  @schedule = Schedule.new
69
74
  @container = container
75
+ @closed = nil
76
+ end
77
+
78
+ # @private
79
+ def close() @lock.synchronize { @closed = StoppedError.new } end
80
+
81
+ # @private
82
+ def process(now)
83
+ while p = @lock.synchronize { @schedule.pop(now) }
84
+ p.call
85
+ end
70
86
  end
71
87
 
72
- def close() @schedule.close; end
73
- def process(now) @schedule.process(now); end
74
- def next_tick() @schedule.next_tick; end
88
+ # @private
89
+ def next_tick() @lock.synchronize { @schedule.next_tick } end
90
+
91
+ # @private
92
+ def empty?() @lock.synchronize { @schedule.empty? } end
93
+
94
+ # @private
95
+ def clear() @lock.synchronize { @schedule.clear } end
75
96
  end
76
97
  end
data/lib/util/schedule.rb CHANGED
@@ -27,53 +27,37 @@ module Qpid::Proton
27
27
  end
28
28
 
29
29
  # @private
30
- # A sorted, thread-safe list of scheduled Proc.
31
- # Note: calls to #process are always serialized, but calls to #add may be concurrent.
30
+ # A time-sorted list of objects. Thread unsafe.
32
31
  class Schedule
33
32
  include TimeCompare
34
- Item = Struct.new(:time, :proc)
33
+ Entry = Struct.new(:time, :item)
35
34
 
36
- def initialize()
37
- @lock = Mutex.new
38
- @items = []
39
- @closed = false
40
- end
35
+ def initialize() @entries = []; end
36
+
37
+ def empty?() @entries.empty?; end
41
38
 
42
39
  def next_tick()
43
- @lock.synchronize { @items.first.time unless @items.empty? }
40
+ @entries.first.time unless @entries.empty?
44
41
  end
45
42
 
46
- # @return true if the Schedule was previously empty
47
- # @raise EOFError if schedule is closed
48
- # @raise ThreadError if +non_block+ and operation would block
49
- def add(time, non_block=false, &proc)
50
- # non_block ignored for now, but we may implement a bounded schedule in future.
51
- @lock.synchronize do
52
- raise EOFError if @closed
53
- if at = (0...@items.size).bsearch { |i| @items[i].time > time }
54
- @items.insert(at, Item.new(time, proc))
55
- else
56
- @items << Item.new(time, proc)
57
- end
58
- return @items.size == 1
59
- end
43
+ # @param at [Time] Insert item at time +at+
44
+ # @param at [Numeric] Insert item at +Time.now \+ at+
45
+ # @param at [0] Insert item at Time.at(0)
46
+ def insert(at, item)
47
+ time = case at
48
+ when 0 then Time.at(0) # Avoid call to Time.now for immediate tasks
49
+ when Numeric then Time.now + at
50
+ else at
51
+ end
52
+ index = time && ((0...@entries.size).bsearch { |i| @entries[i].time > time })
53
+ @entries.insert(index || -1, Entry.new(time, item))
60
54
  end
61
55
 
62
- # @return true if the Schedule became empty as a result of this call
63
- def process(now)
64
- due = []
65
- empty = @lock.synchronize do
66
- due << @items.shift while (!@items.empty? && before_eq(@items.first.time, now))
67
- @items.empty?
68
- end
69
- due.each { |i| i.proc.call() }
70
- return empty && !due.empty?
56
+ # Return next item due at or before time, else nil
57
+ def pop(time)
58
+ @entries.shift.item if !@entries.empty? && before_eq(@entries.first.time, time)
71
59
  end
72
60
 
73
- # #add raises EOFError after #close.
74
- # #process can still be called to drain the schedule.
75
- def close()
76
- @lock.synchronize { @closed = true }
77
- end
61
+ def clear() @entries.clear; end
78
62
  end
79
63
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -330,34 +330,10 @@ class ContainerTest < MiniTest::Test
330
330
  assert_raises(Container::StoppedError) { cont.listen "" }
331
331
  end
332
332
 
333
- # Make sure Container::Scheduler puts tasks in proper order.
334
- def test_scheduler
335
- a = []
336
- s = Schedule.new
337
-
338
- assert_equal true, s.add(Time.at 3) { a << 3 }
339
- assert_equal false, s.process(Time.at 2) # Should not run
340
- assert_equal [], a
341
- assert_equal true, s.process(Time.at 3) # Should run
342
- assert_equal [3], a
343
-
344
- a = []
345
- assert_equal true, s.add(Time.at 3) { a << 3 }
346
- assert_equal false, s.add(Time.at 5) { a << 5 }
347
- assert_equal false, s.add(Time.at 1) { a << 1 }
348
- assert_equal false, s.add(Time.at 4) { a << 4 }
349
- assert_equal false, s.add(Time.at 4) { a << 4.1 }
350
- assert_equal false, s.add(Time.at 4) { a << 4.2 }
351
- assert_equal false, s.process(Time.at 4)
352
- assert_equal [1, 3, 4, 4.1, 4.2], a
353
- a = []
354
- assert_equal true, s.process(Time.at 5)
355
- assert_equal [5], a
356
- end
357
-
358
- def test_container_schedule
333
+ # Test container doesn't stops only when schedule work is done
334
+ def test_container_work_queue
359
335
  c = Container.new __method__
360
- delays = [0.1, 0.03, 0.02, 0.04]
336
+ delays = [0.1, 0.03, 0.02]
361
337
  a = []
362
338
  delays.each { |d| c.schedule(d) { a << [d, Time.now] } }
363
339
  start = Time.now
@@ -369,7 +345,91 @@ class ContainerTest < MiniTest::Test
369
345
  end
370
346
  end
371
347
 
372
- def test_work_queue
348
+ # Test container work queue finishes due tasks on external stop, drops future tasks
349
+ def test_container_work_queue_stop
350
+ q = Queue.new
351
+ c = Container.new __method__
352
+ thread = Thread.new { c.run }
353
+ time = Time.now + 0.01
354
+ # Mix good scheduled tasks at time and bad tasks scheduled after 10 secs
355
+ 10.times do
356
+ c.schedule(time) { q << true }
357
+ c.schedule(10) { q << false }
358
+ end
359
+ assert_same true, q.pop # First task processed, all others at same time are due
360
+ # Mix in some immediate tasks
361
+ 5.times do
362
+ c.work_queue.add { q << true } # Immediate
363
+ c.schedule(time) { q << true }
364
+ c.schedule(10) { q << false }
365
+ end
366
+ c.stop
367
+ thread.join
368
+ 19.times { assert_same true, q.pop }
369
+ assert_equal 0, q.size # Tasks with 10 sec delay should be dropped
370
+ end
371
+
372
+ # Chain schedule calls from other schedule calls
373
+ def test_container_schedule_chain
374
+ c = Container.new(__method__)
375
+ delays = [0.05, 0.02, 0.04]
376
+ i = delays.each
377
+ a = []
378
+ p = Proc.new { c.schedule(i.next) { a << Time.now; p.call } rescue nil }
379
+ p.call # Schedule the first, which schedules the second etc.
380
+ start = Time.now
381
+ c.run
382
+ assert_equal 3, a.size
383
+ delays.inject(0) do |d,sum|
384
+ x = a.shift
385
+ assert_in_delta start + d + sum, x, 0.01
386
+ sum + d
387
+ end
388
+ end
389
+
390
+ # Schedule calls from handlers
391
+ def test_container_schedule_handler
392
+ h = Class.new() do
393
+ def initialize() @got = []; end
394
+ attr_reader :got
395
+ def record(m) @got << m; end
396
+ def on_container_start(c) c.schedule(0) {record __method__}; end
397
+ def on_connection_open(c) c.close; c.container.schedule(0) {record __method__}; end
398
+ def on_connection_close(c) c.container.schedule(0) {record __method__}; end
399
+ end.new
400
+ t = ServerContainerThread.new(__method__, nil, 1, h)
401
+ t.connect(t.url)
402
+ t.join
403
+ assert_equal [:on_container_start, :on_connection_open, :on_connection_open, :on_connection_close, :on_connection_close], h.got
404
+ end
405
+
406
+ # Raising from container handler should stop container
407
+ def test_container_handler_raise
408
+ h = Class.new() do
409
+ def on_container_start(c) raise "BROKEN"; end
410
+ end.new
411
+ c = Container.new(h, __method__)
412
+ assert_equal("BROKEN", (assert_raises(RuntimeError) { c.run }).to_s)
413
+ end
414
+
415
+ # Raising from connection handler should stop container
416
+ def test_connection_handler_raise
417
+ h = Class.new() do
418
+ def on_connection_open(c) raise "BROKEN"; end
419
+ end.new
420
+ c = ServerContainer.new(__method__, nil, 1, h)
421
+ c.connect(c.url)
422
+ assert_equal("BROKEN", (assert_raises(RuntimeError) { c.run }).to_s)
423
+ end
424
+
425
+ # Raising from container schedule should stop container
426
+ def test_container_schedule_raise
427
+ c = Container.new(__method__)
428
+ c.schedule(0) { raise "BROKEN" }
429
+ assert_equal("BROKEN", (assert_raises(RuntimeError) { c.run }).to_s)
430
+ end
431
+
432
+ def test_connection_work_queue
373
433
  cont = ServerContainer.new(__method__, {}, 1)
374
434
  c = cont.connect(cont.url)
375
435
  t = Thread.new { cont.run }
@@ -391,6 +451,14 @@ class ContainerTest < MiniTest::Test
391
451
 
392
452
  c.work_queue.add { c.close }
393
453
  t.join
394
- assert_raises(EOFError) { c.work_queue.add { } }
454
+ assert_raises(WorkQueue::StoppedError) { c.work_queue.add { } }
455
+ end
456
+
457
+ # Raising from connection schedule should stop container
458
+ def test_connection_work_queue_raise
459
+ c = ServerContainer.new(__method__)
460
+ c.connect(c.url)
461
+ c.work_queue.add { raise "BROKEN" }
462
+ assert_equal("BROKEN", (assert_raises(RuntimeError) { c.run }).to_s)
395
463
  end
396
464
  end