qpid_proton 0.21.0 → 0.22.0

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: 6495371bf4622d74d87cfd02d4e58323652572eb
4
- data.tar.gz: 79af23eb910b3f38a1252530b318c45cafe6af54
3
+ metadata.gz: 606167f90e6d8e39827fd3166b073468735f6cb9
4
+ data.tar.gz: 8a98a50b98eebc56846228ed843780d9c139fc06
5
5
  SHA512:
6
- metadata.gz: 52e822deb2d458caa735b88d0d6dc5d5e6377d16e836d744d80cdf847c1fcff89b1f22e2e178aa653411e2ffecefb7e314dfa3315f10034c77795cecf4146414
7
- data.tar.gz: 4cea7fcfbdfadaf4c7fc1319303186c0282292134397c4d536eb46b9e8abccb719164b1b964b09a712aba95fab2193c00a502d1c39fbdace8b6ff636a594fc40
6
+ metadata.gz: d5850008fb8352a0773900878d0bc1afba63e8b8d6caacb9602fa4ff501086ed5b7fcf07edc978fd446866bce66e432698dee49c639fafb3eaf47097b4701b9c
7
+ data.tar.gz: dcd40be0c4ef4cf579e906606030120cd36e6bba9d59e9850e9443454ec6ff4c5da6a9678b1e11c726fd2cc6b65b5033bcbef953a057255b9bef851df756f024
data/examples/README.md CHANGED
@@ -58,19 +58,35 @@ In this set of examples we see the following event occurring, in addition to wha
58
58
 
59
59
  ## Now About That Broker example
60
60
 
61
- The **broker.rb** example application is a nice demonstration of doing something more interesting in Ruby with Proton.
61
+ The **broker.rb** example application is a nice demonstration of doing something more interesting in Ruby with Proton, and shows how to use multiple threads.
62
62
 
63
- The way the broker works is to listen to incoming connections, examine the components of the address for that connection, attach that connection to an exchange managing that address and then it sends any messages destined for that address to them.
63
+ The broker listens for incoming connections and sender/receiver links. It uses the source and target address of senders and receivers to identify a queue. Messages from receivers go on the queue, and are sent via senders.
64
64
 
65
65
  The components of the broker example include:
66
- * **Broker** - A class that extends the MessagingHandler class. It accepts incoming connections, manages subscribing them to exchanges, and transfers messages between them.
67
- * **MessageQueue** - Distributes messages to subscriptions.
68
-
69
- The Broker manages a map connecting a queue address to the instance of Exchange that holds references to the endpoints of interest.
66
+ * **Broker** is a Listener::Handler that accepts connections, and manages the set of named queues.
67
+ * **BrokerHandler** extends MessagingHandler to accept incoming connections, senders and receivers and transfers messages between them and the Broker's queues.
68
+ * **MessageQueue** - A queue of messages that keeps track of waiting senders.
70
69
 
71
70
  The broker application demonstrates a new set of events:
72
71
 
73
- * **on_link_open** - Fired when a remote link is opened. From this event the broker grabs the address and subscribes the link to an exchange for that address.
74
- * **on_link_close** - Fired when a remote link is closed. From this event the broker grabs the address and unsubscribes the link from that exchange.
75
- * **on_connection_close** - Fired when a remote connection is closed but the local end is still open.
76
- * **on_transport_close** - Fired when the protocol transport has closed. The broker removes all links for the disconnected connection, avoiding workign with endpoints that are now gone.
72
+ * **on_sender_open** - Fired when a sender link is opened, the broker gets the address and starts sending messages from the corresponding queue.
73
+ * **on_sender_close** - Fired when a sender link is closed, remove the sender from the queue so no more messages are sent.
74
+ * **on_connection_close** - Fired when the remote connection is closes, close all senders.
75
+ * **on_transport_close** - Fired when the transport (socket) has closed, close all senders.
76
+
77
+ It also demonstrates aspects of multi-threaded proton:
78
+
79
+ * **Thread safe MessageQueue** Uses a Mutex to make actions atomic when called concurrently.
80
+
81
+ * **Using WorkQueue** Proton objects like Sender are not thread safe. They are
82
+ normally only used in MessagingHandler#on_ callbacks. To request work from a
83
+ different thread you can add a code block to a WorkQueue, as shown in
84
+ MessageQueue#push.
85
+
86
+ * **Listener::Handler** The broker creates a new BrokerHandler instance for
87
+ each accepted connection. The container ensures that calls on each handler instance
88
+ are serialized even if there are multiple threads in the container.
89
+
90
+ * **Calling Container#run in multiple threads** The Container uses threads that call
91
+ #run as a thread pool to dispatch calls to MessagingHandler instances. Even
92
+ if there are multiple threads, calls to handler instance are serialized.
data/examples/broker.rb CHANGED
@@ -21,139 +21,142 @@ require 'qpid_proton'
21
21
  require 'optparse'
22
22
  require 'pathname'
23
23
 
24
+ # Thread safe message queue that notifies waiting senders when messages arrive.
24
25
  class MessageQueue
25
26
 
26
- def initialize(dynamic = false)
27
- @dynamic = dynamic
28
- @queue = Queue.new
29
- @consumers = []
30
- end
31
-
32
- def subscribe(consumer)
33
- @consumers << (consumer)
34
- end
35
-
36
- def unsubscribe(consumer)
37
- if @consumers.include?(consumer)
38
- @consumers.delete(consumer)
27
+ def initialize
28
+ @lock = Mutex.new # Make ations on the queue atomic
29
+ @messages = [] # Messages on the queue
30
+ @waiting = [] # Senders that are waiting for messages
31
+ end
32
+
33
+ # Push a message onto the queue and notify any waiting senders
34
+ def push(message)
35
+ @lock.synchronize do
36
+ @messages << message
37
+ unless @waiting.empty? # Notify waiting senders
38
+ # NOTE: the call to self.send_to is added to the sender's work_queue,
39
+ # and will be executed in the sender's thread
40
+ @waiting.each { |s| s.work_queue.add { self.send_to(s); } }
41
+ @waiting.clear
42
+ end
39
43
  end
40
- @consumers.empty? && (@dynamic || @queue.empty?)
41
- end
42
-
43
- def publish(message)
44
- @queue << message
45
- self.dispatch
46
44
  end
47
45
 
48
- def dispatch(consumer = nil)
49
- if consumer
50
- c = [consumer]
51
- else
52
- c = @consumers
53
- end
54
-
55
- while self.deliver_to(c) do
46
+ # Pop a message off the queue.
47
+ # If no messages available, record sender as waiting and return nil.
48
+ def pop(sender)
49
+ @lock.synchronize do
50
+ if @messages.empty?
51
+ @waiting << sender
52
+ nil
53
+ else
54
+ @messages.shift
55
+ end
56
56
  end
57
57
  end
58
58
 
59
- def deliver_to(consumers)
60
- result = false
61
- consumers.each do |consumer|
62
- if consumer.credit > 0 && !@queue.empty?
63
- consumer.send(@queue.pop(true))
64
- result = true
65
- end
59
+ # NOTE: Called in sender's thread.
60
+ # Pull messages from the queue as long as sender has credit.
61
+ # If queue runs out of messages, record sender as waiting.
62
+ def send_to(sender)
63
+ while sender.credit > 0 && (message = pop(sender))
64
+ sender.send(message)
66
65
  end
67
- return result
68
66
  end
69
67
 
68
+ def forget(sender)
69
+ @lock.synchronize { @waiting.delete(sender) }
70
+ end
70
71
  end
71
72
 
72
- class Broker < Qpid::Proton::MessagingHandler
73
-
74
- def initialize(url)
75
- super()
76
- @url = url
77
- @queues = {}
78
- begin # Optional SSL setup, ignore if we don't find cert files etc.
79
- @ssl_domain = Qpid::Proton::SSLDomain.new(Qpid::Proton::SSLDomain::MODE_SERVER)
80
- cert_passsword = "tserverpw"
81
- if Gem.win_platform? # Use P12 certs for windows schannel
82
- @ssl_domain.credentials("ssl_certs/tserver-certificate.p12", "", cert_passsword)
83
- else
84
- @ssl_domain.credentials("ssl_certs/tserver-certificate.pem", "ssl_certs/tserver-private-key.pem", cert_passsword)
85
- end
86
- @ssl_domain.allow_unsecured_client # SSL is optional, this is not secure.
87
- rescue
88
- @ssl_domain = nil # Don't worry if we can't set up SSL.
89
- end
90
- end
91
73
 
92
- def on_container_start(container)
93
- # Options for incoming connections, provide SSL configuration if we have it.
94
- opts = {:ssl_domain => @ssl_domain} if @ssl_domain
95
- @listener = container.listen(@url, Qpid::Proton::Listener::Handler.new(opts))
96
- STDOUT.puts "Listening on #{@url.inspect}"; STDOUT.flush
97
- end
74
+ # Handler for broker connections. In a multi-threaded application you should
75
+ # normally create a separate handler instance for each connection.
76
+ class BrokerHandler < Qpid::Proton::MessagingHandler
98
77
 
99
- def queue(address)
100
- unless @queues.has_key?(address)
101
- @queues[address] = MessageQueue.new
102
- end
103
- @queues[address]
78
+ def initialize(broker)
79
+ @broker = broker
104
80
  end
105
81
 
106
82
  def on_sender_open(sender)
107
83
  if sender.remote_source.dynamic?
108
- address = SecureRandom.uuid
109
- sender.source.address = address
110
- q = MessageQueue.new(true)
111
- @queues[address] = q
112
- q.subscribe(sender)
84
+ sender.source.address = SecureRandom.uuid
113
85
  elsif sender.remote_source.address
114
86
  sender.source.address = sender.remote_source.address
115
- self.queue(sender.source.address).subscribe(sender)
87
+ else
88
+ sender.connection.close("no source address")
89
+ return
116
90
  end
91
+ q = @broker.queue(sender.source.address)
92
+ q.send_to(sender)
117
93
  end
118
94
 
119
95
  def on_receiver_open(receiver)
120
96
  if receiver.remote_target.address
121
97
  receiver.target.address = receiver.remote_target.address
122
- end
123
- end
124
-
125
- def unsubscribe(link)
126
- if @queues.has_key?(link.source.address)
127
- if @queues[link.source.address].unsubscribe(link)
128
- @queues.delete(link.source.address)
129
- end
98
+ else
99
+ receiver.connection.close("no target address")
130
100
  end
131
101
  end
132
102
 
133
103
  def on_sender_close(sender)
134
- self.unsubscribe(sender)
104
+ q = @broker.queue(sender.source.address)
105
+ q.forget(sender) if q
135
106
  end
136
107
 
137
108
  def on_connection_close(connection)
138
- self.remove_stale_consumers(connection)
109
+ connection.each_sender { |s| on_sender_close(s) }
139
110
  end
140
111
 
141
112
  def on_transport_close(transport)
142
- self.remove_stale_consumers(transport.connection)
143
- end
144
-
145
- def remove_stale_consumers(connection)
146
- connection.each_sender { |s| unsubscribe(s) }
113
+ transport.connection.each_sender { |s| on_sender_close(s) }
147
114
  end
148
115
 
149
116
  def on_sendable(sender)
150
- q = self.queue(sender.source.address)
151
- q.dispatch(sender)
117
+ @broker.queue(sender.source.address).send_to(sender)
152
118
  end
153
119
 
154
120
  def on_message(delivery, message)
155
- q = self.queue(delivery.link.target.address)
156
- q.publish(message)
121
+ @broker.queue(delivery.receiver.target.address).push(message)
122
+ end
123
+ end
124
+
125
+ # Broker manages the queues and accepts incoming connections.
126
+ class Broker < Qpid::Proton::Listener::Handler
127
+
128
+ def initialize
129
+ @queues = {}
130
+ @connection_options = {}
131
+ ssl_setup
132
+ end
133
+
134
+ def ssl_setup
135
+ # Optional SSL setup
136
+ ssl = Qpid::Proton::SSLDomain.new(Qpid::Proton::SSLDomain::MODE_SERVER)
137
+ cert_passsword = "tserverpw"
138
+ if Gem.win_platform? # Use P12 certs for windows schannel
139
+ ssl.credentials("ssl_certs/tserver-certificate.p12", "", cert_passsword)
140
+ else
141
+ ssl.credentials("ssl_certs/tserver-certificate.pem", "ssl_certs/tserver-private-key.pem", cert_passsword)
142
+ end
143
+ ssl.allow_unsecured_client # SSL is optional, this is not secure.
144
+ @connection_options[:ssl_domain] = ssl if ssl
145
+ rescue
146
+ # Don't worry if we can't set up SSL.
147
+ end
148
+
149
+ def on_open(l)
150
+ STDOUT.puts "Listening on #{l}\n"; STDOUT.flush
151
+ end
152
+
153
+ # Create a new BrokerHandler instance for each connection we accept
154
+ def on_accept(l)
155
+ { :handler => BrokerHandler.new(self) }.update(@connection_options)
156
+ end
157
+
158
+ def queue(address)
159
+ @queues[address] ||= MessageQueue.new
157
160
  end
158
161
 
159
162
  end
@@ -164,4 +167,9 @@ Start an example broker listening on URL"
164
167
  return 1
165
168
  end
166
169
  url, = ARGV
167
- Qpid::Proton::Container.new(Broker.new(url)).run
170
+ container = Qpid::Proton::Container.new
171
+ container.listen(url, Broker.new)
172
+
173
+ # Run the container in multiple threads.
174
+ threads = 4.times.map { Thread.new { container.run }}
175
+ threads.each { |t| t.join }
@@ -46,7 +46,7 @@ class ExampleTest < MiniTest::Test
46
46
  end
47
47
 
48
48
  def test_helloworld
49
- assert_output("Hello world!", "helloworld.rb", $url, __method__)
49
+ assert_output("Hello world!", "helloworld.rb", $url, "examples")
50
50
  end
51
51
 
52
52
  def test_client_server
@@ -60,40 +60,40 @@ class ExampleTest < MiniTest::Test
60
60
  -> And the mome raths outgrabe.
61
61
  <- AND THE MOME RATHS OUTGRABE.
62
62
  EOS
63
- server = run_script("server.rb", $url, __method__)
64
- assert_output(want.strip, "client.rb", $url, __method__)
63
+ server = run_script("server.rb", $url, "examples")
64
+ assert_output(want.strip, "client.rb", $url, "examples")
65
65
  ensure
66
66
  Process.kill :TERM, server.pid if server
67
67
  end
68
68
 
69
69
  def test_send_recv
70
- assert_output("All 10 messages confirmed!", "simple_send.rb", $url, __method__)
70
+ assert_output("All 10 messages confirmed!", "simple_send.rb", $url, "examples")
71
71
  want = (0..9).reduce("") { |x,y| x << "Received: sequence #{y}\n" }
72
- assert_output(want.strip, "simple_recv.rb", $url, __method__)
72
+ assert_output(want.strip, "simple_recv.rb", $url, "examples")
73
73
  end
74
74
 
75
75
  def test_ssl_send_recv
76
- out = run_script("ssl_send.rb", $url, __method__).read.strip
76
+ out = run_script("ssl_send.rb", $url, "examples").read.strip
77
77
  assert_match(/Connection secured with "...*\"\nAll 10 messages confirmed!/, out)
78
78
  want = (0..9).reduce("") { |x,y| x << "Received: sequence #{y}\n" }
79
- assert_output(want.strip, "simple_recv.rb", $url, __method__)
79
+ assert_output(want.strip, "simple_recv.rb", $url, "examples")
80
80
  end
81
81
 
82
82
  def test_direct_recv
83
83
  url = test_url
84
- p = run_script("direct_recv.rb", url, __method__)
84
+ p = run_script("direct_recv.rb", url, "examples")
85
85
  p.readline # Wait till ready
86
- assert_output("All 10 messages confirmed!", "simple_send.rb", url, __method__)
86
+ assert_output("All 10 messages confirmed!", "simple_send.rb", url, "examples")
87
87
  want = (0..9).reduce("") { |x,y| x << "Received: sequence #{y}\n" }
88
88
  assert_equal(want.strip, p.read.strip)
89
89
  end
90
90
 
91
91
  def test_direct_send
92
92
  url = test_url
93
- p = run_script("direct_send.rb", url, __method__)
93
+ p = run_script("direct_send.rb", url, "examples")
94
94
  p.readline # Wait till ready
95
95
  want = (0..9).reduce("") { |x,y| x << "Received: sequence #{y}\n" }
96
- assert_output(want.strip, "simple_recv.rb", url, __method__)
96
+ assert_output(want.strip, "simple_recv.rb", url, "examples")
97
97
  assert_equal("All 10 messages confirmed!", p.read.strip)
98
98
  end
99
99
  end
@@ -19329,7 +19329,7 @@ _wrap_pn_data_put_decimal32(int argc, VALUE *argv, VALUE self) {
19329
19329
  }
19330
19330
  arg1 = (pn_data_t *)(argp1);
19331
19331
  {
19332
- arg2 = FIX2UINT(argv[1]);
19332
+ arg2 = NUM2UINT(argv[1]);
19333
19333
  }
19334
19334
  result = (int)pn_data_put_decimal32(arg1,arg2);
19335
19335
  vresult = SWIG_From_int((int)(result));
@@ -20044,7 +20044,7 @@ _wrap_pn_data_get_decimal32(int argc, VALUE *argv, VALUE self) {
20044
20044
  arg1 = (pn_data_t *)(argp1);
20045
20045
  result = (pn_decimal32_t)pn_data_get_decimal32(arg1);
20046
20046
  {
20047
- vresult = ULL2NUM(result);
20047
+ vresult = UINT2NUM(result);
20048
20048
  }
20049
20049
  return vresult;
20050
20050
  fail:
@@ -23711,7 +23711,7 @@ SWIGEXPORT void Init_cproton(void) {
23711
23711
  rb_define_module_function(mCproton, "pni_connection_driver", _wrap_pni_connection_driver, -1);
23712
23712
  rb_define_const(mCproton, "PROTON_IMPORT_EXPORT_H", SWIG_From_int((int)(1)));
23713
23713
  rb_define_const(mCproton, "PN_VERSION_MAJOR", SWIG_From_int((int)(0)));
23714
- rb_define_const(mCproton, "PN_VERSION_MINOR", SWIG_From_int((int)(21)));
23714
+ rb_define_const(mCproton, "PN_VERSION_MINOR", SWIG_From_int((int)(22)));
23715
23715
  rb_define_const(mCproton, "PN_VERSION_POINT", SWIG_From_int((int)(0)));
23716
23716
  rb_define_const(mCproton, "PROTON_TYPES_H", SWIG_From_int((int)(1)));
23717
23717
  rb_define_const(mCproton, "PN_MILLIS_MAX", SWIG_From_unsigned_SS_int((unsigned int)((~0U))));
@@ -124,9 +124,8 @@ module Qpid::Proton
124
124
 
125
125
  # @private
126
126
  def apply opts
127
- # NOTE: Only connection options are set here. Transport options are set
128
- # with {Transport#apply} from the connection_driver (or in
129
- # on_connection_bound if not using a connection_driver)
127
+ # NOTE: Only connection options are set here.
128
+ # Transport options must be applied with {Transport#apply}
130
129
  @container = opts[:container]
131
130
  cid = opts[:container_id] || (@container && @container.id) || SecureRandom.uuid
132
131
  cid = cid.to_s if cid.is_a? Symbol # Allow symbols as container name
@@ -135,12 +134,9 @@ module Qpid::Proton
135
134
  Cproton.pn_connection_set_password(@impl, opts[:password]) if opts[:password]
136
135
  Cproton.pn_connection_set_hostname(@impl, opts[:virtual_host]) if opts[:virtual_host]
137
136
  @link_prefix = opts[:link_prefix] || cid
138
- Codec::Data.from_object(Cproton.pn_connection_offered_capabilities(@impl),
139
- Types.symbol_array(opts[:offered_capabilities]))
140
- Codec::Data.from_object(Cproton.pn_connection_desired_capabilities(@impl),
141
- Types.symbol_array(opts[:desired_capabilities]))
142
- Codec::Data.from_object(Cproton.pn_connection_properties(@impl),
143
- Types.symbol_keys(opts[:properties]))
137
+ Codec::Data.from_object(Cproton.pn_connection_offered_capabilities(@impl), opts[:offered_capabilities])
138
+ Codec::Data.from_object(Cproton.pn_connection_desired_capabilities(@impl), opts[:desired_capabilities])
139
+ Codec::Data.from_object(Cproton.pn_connection_properties(@impl), opts[:properties])
144
140
  end
145
141
 
146
142
  # Idle-timeout advertised by the remote peer, in seconds.
@@ -253,17 +249,24 @@ module Qpid::Proton
253
249
  return enum_for(:each_link) unless block_given?
254
250
  l = Cproton.pn_link_head(@impl, 0);
255
251
  while l
256
- yield Link.wrap(l)
252
+ l2 = l # get next before yield, in case yield closes l and unlinks it
257
253
  l = Cproton.pn_link_next(l, 0)
254
+ yield Link.wrap(l2)
258
255
  end
259
256
  self
260
257
  end
261
258
 
262
259
  # Get the {Sender} links - see {#each_link}
263
- def each_sender() each_link.select { |l| l.sender? }; end
260
+ def each_sender()
261
+ return enum_for(:each_sender) unless block_given?
262
+ each_link.select { |l| yield l if l.sender? }
263
+ end
264
264
 
265
265
  # Get the {Receiver} links - see {#each_link}
266
- def each_receiver() each_link.select { |l| l.receiver? }; end
266
+ def each_receiver()
267
+ return enum_for(:each_receiver) unless block_given?
268
+ each_link.select { |l| yield l if l.receiver? }
269
+ end
267
270
 
268
271
  # @deprecated use {#MessagingHandler} to handle work
269
272
  def work_head
@@ -282,6 +285,9 @@ module Qpid::Proton
282
285
  @link_prefix + "/" + (@link_count += 1).to_s(32)
283
286
  end
284
287
 
288
+ # @return [WorkQueue] work queue to execute code serialized correctly for this connection
289
+ attr_reader :work_queue
290
+
285
291
  protected
286
292
 
287
293
  def _local_condition
@@ -118,9 +118,17 @@ module Qpid::Proton
118
118
  def tick(now=Time.now)
119
119
  transport = Cproton.pni_connection_driver_transport(@impl)
120
120
  ms = Cproton.pn_transport_tick(transport, (now.to_r * 1000).to_i)
121
- return ms.zero? ? nil : Time.at(ms.to_r / 1000);
121
+ @next_tick = ms.zero? ? nil : Time.at(ms.to_r / 1000);
122
+ unless @next_tick
123
+ idle = Cproton.pn_transport_get_idle_timeout(transport);
124
+ @next_tick = now + (idle.to_r / 1000) unless idle.zero?
125
+ end
126
+ @next_tick
122
127
  end
123
128
 
129
+ # Time returned by the last call to {#tick}
130
+ attr_accessor :next_tick
131
+
124
132
  # Disconnect the write side of the transport, *without* sending an AMQP
125
133
  # close frame. To close politely, you should use {Connection#close}, the
126
134
  # transport will close itself once the protocol close is complete.
@@ -181,6 +189,7 @@ module Qpid::Proton
181
189
  case e.method # Events that affect the driver
182
190
  when :on_transport_tail_closed then close_read
183
191
  when :on_transport_head_closed then close_write
192
+ when :on_transport_closed then @io.close rescue nil # Allow double-close
184
193
  end
185
194
  e.dispatch @adapter
186
195
  end
@@ -192,10 +201,11 @@ module Qpid::Proton
192
201
  # or nil if there are no scheduled events
193
202
  def process(now=Time.now)
194
203
  read
204
+ dispatch
195
205
  next_tick = tick(now)
196
- dispatch # Generate data for write
206
+ dispatch
197
207
  write
198
- dispatch # Consume events generated by write
208
+ dispatch
199
209
  return next_tick
200
210
  end
201
211
  end