qpid_proton 0.21.0 → 0.22.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.
@@ -19,8 +19,11 @@
19
19
  require 'thread'
20
20
  require 'set'
21
21
  require_relative 'listener'
22
+ require_relative 'work_queue'
22
23
 
23
24
  module Qpid::Proton
25
+ public
26
+
24
27
  # An AMQP container manages a set of {Listener}s and {Connection}s which
25
28
  # contain {#Sender} and {#Receiver} links to transfer messages. Usually, each
26
29
  # AMQP client or server process has a single container for all of its
@@ -29,63 +32,7 @@ module Qpid::Proton
29
32
  # One or more threads can call {#run}, events generated by all the listeners and
30
33
  # connections will be dispatched in the {#run} threads.
31
34
  class Container
32
- private
33
-
34
- # Container driver applies options and adds container context to events
35
- class ConnectionTask < Qpid::Proton::HandlerDriver
36
- def initialize container, io, opts, server=false
37
- super io, opts[:handler]
38
- transport.set_server if server
39
- transport.apply opts
40
- connection.apply opts
41
- end
42
- end
43
-
44
- class ListenTask < Listener
45
-
46
- def initialize(io, handler, container)
47
- super
48
- @closing = @closed = nil
49
- env = ENV['PN_TRACE_EVT']
50
- if env && ["true", "1", "yes", "on"].include?(env.downcase)
51
- @log_prefix = "[0x#{object_id.to_s(16)}](PN_LISTENER_"
52
- else
53
- @log_prefix = nil
54
- end
55
- dispatch(:on_open);
56
- end
57
-
58
- def process
59
- return if @closed
60
- unless @closing
61
- begin
62
- return @io.accept, dispatch(:on_accept)
63
- rescue IO::WaitReadable, Errno::EINTR
64
- rescue IOError, SystemCallError => e
65
- close e
66
- end
67
- end
68
- ensure
69
- if @closing
70
- @io.close rescue nil
71
- @closed = true
72
- dispatch(:on_error, @condition) if @condition
73
- dispatch(:on_close)
74
- end
75
- end
76
-
77
- def can_read?() !finished?; end
78
- def can_write?() false; end
79
- def finished?() @closed; end
80
-
81
- def dispatch(method, *args)
82
- # TODO aconway 2017-11-27: better logging
83
- STDERR.puts "#{@log_prefix}#{([method[3..-1].upcase]+args).join ', '})" if @log_prefix
84
- @handler.__send__(method, self, *args) if @handler && @handler.respond_to?(method)
85
- end
86
- end
87
-
88
- public
35
+ include TimeCompare
89
36
 
90
37
  # Error raised if the container is used after {#stop} has been called.
91
38
  class StoppedError < RuntimeError
@@ -106,11 +53,13 @@ module Qpid::Proton
106
53
  # concurrently.
107
54
  #
108
55
  def initialize(*args)
56
+ @handler, @id, @panic = nil
109
57
  case args.size
110
58
  when 2 then @handler, @id = args
111
59
  when 1 then
112
60
  @id = String.try_convert(args[0]) || (args[0].to_s if args[0].is_a? Symbol)
113
61
  @handler = args[0] unless @id
62
+ when 0 then
114
63
  else raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..2"
115
64
  end
116
65
  # Use an empty messaging adapter to give default behaviour if there's no global handler.
@@ -119,16 +68,17 @@ module Qpid::Proton
119
68
 
120
69
  # Implementation note:
121
70
  #
122
- # - #run threads take work from @work
123
- # - Each driver and the Container itself is processed by at most one #run thread at a time
124
- # - The Container thread does IO.select
71
+ # - #run threads take work items from @work, process them, and rearm them for select
72
+ # - work items are: ConnectionTask, ListenTask, :start, :select, :schedule
125
73
  # - nil on the @work queue makes a #run thread exit
126
74
 
127
75
  @work = Queue.new
128
76
  @work << :start
129
- @work << self # Issue start and start start selecting
130
- @wake = IO.pipe # Wakes #run thread in IO.select
77
+ @work << :select
78
+ @wake = SelectWaker.new # Wakes #run thread in IO.select
131
79
  @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
132
82
 
133
83
  # Following instance variables protected by lock
134
84
  @lock = Mutex.new
@@ -161,7 +111,7 @@ module Qpid::Proton
161
111
 
162
112
  # Number of threads in {#run}
163
113
  # @return [Bool] {#run} thread count
164
- def running; @lock.synchronize { @running }; end
114
+ def running() @lock.synchronize { @running }; end
165
115
 
166
116
  # Open an AMQP connection.
167
117
  #
@@ -220,10 +170,25 @@ module Qpid::Proton
220
170
 
221
171
  # Run the container: wait for IO activity, dispatch events to handlers.
222
172
  #
223
- # More than one thread can call {#run} concurrently, the container will use
224
- # all the {#run} threads as a thread pool. Calls to
225
- # {Handler::MessagingHandler} methods are serialized for each connection or
226
- # listener, even if the container has multiple threads.
173
+ # *Multi-threaading* : More than one thread can call {#run} concurrently,
174
+ # the container will use all {#run} threads as a thread pool. Calls to
175
+ # {MessagingHandler} or {Listener::Handler} methods are serialized for each
176
+ # connection or listener. See {WorkQueue} for coordinating with other
177
+ # threads.
178
+ #
179
+ # *Exceptions*: If any handler method raises an exception it will stop the
180
+ # container, and the exception will be raised by all calls to {#run}. For
181
+ # single threaded code this is often desirable. Multi-threaded server
182
+ # applications should normally rescue exceptions in the handler and deal
183
+ # with them in another way: logging, closing the connection with an error
184
+ # condition, signalling another thread etc.
185
+ #
186
+ # @return [void] Returns when the container stops, see {#stop} and {#auto_stop}
187
+ #
188
+ # @raise [StoppedError] If the container has already been stopped when {#run} was called.
189
+ #
190
+ # @raise [Exception] If any {MessagingHandler} or {Listener::Handler} managed by
191
+ # the container raises an exception, that exception will be raised by {#run}
227
192
  #
228
193
  def run
229
194
  @lock.synchronize do
@@ -231,45 +196,9 @@ module Qpid::Proton
231
196
  raise StoppedError if @stopped
232
197
  end
233
198
  while task = @work.pop
234
- case task
235
-
236
- when :start
237
- @adapter.on_container_start(self) if @adapter.respond_to? :on_container_start
238
-
239
- when Container
240
- r, w = [@wake[0]], []
241
- @lock.synchronize do
242
- @selectable.each do |s|
243
- r << s if s.send :can_read?
244
- w << s if s.send :can_write?
245
- end
246
- end
247
- r, w = IO.select(r, w)
248
- selected = Set.new(r).merge(w)
249
- drain_wake if selected.delete?(@wake[0])
250
- stop_select = nil
251
- @lock.synchronize do
252
- if stop_select = @stopped # close everything
253
- selected += @selectable
254
- selected.each { |s| s.close @stop_err }
255
- @wake.each { |fd| fd.close() }
256
- end
257
- @selectable -= selected # Remove selected tasks
258
- end
259
- selected.each { |s| @work << s } # Queue up tasks needing #process
260
- @work << self unless stop_select
261
-
262
- when ConnectionTask then
263
- task.process
264
- rearm task
265
-
266
- when ListenTask then
267
- io, opts = task.process
268
- add(connection_driver(io, opts, true)) if io
269
- rearm task
270
- end
271
- # TODO aconway 2017-10-26: scheduled tasks, heartbeats
199
+ run_one(task, Time.now)
272
200
  end
201
+ raise @panic if @panic
273
202
  ensure
274
203
  @lock.synchronize do
275
204
  if (@running -= 1) > 0
@@ -291,37 +220,264 @@ module Qpid::Proton
291
220
  # {StoppedError} on attempting. Create a new container if you want to
292
221
  # resume activity.
293
222
  #
294
- # @param error [Condition] Optional transport/listener error condition
223
+ # @param error [Condition] Optional error condition passed to
224
+ # {MessagingHandler#on_transport_error} for each connection and
225
+ # {Listener::Handler::on_error} for each listener.
295
226
  #
296
- def stop(error=nil)
227
+ # @param panic [Exception] Optional exception raised by all concurrent calls
228
+ # to run()
229
+ #
230
+ def stop(error=nil, panic=nil)
297
231
  @lock.synchronize do
298
- raise StoppedError if @stopped
299
- @stopped = true
232
+ return if @stopped
300
233
  @stop_err = Condition.convert(error)
234
+ @panic = panic
235
+ @stopped = true
301
236
  check_stop_lh
302
237
  # NOTE: @stopped =>
303
238
  # - no new run threads can join
304
239
  # - no more select calls after next wakeup
305
240
  # - once @active == 0, all threads will be stopped with nil
306
241
  end
307
- wake
242
+ @wake.wake
308
243
  end
309
244
 
310
- protected
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
311
255
 
312
- def wake; @wake[1].write_nonblock('x') rescue nil; end
256
+ private
313
257
 
314
- # Normally if we add work we need to set a wakeup to ensure a single #run
315
- # thread doesn't get stuck in select while there is other work on the queue.
316
- def work_wake(task) @work << task; wake; end
258
+ def wake() @wake.wake; end
317
259
 
318
- def drain_wake
260
+ # Container driver applies options and adds container context to events
261
+ class ConnectionTask < Qpid::Proton::HandlerDriver
262
+ include TimeCompare
263
+
264
+ def initialize container, io, opts, server=false
265
+ super io, opts[:handler]
266
+ transport.set_server if server
267
+ transport.apply opts
268
+ connection.apply opts
269
+ @work_queue = WorkQueue.new container
270
+ connection.instance_variable_set(:@work_queue, @work_queue)
271
+ end
272
+ def next_tick() earliest(super, @work_queue.send(:next_tick)); end
273
+ def process(now) @work_queue.send(:process, now); super(); end
274
+
275
+ def dispatch # Intercept dispatch to close work_queue
276
+ super
277
+ @work_queue.send(:close) if read_closed? && write_closed?
278
+ end
279
+ end
280
+
281
+ class ListenTask < Listener
282
+
283
+ def initialize(io, handler, container)
284
+ super
285
+ @closing = @closed = nil
286
+ env = ENV['PN_TRACE_EVT']
287
+ if env && ["true", "1", "yes", "on"].include?(env.downcase)
288
+ @log_prefix = "[0x#{object_id.to_s(16)}](PN_LISTENER_"
289
+ else
290
+ @log_prefix = nil
291
+ end
292
+ dispatch(:on_open);
293
+ end
294
+
295
+ def process
296
+ return if @closed
297
+ unless @closing
298
+ begin
299
+ return @io.accept, dispatch(:on_accept)
300
+ rescue IO::WaitReadable, Errno::EINTR
301
+ rescue IOError, SystemCallError => e
302
+ close e
303
+ end
304
+ end
305
+ ensure
306
+ if @closing
307
+ @io.close rescue nil
308
+ @closed = true
309
+ dispatch(:on_error, @condition) if @condition
310
+ dispatch(:on_close)
311
+ end
312
+ end
313
+
314
+ def can_read?() !finished?; end
315
+ def can_write?() false; end
316
+ def finished?() @closed; end
317
+
318
+ def dispatch(method, *args)
319
+ # TODO aconway 2017-11-27: better logging
320
+ STDERR.puts "#{@log_prefix}#{([method[3..-1].upcase]+args).join ', '})" if @log_prefix
321
+ @handler.__send__(method, self, *args) if @handler && @handler.respond_to?(method)
322
+ end
323
+
324
+ def next_tick() nil; end
325
+ end
326
+
327
+ # Selectable object that can be used to wake IO.select from another thread
328
+ class SelectWaker
329
+ def initialize
330
+ @rd, @wr = IO.pipe
331
+ @lock = Mutex.new
332
+ @set = false
333
+ end
334
+
335
+ def to_io() @rd; end
336
+
337
+ def wake
338
+ @lock.synchronize do
339
+ return if @set # Don't write if already has data
340
+ @set = true
341
+ begin @wr.write_nonblock('x') rescue IO::WaitWritable end
342
+ end
343
+ end
344
+
345
+ def reset
346
+ @lock.synchronize do
347
+ return unless @set
348
+ begin @rd.read_nonblock(1) rescue IO::WaitReadable end
349
+ @set = false
350
+ end
351
+ end
352
+
353
+ def close
354
+ @rd.close
355
+ @wr.close
356
+ end
357
+ end
358
+
359
+ # Handle a single item from the @work queue, this is the heart of the #run loop.
360
+ def run_one(task, now)
361
+ case task
362
+
363
+ when :start
364
+ @adapter.on_container_start(self) if @adapter.respond_to? :on_container_start
365
+
366
+ when :select
367
+ # Compute read/write select sets and minimum next_tick for select timeout
368
+ r, w = [@wake], []
369
+ next_tick = @schedule.next_tick
370
+ @lock.synchronize do
371
+ @selectable.each do |s|
372
+ r << s if s.send :can_read?
373
+ w << s if s.send :can_write?
374
+ next_tick = earliest(s.next_tick, next_tick)
375
+ end
376
+ end
377
+
378
+ timeout = ((next_tick > now) ? next_tick - now : 0) if next_tick
379
+ r, w = IO.select(r, w, nil, timeout)
380
+ now = Time.now unless timeout == 0
381
+ @wake.reset if r && r.delete(@wake)
382
+
383
+ # selected is a Set to eliminate duplicates between r, w and next_tick due.
384
+ selected = Set.new
385
+ selected.merge(r) if r
386
+ selected.merge(w) if w
387
+ @lock.synchronize do
388
+ if @stopped # close everything
389
+ @selectable.each { |s| s.close @stop_err; @work << s }
390
+ @selectable.clear
391
+ @wake.close
392
+ return
393
+ end
394
+ if !@schedule_working && before_eq(@schedule.next_tick, now)
395
+ @schedule_working = true
396
+ @work << :schedule
397
+ end
398
+ selected.merge(@selectable.select { |s| before_eq(s.next_tick, now) })
399
+ @selectable -= selected # Remove selected tasks from @selectable
400
+ end
401
+ selected.each { |s| @work << s } # Queue up tasks needing #process
402
+ @work << :select # Enable next select
403
+
404
+ when ConnectionTask then
405
+ maybe_panic { task.process now }
406
+ rearm task
407
+
408
+ when ListenTask then
409
+ io, opts = maybe_panic { task.process }
410
+ add(connection_driver(io, opts, true)) if io
411
+ 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
+ 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
+ end
463
+
464
+ # Rescue any exception raised by the block and stop the container.
465
+ def maybe_panic
319
466
  begin
320
- @wake[0].read_nonblock(256) while true
321
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
467
+ yield
468
+ rescue Exception => e
469
+ stop(nil, e)
470
+ nil
322
471
  end
323
472
  end
324
473
 
474
+ # Normally if we add work we need to set a wakeup to ensure a single #run
475
+ # thread doesn't get stuck in select while there is other work on the queue.
476
+ def work_wake(task)
477
+ @work << task
478
+ @wake.wake
479
+ end
480
+
325
481
  def connection_driver(io, opts=nil, server=false)
326
482
  opts ||= {}
327
483
  opts[:container] = self
@@ -350,7 +506,7 @@ module Qpid::Proton
350
506
  @selectable << task
351
507
  end
352
508
  end
353
- wake
509
+ @wake.wake
354
510
  end
355
511
 
356
512
  def check_stop_lh
@@ -361,7 +517,7 @@ module Qpid::Proton
361
517
  end
362
518
  end
363
519
 
364
- def not_stopped; raise StoppedError if @lock.synchronize { @stopped }; end
520
+ def not_stopped() raise StoppedError if @lock.synchronize { @stopped }; end
365
521
 
366
522
  end
367
523
  end
@@ -25,7 +25,7 @@ module Qpid::Proton
25
25
  # @private
26
26
  PROTON_METHOD_PREFIX = "pn_disposition"
27
27
  # @private
28
- include Util::Wrapper
28
+ extend Util::SWIGClassHelper
29
29
 
30
30
 
31
31
  ACCEPTED = Cproton::PN_ACCEPTED
data/lib/core/endpoint.rb CHANGED
@@ -67,6 +67,9 @@ module Qpid::Proton
67
67
  self.connection.transport
68
68
  end
69
69
 
70
+ # @return [WorkQueue] the work queue for work on this endpoint.
71
+ def work_queue() connection.work_queue; end
72
+
70
73
  # @private
71
74
  # @return [Bool] true if {#state} has all the bits of `mask` set
72
75
  def check_state(mask) (self.state & mask) == mask; end
data/lib/core/listener.rb CHANGED
@@ -29,6 +29,9 @@ module Qpid::Proton
29
29
  # connections. This class simply returns a fixed set of options for every
30
30
  # connection accepted, but you can subclass and override all of the on_
31
31
  # methods to provide more interesting behaviour.
32
+ #
33
+ # *Note*: If a {Listener} method raises an exception, it will stop the {Container}
34
+ # that the handler is running in. See {Container#run}
32
35
  class Handler
33
36
  # @param opts [Hash] Options to return from on_accept.
34
37
  def initialize(opts=nil) @opts = opts || {}; end
@@ -72,6 +75,9 @@ module Qpid::Proton
72
75
  # Get the {IO} server socket used by the listener
73
76
  def to_io() @io; end
74
77
 
78
+ # Get the IP port used by the listener
79
+ def port() to_io.addr[1]; end
80
+
75
81
  private # Called by {Container}
76
82
 
77
83
  def initialize(io, handler, container)
data/lib/core/message.rb CHANGED
@@ -73,15 +73,8 @@ module Qpid::Proton
73
73
  # @private
74
74
  def pre_encode
75
75
  # encode elements from the message
76
- Codec::Data.from_object(Cproton::pn_message_properties(@impl),
77
- !@properties.empty? && Types.symbol_keys!(@properties))
78
- Codec::Data.from_object(Cproton::pn_message_instructions(@impl),
79
- !@instructions.empty? && Types.symbol_keys!(@instructions))
80
- if @annotations # Make sure keys are symbols
81
- @annotations.keys.each do |k|
82
- @annotations[k.to_sym] = @annotations.delete(k) unless k.is_a? Symbol
83
- end
84
- end
76
+ Codec::Data.from_object(Cproton::pn_message_properties(@impl), !@properties.empty? && @properties)
77
+ Codec::Data.from_object(Cproton::pn_message_instructions(@impl), !@instructions.empty? && @instructions)
85
78
  Codec::Data.from_object(Cproton::pn_message_annotations(@impl), !@annotations.empty? && @annotations)
86
79
  Codec::Data.from_object(Cproton::pn_message_body(@impl), @body)
87
80
  end
@@ -95,6 +88,7 @@ module Qpid::Proton
95
88
  @properties = {}
96
89
  @instructions = {}
97
90
  @annotations = {}
91
+ @body = nil
98
92
  self.body = body unless body.nil?
99
93
  if !opts.nil? then
100
94
  opts.each do |k, v|
@@ -510,6 +504,8 @@ module Qpid::Proton
510
504
  # @return [Object] body of the message.
511
505
  attr_accessor :body
512
506
 
507
+ def inspect() pre_encode; super; end
508
+
513
509
  private
514
510
 
515
511
  def check(err) # :nodoc:
@@ -28,6 +28,9 @@ module Qpid::Proton
28
28
  # {StopAutoResponse} from +#on_xxx_open+ or +#on_xxx_close+. The application becomes responsible
29
29
  # for calling +#open/#close+ at a later point.
30
30
  #
31
+ # *Note*: If a {MessagingHandler} method raises an exception, it will stop the {Container}
32
+ # that the handler is running in. See {Container#run}
33
+ #
31
34
  class MessagingHandler
32
35
 
33
36
  # @return [Hash] handler options, see {#initialize}
@@ -177,7 +180,8 @@ module Qpid::Proton
177
180
  # @!group Unhandled events
178
181
 
179
182
  # @!method on_error(error_condition)
180
- # The fallback error handler when no specific on_xxx_error is defined
183
+ # Called on an error if no more specific on_xxx_error method is provided.
184
+ # If on_error() is also not defined, the connection is closed with error_condition
181
185
  # @param error_condition [Condition] Provides information about the error.
182
186
 
183
187
  # @!method on_unhandled(method_name, *args)
data/lib/core/receiver.rb CHANGED
@@ -35,11 +35,10 @@ module Qpid::Proton
35
35
  # @param address [String] address of the source to receive from
36
36
  # @overload open_receiver(opts)
37
37
  # @param opts [Hash] Receiver options, see {Receiver#open}
38
- # @option opts [Boolean] :credit_window automatically maintain this much credit
38
+ # @option opts [Integer] :credit_window automatically maintain this much credit
39
39
  # for messages to be pre-fetched while the current message is processed.
40
40
  # @option opts [Boolean] :auto_accept if true, deliveries that are not settled by
41
41
  # the application in {MessagingHandler#on_message} are automatically accepted.
42
- # @option opts [Integer] :credit_window (10) automatically replenish credits for flow control.
43
42
  # @option opts [Boolean] :dynamic (false) dynamic property for source {Terminus#dynamic}
44
43
  # @option opts [String,Hash] :source source address or source options, see {Terminus#apply}
45
44
  # @option opts [String,Hash] :target target address or target options, see {Terminus#apply}
data/lib/core/sasl.rb CHANGED
@@ -40,7 +40,7 @@ module Qpid::Proton
40
40
 
41
41
  private
42
42
 
43
- include Util::Wrapper
43
+ extend Util::SWIGClassHelper
44
44
  PROTON_METHOD_PREFIX = "pn_sasl"
45
45
 
46
46
  public
data/lib/core/session.rb CHANGED
@@ -114,16 +114,14 @@ module Qpid::Proton
114
114
  # @param opts [Hash] receiver options, see {Receiver#open}
115
115
  # @return [Receiver]
116
116
  def open_receiver(opts=nil)
117
- name = opts[:name] rescue connection.link_name
118
- Receiver.new(Cproton.pn_receiver(@impl, name)).open(opts)
117
+ Receiver.new(Cproton.pn_receiver(@impl, link_name(opts))).open(opts)
119
118
  end
120
119
 
121
120
  # Create and open a {Sender} link, see {#open}
122
121
  # @param opts [Hash] sender options, see {Sender#open}
123
122
  # @return [Sender]
124
123
  def open_sender(opts=nil)
125
- name = opts[:name] rescue connection.link_name
126
- Sender.new(Cproton.pn_sender(@impl, name)).open(opts)
124
+ Sender.new(Cproton.pn_sender(@impl, link_name(opts))).open(opts)
127
125
  end
128
126
 
129
127
  # Get the links on this Session.
@@ -150,6 +148,10 @@ module Qpid::Proton
150
148
 
151
149
  private
152
150
 
151
+ def link_name(opts)
152
+ (opts.respond_to?(:to_hash) && opts[:name]) || connection.link_name
153
+ end
154
+
153
155
  def _local_condition
154
156
  Cproton.pn_session_condition(@impl)
155
157
  end
data/lib/core/terminus.rb CHANGED
@@ -68,7 +68,7 @@ module Qpid::Proton
68
68
  # @private
69
69
  PROTON_METHOD_PREFIX = "pn_terminus"
70
70
  # @private
71
- include Util::Wrapper
71
+ extend Util::SWIGClassHelper
72
72
 
73
73
  # @!attribute type
74
74
  #
@@ -234,15 +234,21 @@ module Qpid::Proton
234
234
  when :dynamic then self.dynamic = !!v
235
235
  when :distribution_mode then self.distribution_mode = v
236
236
  when :durability_mode then self.durability_mode = v
237
- when :timeout then self.timeout = v
237
+ when :timeout then self.timeout = v.round # Should be integer seconds
238
238
  when :expiry_policy then self.expiry_policy = v
239
- when :filter then self.filter = v
240
- when :capabilities then self.capabilities = v
239
+ when :filter then self.filter << v
240
+ when :capabilities then self.capabilities << v
241
241
  end
242
242
  end
243
243
  end
244
244
  end
245
245
 
246
+ def inspect()
247
+ "\#<#{self.class}: address=#{address.inspect} dynamic?=#{dynamic?.inspect}>"
248
+ end
249
+
250
+ def to_s() inspect; end
251
+
246
252
  can_raise_error([:type=, :address=, :durability=, :expiry_policy=,
247
253
  :timeout=, :dynamic=, :distribution_mode=, :copy],
248
254
  :error_class => Qpid::Proton::LinkError)
data/lib/core/transfer.rb CHANGED
@@ -88,6 +88,9 @@ module Qpid::Proton
88
88
  # @return [Transport] The parent connection's transport.
89
89
  def transport() self.connection.transport; end
90
90
 
91
+ # @return [WorkQueue] The parent connection's work-queue.
92
+ def work_queue() self.connection.work_queue; end
93
+
91
94
  # @deprecated internal use only
92
95
  proton_caller :writable?
93
96
  # @deprecated internal use only