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.
- checksums.yaml +4 -4
- data/{LICENSE → LICENSE.txt} +0 -0
- data/examples/broker.rb +3 -4
- data/examples/direct_recv.rb +1 -1
- data/examples/direct_send.rb +1 -1
- data/examples/example_test.rb +15 -15
- data/examples/{ssl_certs → ssl-certs}/README.txt +0 -0
- data/examples/{ssl_certs → ssl-certs}/tclient-certificate.p12 +0 -0
- data/examples/{ssl_certs → ssl-certs}/tclient-certificate.pem +0 -0
- data/examples/{ssl_certs → ssl-certs}/tclient-full.p12 +0 -0
- data/examples/{ssl_certs → ssl-certs}/tclient-private-key.pem +0 -0
- data/examples/{ssl_certs → ssl-certs}/tserver-certificate.p12 +0 -0
- data/examples/{ssl_certs → ssl-certs}/tserver-certificate.pem +0 -0
- data/examples/{ssl_certs → ssl-certs}/tserver-full.p12 +0 -0
- data/examples/{ssl_certs → ssl-certs}/tserver-private-key.pem +0 -0
- data/ext/cproton/cproton.c +42 -1
- data/lib/core/container.rb +75 -110
- data/lib/core/disposition.rb +24 -6
- data/lib/core/exceptions.rb +4 -0
- data/lib/core/listener.rb +10 -4
- data/lib/core/transfer.rb +1 -20
- data/lib/core/work_queue.rb +54 -33
- data/lib/util/schedule.rb +21 -37
- data/tests/{old_examples → old-examples}/broker.rb +0 -0
- data/tests/{old_examples → old-examples}/client.rb +0 -0
- data/tests/{old_examples → old-examples}/direct_recv.rb +0 -0
- data/tests/{old_examples → old-examples}/direct_send.rb +0 -0
- data/tests/{old_examples → old-examples}/helloworld.rb +0 -0
- data/tests/{old_examples → old-examples}/helloworld_direct.rb +0 -0
- data/tests/{old_examples → old-examples}/lib/debugging.rb +0 -0
- data/tests/{old_examples → old-examples}/lib/driver.rb +0 -0
- data/tests/{old_examples → old-examples}/lib/qpid_examples.rb +0 -0
- data/tests/{old_examples → old-examples}/lib/selectable.rb +0 -0
- data/tests/{old_examples → old-examples}/lib/send_and_receive.rb +0 -0
- data/tests/{old_examples → old-examples}/old_example_test.rb +0 -0
- data/tests/{old_examples → old-examples}/server.rb +0 -0
- data/tests/{old_examples → old-examples}/simple_recv.rb +0 -0
- data/tests/{old_examples → old-examples}/simple_send.rb +0 -0
- data/tests/test_container.rb +97 -29
- data/tests/test_delivery.rb +8 -0
- data/tests/test_interop.rb +1 -1
- data/tests/test_tools.rb +3 -3
- data/tests/test_utils.rb +63 -0
- metadata +28 -29
- data/ChangeLog +0 -185
- data/TODO +0 -8
data/lib/core/disposition.rb
CHANGED
@@ -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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
|
data/lib/core/exceptions.rb
CHANGED
@@ -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
|
-
#
|
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 #
|
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,
|
84
|
-
@io
|
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
|
-
|
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}
|
data/lib/core/work_queue.rb
CHANGED
@@ -19,58 +19,79 @@ module Qpid::Proton
|
|
19
19
|
|
20
20
|
# A thread-safe queue of work for multi-threaded programs.
|
21
21
|
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
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
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
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
|
-
#
|
35
|
-
|
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 [
|
43
|
-
|
44
|
-
|
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
|
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
|
-
# @
|
56
|
-
# @param (see #add)
|
57
|
-
# @yield (see #add)
|
58
|
-
# @return [void]
|
59
|
+
# @return (see #add)
|
59
60
|
# @raise (see #add)
|
60
|
-
def schedule(
|
61
|
-
|
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
|
-
|
73
|
-
def
|
74
|
-
|
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
|
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
|
-
|
33
|
+
Entry = Struct.new(:time, :item)
|
35
34
|
|
36
|
-
def initialize()
|
37
|
-
|
38
|
-
|
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
|
-
@
|
40
|
+
@entries.first.time unless @entries.empty?
|
44
41
|
end
|
45
42
|
|
46
|
-
# @
|
47
|
-
# @
|
48
|
-
# @
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
#
|
63
|
-
def
|
64
|
-
|
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
|
-
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/tests/test_container.rb
CHANGED
@@ -330,34 +330,10 @@ class ContainerTest < MiniTest::Test
|
|
330
330
|
assert_raises(Container::StoppedError) { cont.listen "" }
|
331
331
|
end
|
332
332
|
|
333
|
-
#
|
334
|
-
def
|
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
|
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
|
-
|
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(
|
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
|