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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 606167f90e6d8e39827fd3166b073468735f6cb9
4
- data.tar.gz: 8a98a50b98eebc56846228ed843780d9c139fc06
3
+ metadata.gz: e42bf4b912dd9ac683d708d1f495f930524ae050
4
+ data.tar.gz: 88efa2086f91726b70d22ec26335c4199bd937e6
5
5
  SHA512:
6
- metadata.gz: d5850008fb8352a0773900878d0bc1afba63e8b8d6caacb9602fa4ff501086ed5b7fcf07edc978fd446866bce66e432698dee49c639fafb3eaf47097b4701b9c
7
- data.tar.gz: dcd40be0c4ef4cf579e906606030120cd36e6bba9d59e9850e9443454ec6ff4c5da6a9678b1e11c726fd2cc6b65b5033bcbef953a057255b9bef851df756f024
6
+ metadata.gz: fb46d347def13c8622f75d4a5db0f5263a3ea0659cdf140a6cbf2e7a1f3dd6a8ec4152313c0c7d61651530ca64beecc451a00926b176901ff018268fc867033a
7
+ data.tar.gz: 275ada30ae20a91a49cba9ae86352da2aaca5240294899165971d2cfb8540fcfefda5956dc9862fb6743eb26620c12e7a9af15c6afbd4acc589a087db990e34e
File without changes
data/examples/broker.rb CHANGED
@@ -70,7 +70,6 @@ class MessageQueue
70
70
  end
71
71
  end
72
72
 
73
-
74
73
  # Handler for broker connections. In a multi-threaded application you should
75
74
  # normally create a separate handler instance for each connection.
76
75
  class BrokerHandler < Qpid::Proton::MessagingHandler
@@ -136,9 +135,9 @@ class Broker < Qpid::Proton::Listener::Handler
136
135
  ssl = Qpid::Proton::SSLDomain.new(Qpid::Proton::SSLDomain::MODE_SERVER)
137
136
  cert_passsword = "tserverpw"
138
137
  if Gem.win_platform? # Use P12 certs for windows schannel
139
- ssl.credentials("ssl_certs/tserver-certificate.p12", "", cert_passsword)
138
+ ssl.credentials("ssl-certs/tserver-certificate.p12", "", cert_passsword)
140
139
  else
141
- ssl.credentials("ssl_certs/tserver-certificate.pem", "ssl_certs/tserver-private-key.pem", cert_passsword)
140
+ ssl.credentials("ssl-certs/tserver-certificate.pem", "ssl-certs/tserver-private-key.pem", cert_passsword)
142
141
  end
143
142
  ssl.allow_unsecured_client # SSL is optional, this is not secure.
144
143
  @connection_options[:ssl_domain] = ssl if ssl
@@ -147,7 +146,7 @@ class Broker < Qpid::Proton::Listener::Handler
147
146
  end
148
147
 
149
148
  def on_open(l)
150
- STDOUT.puts "Listening on #{l}\n"; STDOUT.flush
149
+ STDOUT.puts "Listening on #{l.port}\n"; STDOUT.flush
151
150
  end
152
151
 
153
152
  # Create a new BrokerHandler instance for each connection we accept
@@ -31,7 +31,7 @@ class DirectReceive < Qpid::Proton::MessagingHandler
31
31
  end
32
32
 
33
33
  class ListenOnce < Qpid::Proton::Listener::Handler
34
- def on_open(l) STDOUT.puts "Listening\n"; STDOUT.flush; end
34
+ def on_open(l) STDOUT.puts "Listening on #{l.port}\n"; STDOUT.flush; end
35
35
  def on_accept(l) l.close; end
36
36
  end
37
37
 
@@ -32,7 +32,7 @@ class DirectSend < Qpid::Proton::MessagingHandler
32
32
  end
33
33
 
34
34
  class ListenOnce < Qpid::Proton::Listener::Handler
35
- def on_open(l) STDOUT.puts "Listening\n"; STDOUT.flush; end
35
+ def on_open(l) STDOUT.puts "Listening on #{l.port}\n"; STDOUT.flush; end
36
36
  def on_accept(l) l.close; end
37
37
  end
38
38
 
@@ -29,11 +29,13 @@ rescue NameError # For older versions of MiniTest
29
29
  MiniTest::Test = MiniTest::Unit::TestCase
30
30
  end
31
31
 
32
- # URL with an unused port
33
- def test_url()
34
- "amqp://:#{TCPServer.open(0) { |s| s.addr[1] }}"
32
+ def listening_port(s)
33
+ /Listening on ([0-9]+)/.match(s)[1]
35
34
  end
36
35
 
36
+ def listening_url(s)
37
+ ":#{listening_port s}"
38
+ end
37
39
 
38
40
  class ExampleTest < MiniTest::Test
39
41
 
@@ -80,18 +82,16 @@ EOS
80
82
  end
81
83
 
82
84
  def test_direct_recv
83
- url = test_url
84
- p = run_script("direct_recv.rb", url, "examples")
85
- p.readline # Wait till ready
86
- assert_output("All 10 messages confirmed!", "simple_send.rb", url, "examples")
87
- want = (0..9).reduce("") { |x,y| x << "Received: sequence #{y}\n" }
88
- assert_equal(want.strip, p.read.strip)
85
+ p = run_script("direct_recv.rb", ":0", "examples")
86
+ url = listening_url(p.readline) # Wait till ready
87
+ assert_output("All 10 messages confirmed!", "simple_send.rb", url, "examples")
88
+ want = (0..9).reduce("") { |x,y| x << "Received: sequence #{y}\n" }
89
+ assert_equal(want.strip, p.read.strip)
89
90
  end
90
91
 
91
92
  def test_direct_send
92
- url = test_url
93
- p = run_script("direct_send.rb", url, "examples")
94
- p.readline # Wait till ready
93
+ p = run_script("direct_send.rb", ":0", "examples")
94
+ url = listening_url(p.readline) # Wait till ready
95
95
  want = (0..9).reduce("") { |x,y| x << "Received: sequence #{y}\n" }
96
96
  assert_output(want.strip, "simple_recv.rb", url, "examples")
97
97
  assert_equal("All 10 messages confirmed!", p.read.strip)
@@ -99,9 +99,9 @@ EOS
99
99
  end
100
100
 
101
101
  # Start the broker before all tests.
102
- $url = test_url
103
- $broker = IO.popen([RbConfig.ruby, 'broker.rb', $url])
104
- $broker.readline
102
+ $broker = IO.popen([RbConfig.ruby, 'broker.rb', ":0"])
103
+ l = $broker.readline
104
+ $url = listening_url(l)
105
105
 
106
106
  # Kill the broker after all tests
107
107
  MiniTest.after_run do
File without changes
File without changes
File without changes
@@ -14790,6 +14790,46 @@ fail:
14790
14790
  }
14791
14791
 
14792
14792
 
14793
+ SWIGINTERN VALUE
14794
+ _wrap_pn_message_send(int argc, VALUE *argv, VALUE self) {
14795
+ pn_message_t *arg1 = (pn_message_t *) 0 ;
14796
+ pn_link_t *arg2 = (pn_link_t *) 0 ;
14797
+ pn_rwbytes_t *arg3 = (pn_rwbytes_t *) 0 ;
14798
+ void *argp1 = 0 ;
14799
+ int res1 = 0 ;
14800
+ void *argp2 = 0 ;
14801
+ int res2 = 0 ;
14802
+ void *argp3 = 0 ;
14803
+ int res3 = 0 ;
14804
+ ssize_t result;
14805
+ VALUE vresult = Qnil;
14806
+
14807
+ if ((argc < 3) || (argc > 3)) {
14808
+ rb_raise(rb_eArgError, "wrong # of arguments(%d for 3)",argc); SWIG_fail;
14809
+ }
14810
+ res1 = SWIG_ConvertPtr(argv[0], &argp1,SWIGTYPE_p_pn_message_t, 0 | 0 );
14811
+ if (!SWIG_IsOK(res1)) {
14812
+ SWIG_exception_fail(SWIG_ArgError(res1), Ruby_Format_TypeError( "", "pn_message_t *","pn_message_send", 1, argv[0] ));
14813
+ }
14814
+ arg1 = (pn_message_t *)(argp1);
14815
+ res2 = SWIG_ConvertPtr(argv[1], &argp2,SWIGTYPE_p_pn_link_t, 0 | 0 );
14816
+ if (!SWIG_IsOK(res2)) {
14817
+ SWIG_exception_fail(SWIG_ArgError(res2), Ruby_Format_TypeError( "", "pn_link_t *","pn_message_send", 2, argv[1] ));
14818
+ }
14819
+ arg2 = (pn_link_t *)(argp2);
14820
+ res3 = SWIG_ConvertPtr(argv[2], &argp3,SWIGTYPE_p_pn_rwbytes_t, 0 | 0 );
14821
+ if (!SWIG_IsOK(res3)) {
14822
+ SWIG_exception_fail(SWIG_ArgError(res3), Ruby_Format_TypeError( "", "pn_rwbytes_t *","pn_message_send", 3, argv[2] ));
14823
+ }
14824
+ arg3 = (pn_rwbytes_t *)(argp3);
14825
+ result = (ssize_t)pn_message_send(arg1,arg2,arg3);
14826
+ vresult = SWIG_From_int((int)(result));
14827
+ return vresult;
14828
+ fail:
14829
+ return Qnil;
14830
+ }
14831
+
14832
+
14793
14833
  SWIGINTERN VALUE
14794
14834
  _wrap_pn_message_data(int argc, VALUE *argv, VALUE self) {
14795
14835
  pn_message_t *arg1 = (pn_message_t *) 0 ;
@@ -23711,7 +23751,7 @@ SWIGEXPORT void Init_cproton(void) {
23711
23751
  rb_define_module_function(mCproton, "pni_connection_driver", _wrap_pni_connection_driver, -1);
23712
23752
  rb_define_const(mCproton, "PROTON_IMPORT_EXPORT_H", SWIG_From_int((int)(1)));
23713
23753
  rb_define_const(mCproton, "PN_VERSION_MAJOR", SWIG_From_int((int)(0)));
23714
- rb_define_const(mCproton, "PN_VERSION_MINOR", SWIG_From_int((int)(22)));
23754
+ rb_define_const(mCproton, "PN_VERSION_MINOR", SWIG_From_int((int)(23)));
23715
23755
  rb_define_const(mCproton, "PN_VERSION_POINT", SWIG_From_int((int)(0)));
23716
23756
  rb_define_const(mCproton, "PROTON_TYPES_H", SWIG_From_int((int)(1)));
23717
23757
  rb_define_const(mCproton, "PN_MILLIS_MAX", SWIG_From_unsigned_SS_int((unsigned int)((~0U))));
@@ -24221,6 +24261,7 @@ SWIGEXPORT void Init_cproton(void) {
24221
24261
  rb_define_module_function(mCproton, "pn_message_properties", _wrap_pn_message_properties, -1);
24222
24262
  rb_define_module_function(mCproton, "pn_message_body", _wrap_pn_message_body, -1);
24223
24263
  rb_define_module_function(mCproton, "pn_message_decode", _wrap_pn_message_decode, -1);
24264
+ rb_define_module_function(mCproton, "pn_message_send", _wrap_pn_message_send, -1);
24224
24265
  rb_define_module_function(mCproton, "pn_message_data", _wrap_pn_message_data, -1);
24225
24266
  rb_define_const(mCproton, "PROTON_SASL_H", SWIG_From_int((int)(1)));
24226
24267
  rb_define_const(mCproton, "PN_SASL_NONE", SWIG_From_int((int)(PN_SASL_NONE)));
@@ -35,8 +35,8 @@ module Qpid::Proton
35
35
  include TimeCompare
36
36
 
37
37
  # Error raised if the container is used after {#stop} has been called.
38
- class StoppedError < RuntimeError
39
- def initialize(*args) super("container has been stopped"); end
38
+ class StoppedError < Qpid::Proton::StoppedError
39
+ def initialize() super("container has been stopped"); end
40
40
  end
41
41
 
42
42
  # Create a new Container
@@ -53,7 +53,7 @@ module Qpid::Proton
53
53
  # concurrently.
54
54
  #
55
55
  def initialize(*args)
56
- @handler, @id, @panic = nil
56
+ @handler, @id = nil
57
57
  case args.size
58
58
  when 2 then @handler, @id = args
59
59
  when 1 then
@@ -66,19 +66,13 @@ module Qpid::Proton
66
66
  @adapter = Handler::Adapter.adapt(@handler) || Handler::MessagingAdapter.new(nil)
67
67
  @id = (@id || SecureRandom.uuid).freeze
68
68
 
69
- # Implementation note:
70
- #
71
- # - #run threads take work items from @work, process them, and rearm them for select
72
- # - work items are: ConnectionTask, ListenTask, :start, :select, :schedule
73
- # - nil on the @work queue makes a #run thread exit
74
-
69
+ # Threading and implementation notes: see comment on #run_one
75
70
  @work = Queue.new
76
71
  @work << :start
77
72
  @work << :select
78
73
  @wake = SelectWaker.new # Wakes #run thread in IO.select
79
74
  @auto_stop = true # Stop when @active drops to 0
80
- @schedule = Schedule.new
81
- @schedule_working = false # True if :schedule is on the work queue
75
+ @work_queue = WorkQueue.new(self) # work scheduled by other threads for :select context
82
76
 
83
77
  # Following instance variables protected by lock
84
78
  @lock = Mutex.new
@@ -87,6 +81,7 @@ module Qpid::Proton
87
81
  @running = 0 # Count of #run threads
88
82
  @stopped = false # #stop called
89
83
  @stop_err = nil # Optional error to pass to tasks, from #stop
84
+ @panic = nil # Exception caught in a run thread, to be raised by all run threads
90
85
  end
91
86
 
92
87
  # @return [MessagingHandler] The container-wide handler
@@ -95,6 +90,9 @@ module Qpid::Proton
95
90
  # @return [String] unique identifier for this container
96
91
  attr_reader :id
97
92
 
93
+ def to_s() "#<#{self.class} id=#{id.inspect}>"; end
94
+ def inspect() to_s; end
95
+
98
96
  # Auto-stop flag.
99
97
  #
100
98
  # True (the default) means that the container will stop automatically, as if {#stop}
@@ -165,7 +163,7 @@ module Qpid::Proton
165
163
  not_stopped
166
164
  l = ListenTask.new(io, handler, self)
167
165
  add(l)
168
- l
166
+ l.listener
169
167
  end
170
168
 
171
169
  # Run the container: wait for IO activity, dispatch events to handlers.
@@ -198,12 +196,13 @@ module Qpid::Proton
198
196
  while task = @work.pop
199
197
  run_one(task, Time.now)
200
198
  end
201
- raise @panic if @panic
199
+ @lock.synchronize { raise @panic if @panic }
202
200
  ensure
203
201
  @lock.synchronize do
204
202
  if (@running -= 1) > 0
205
203
  work_wake nil # Signal the next thread
206
204
  else
205
+ # This is the last thread, no need to do maybe_panic around this final handler call.
207
206
  @adapter.on_container_stop(self) if @adapter.respond_to? :on_container_stop
208
207
  end
209
208
  end
@@ -217,15 +216,13 @@ module Qpid::Proton
217
216
  # is finished.
218
217
  #
219
218
  # The container can no longer be used, using a stopped container raises
220
- # {StoppedError} on attempting. Create a new container if you want to
221
- # resume activity.
219
+ # {StoppedError}. Create a new container if you want to resume activity.
222
220
  #
223
221
  # @param error [Condition] Optional error condition passed to
224
222
  # {MessagingHandler#on_transport_error} for each connection and
225
223
  # {Listener::Handler::on_error} for each listener.
226
224
  #
227
- # @param panic [Exception] Optional exception raised by all concurrent calls
228
- # to run()
225
+ # @param panic [Exception] Optional exception to raise from all calls to run()
229
226
  #
230
227
  def stop(error=nil, panic=nil)
231
228
  @lock.synchronize do
@@ -239,25 +236,22 @@ module Qpid::Proton
239
236
  # - no more select calls after next wakeup
240
237
  # - once @active == 0, all threads will be stopped with nil
241
238
  end
242
- @wake.wake
239
+ wake
243
240
  end
244
241
 
245
- # Schedule code to be executed after a delay.
246
- # @param delay [Numeric] delay in seconds, must be >= 0
247
- # @yield [ ] the block is invoked with no parameters in a {#run} thread after +delay+ has elapsed
248
- # @return [void]
249
- # @raise [ThreadError] if +non_block+ is true and the operation would block
250
- def schedule(delay, non_block=false, &block)
251
- not_stopped
252
- @lock.synchronize { @active += 1 } if @schedule.add(Time.now + delay, non_block, &block)
253
- @wake.wake
254
- end
242
+ # Get the {WorkQueue} that can be used to schedule code to be run by the container.
243
+ #
244
+ # Note: to run code that affects a {Connection} or it's associated objects,
245
+ # use {Connection#work_queue}
246
+ def work_queue() @work_queue; end
247
+
248
+ # (see WorkQueue#schedule)
249
+ def schedule(at, &block) @work_queue.schedule(at, &block) end
255
250
 
256
251
  private
257
252
 
258
253
  def wake() @wake.wake; end
259
254
 
260
- # Container driver applies options and adds container context to events
261
255
  class ConnectionTask < Qpid::Proton::HandlerDriver
262
256
  include TimeCompare
263
257
 
@@ -266,23 +260,23 @@ module Qpid::Proton
266
260
  transport.set_server if server
267
261
  transport.apply opts
268
262
  connection.apply opts
269
- @work_queue = WorkQueue.new container
263
+ @work_queue = WorkQueue.new(container)
270
264
  connection.instance_variable_set(:@work_queue, @work_queue)
271
265
  end
272
- def next_tick() earliest(super, @work_queue.send(:next_tick)); end
273
- def process(now) @work_queue.send(:process, now); super(); end
266
+ def next_tick() earliest(super, @work_queue.next_tick); end
267
+ def process(now) @work_queue.process(now); super(); end
274
268
 
275
269
  def dispatch # Intercept dispatch to close work_queue
276
270
  super
277
- @work_queue.send(:close) if read_closed? && write_closed?
271
+ @work_queue.close if read_closed? && write_closed?
278
272
  end
279
273
  end
280
274
 
281
275
  class ListenTask < Listener
282
276
 
283
277
  def initialize(io, handler, container)
284
- super
285
- @closing = @closed = nil
278
+ @io, @handler = io, handler
279
+ @listener = Listener.new(io, container)
286
280
  env = ENV['PN_TRACE_EVT']
287
281
  if env && ["true", "1", "yes", "on"].include?(env.downcase)
288
282
  @log_prefix = "[0x#{object_id.to_s(16)}](PN_LISTENER_"
@@ -292,28 +286,33 @@ module Qpid::Proton
292
286
  dispatch(:on_open);
293
287
  end
294
288
 
289
+ attr_reader :listener
290
+ def closing?() @listener.instance_variable_get(:@closing); end
291
+ def condition() @listener.instance_variable_get(:@condition); end
292
+ def closed?() @io.closed?; end
293
+
295
294
  def process
296
- return if @closed
297
- unless @closing
295
+ return if closed?
296
+ unless closing?
298
297
  begin
299
298
  return @io.accept, dispatch(:on_accept)
300
299
  rescue IO::WaitReadable, Errno::EINTR
301
- rescue IOError, SystemCallError => e
302
- close e
300
+ rescue StandardError => e
301
+ @listener.close(e)
303
302
  end
304
303
  end
305
304
  ensure
306
- if @closing
305
+ if closing?
307
306
  @io.close rescue nil
308
- @closed = true
309
- dispatch(:on_error, @condition) if @condition
307
+ @listener.instance_variable_set(:@closed, true)
308
+ dispatch(:on_error, condition) if condition
310
309
  dispatch(:on_close)
311
310
  end
312
311
  end
313
312
 
314
313
  def can_read?() !finished?; end
315
314
  def can_write?() false; end
316
- def finished?() @closed; end
315
+ def finished?() closed?; end
317
316
 
318
317
  def dispatch(method, *args)
319
318
  # TODO aconway 2017-11-27: better logging
@@ -322,6 +321,12 @@ module Qpid::Proton
322
321
  end
323
322
 
324
323
  def next_tick() nil; end
324
+
325
+ # Close listener and force immediate close of socket
326
+ def close(e=nil)
327
+ @listener.close(e)
328
+ @io.close rescue nil
329
+ end
325
330
  end
326
331
 
327
332
  # Selectable object that can be used to wake IO.select from another thread
@@ -338,14 +343,14 @@ module Qpid::Proton
338
343
  @lock.synchronize do
339
344
  return if @set # Don't write if already has data
340
345
  @set = true
341
- begin @wr.write_nonblock('x') rescue IO::WaitWritable end
346
+ @wr.write_nonblock('x') rescue nil
342
347
  end
343
348
  end
344
349
 
345
350
  def reset
346
351
  @lock.synchronize do
347
352
  return unless @set
348
- begin @rd.read_nonblock(1) rescue IO::WaitReadable end
353
+ @rd.read_nonblock(1) rescue nil
349
354
  @set = false
350
355
  end
351
356
  end
@@ -357,49 +362,58 @@ module Qpid::Proton
357
362
  end
358
363
 
359
364
  # Handle a single item from the @work queue, this is the heart of the #run loop.
365
+ # Take one task from @work, process it, and rearm for select
366
+ # Tasks are: ConnectionTask, ListenTask, :start, :select
367
+ # - ConnectionTask/ListenTask have #can_read, #can_write, #next_tick to set up IO.select
368
+ # and #process to run handlers and process relevant work_queue
369
+ # - nil means exit from the #run thread exit (handled by #run)
370
+ # - :select does IO.select and processes Container#work_queue
360
371
  def run_one(task, now)
361
372
  case task
362
373
 
363
374
  when :start
364
- @adapter.on_container_start(self) if @adapter.respond_to? :on_container_start
375
+ maybe_panic { @adapter.on_container_start(self) } if @adapter.respond_to? :on_container_start
365
376
 
366
377
  when :select
367
378
  # Compute read/write select sets and minimum next_tick for select timeout
368
379
  r, w = [@wake], []
369
- next_tick = @schedule.next_tick
380
+ next_tick = @work_queue.next_tick
370
381
  @lock.synchronize do
371
382
  @selectable.each do |s|
372
- r << s if s.send :can_read?
373
- w << s if s.send :can_write?
383
+ r << s if s.can_read?
384
+ w << s if s.can_write?
374
385
  next_tick = earliest(s.next_tick, next_tick)
375
386
  end
376
387
  end
377
388
 
378
389
  timeout = ((next_tick > now) ? next_tick - now : 0) if next_tick
379
390
  r, w = IO.select(r, w, nil, timeout)
380
- now = Time.now unless timeout == 0
381
391
  @wake.reset if r && r.delete(@wake)
392
+ now = Time.now unless timeout == 0 # Update now if we may have blocked
382
393
 
383
394
  # selected is a Set to eliminate duplicates between r, w and next_tick due.
384
395
  selected = Set.new
385
396
  selected.merge(r) if r
386
397
  selected.merge(w) if w
387
- @lock.synchronize do
388
- if @stopped # close everything
398
+ stopped = @lock.synchronize do
399
+ if @stopped # close everything
389
400
  @selectable.each { |s| s.close @stop_err; @work << s }
390
401
  @selectable.clear
402
+ @work_queue.close
391
403
  @wake.close
392
- return
393
- end
394
- if !@schedule_working && before_eq(@schedule.next_tick, now)
395
- @schedule_working = true
396
- @work << :schedule
404
+ else
405
+ @selectable -= selected # Remove already-selected tasks from @selectable
406
+ # Also select and remove items with next_tick before now
407
+ @selectable.delete_if { |s| before_eq(s.next_tick, now) and selected << s }
397
408
  end
398
- selected.merge(@selectable.select { |s| before_eq(s.next_tick, now) })
399
- @selectable -= selected # Remove selected tasks from @selectable
409
+ @stopped
400
410
  end
401
411
  selected.each { |s| @work << s } # Queue up tasks needing #process
402
- @work << :select # Enable next select
412
+ maybe_panic { @work_queue.process(now) } # Process current work queue items
413
+ @work_queue.clear if stopped
414
+ @lock.synchronize { check_stop_lh } if @work_queue.empty?
415
+
416
+ @work << :select unless stopped # Enable next select
403
417
 
404
418
  when ConnectionTask then
405
419
  maybe_panic { task.process now }
@@ -409,56 +423,7 @@ module Qpid::Proton
409
423
  io, opts = maybe_panic { task.process }
410
424
  add(connection_driver(io, opts, true)) if io
411
425
  rearm task
412
-
413
- when :schedule then
414
- if maybe_panic { @schedule.process now }
415
- @lock.synchronize { @active -= 1; check_stop_lh }
416
- else
417
- @lock.synchronize { @schedule_working = false }
418
- end
419
- end
420
- end
421
-
422
- def do_select
423
- # Compute the sets to select for read and write, and the minimum next_tick for the timeout
424
- r, w = [@wake], []
425
- next_tick = nil
426
- @lock.synchronize do
427
- @selectable.each do |s|
428
- r << s if s.can_read?
429
- w << s if s.can_write?
430
- next_tick = earliest(s.next_tick, next_tick)
431
- end
432
426
  end
433
- next_tick = earliest(@schedule.next_tick, next_tick)
434
-
435
- # Do the select and queue up all resulting work
436
- now = Time.now
437
- timeout = next_tick - now if next_tick
438
- r, w = (timeout.nil? || timeout > 0) && IO.select(r, w, nil, timeout)
439
- @wake.reset
440
- selected = Set.new
441
- @lock.synchronize do
442
- if @stopped
443
- @selectable.each { |s| s.close @stop_err; @work << s }
444
- @wake.close
445
- return
446
- end
447
- # Check if schedule has items due and is not already working
448
- if !@schedule_working && before_eq(@schedule.next_tick, now)
449
- @work << :schedule
450
- @schedule_working = true
451
- end
452
- # Eliminate duplicates between r, w and next_tick due.
453
- selected.merge(r) if r
454
- selected.delete(@wake)
455
- selected.merge(w) if w
456
- @selectable -= selected
457
- selected.merge(@selectable.select { |s| before_eq(s.next_tick, now) })
458
- @selectable -= selected
459
- end
460
- selected.each { |s| @work << s } # Queue up tasks needing #process
461
- @work << :select
462
427
  end
463
428
 
464
429
  # Rescue any exception raised by the block and stop the container.
@@ -510,7 +475,7 @@ module Qpid::Proton
510
475
  end
511
476
 
512
477
  def check_stop_lh
513
- if @active.zero? && (@auto_stop || @stopped)
478
+ if @active.zero? && (@auto_stop || @stopped) && @work_queue.empty?
514
479
  @stopped = true
515
480
  work_wake nil # Signal threads to stop
516
481
  true