qpid_proton 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
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