qpid_proton 0.18.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/cproton/cproton.c +863 -75
- data/lib/codec/data.rb +589 -815
- data/lib/codec/mapping.rb +142 -126
- data/lib/core/condition.rb +89 -0
- data/lib/core/connection.rb +188 -228
- data/lib/core/connection_driver.rb +202 -0
- data/lib/core/container.rb +366 -0
- data/lib/core/delivery.rb +76 -251
- data/lib/core/disposition.rb +21 -35
- data/lib/core/endpoint.rb +21 -53
- data/lib/core/event.rb +156 -0
- data/lib/core/exceptions.rb +109 -106
- data/lib/core/link.rb +24 -49
- data/lib/core/listener.rb +82 -0
- data/lib/core/message.rb +59 -155
- data/lib/core/messaging_handler.rb +190 -0
- data/lib/core/receiver.rb +38 -7
- data/lib/core/sasl.rb +43 -46
- data/lib/core/sender.rb +55 -32
- data/lib/core/session.rb +58 -58
- data/lib/core/ssl.rb +5 -13
- data/lib/core/ssl_details.rb +1 -2
- data/lib/core/ssl_domain.rb +5 -8
- data/lib/core/terminus.rb +62 -30
- data/lib/core/tracker.rb +45 -0
- data/lib/core/transfer.rb +121 -0
- data/lib/core/transport.rb +62 -97
- data/lib/core/uri.rb +73 -0
- data/lib/core/url.rb +11 -7
- data/lib/handler/adapter.rb +78 -0
- data/lib/handler/messaging_adapter.rb +127 -0
- data/lib/handler/messaging_handler.rb +128 -178
- data/lib/handler/reactor_messaging_adapter.rb +158 -0
- data/lib/messenger/messenger.rb +9 -8
- data/lib/messenger/subscription.rb +1 -2
- data/lib/messenger/tracker.rb +1 -2
- data/lib/messenger/tracker_status.rb +1 -2
- data/lib/qpid_proton.rb +36 -66
- data/lib/reactor/container.rb +40 -234
- data/lib/types/array.rb +73 -130
- data/lib/types/described.rb +2 -44
- data/lib/types/hash.rb +19 -56
- data/lib/types/strings.rb +1 -2
- data/lib/types/type.rb +68 -0
- data/lib/util/{handler.rb → deprecation.rb} +22 -15
- data/lib/util/error_handler.rb +4 -25
- data/lib/util/timeout.rb +1 -2
- data/lib/util/version.rb +1 -2
- data/lib/util/wrapper.rb +58 -38
- metadata +16 -33
- data/lib/core/base_handler.rb +0 -31
- data/lib/core/selectable.rb +0 -130
- data/lib/event/collector.rb +0 -148
- data/lib/event/event.rb +0 -318
- data/lib/event/event_base.rb +0 -91
- data/lib/event/event_type.rb +0 -71
- data/lib/handler/acking.rb +0 -70
- data/lib/handler/c_adaptor.rb +0 -47
- data/lib/handler/c_flow_controller.rb +0 -33
- data/lib/handler/endpoint_state_handler.rb +0 -217
- data/lib/handler/incoming_message_handler.rb +0 -74
- data/lib/handler/outgoing_message_handler.rb +0 -100
- data/lib/handler/wrapped_handler.rb +0 -76
- data/lib/reactor/acceptor.rb +0 -41
- data/lib/reactor/backoff.rb +0 -41
- data/lib/reactor/connector.rb +0 -115
- data/lib/reactor/global_overrides.rb +0 -44
- data/lib/reactor/link_option.rb +0 -90
- data/lib/reactor/reactor.rb +0 -196
- data/lib/reactor/session_per_connection.rb +0 -45
- data/lib/reactor/ssl_config.rb +0 -41
- data/lib/reactor/task.rb +0 -39
- data/lib/reactor/urls.rb +0 -45
- data/lib/util/class_wrapper.rb +0 -54
- data/lib/util/condition.rb +0 -47
- data/lib/util/constants.rb +0 -85
- data/lib/util/engine.rb +0 -82
- data/lib/util/reactor.rb +0 -32
- data/lib/util/swig_helper.rb +0 -114
- data/lib/util/uuid.rb +0 -32
@@ -0,0 +1,202 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
require 'socket'
|
19
|
+
|
20
|
+
module Qpid::Proton
|
21
|
+
|
22
|
+
# Associate an AMQP {Connection} and {Transport} with an {IO}
|
23
|
+
#
|
24
|
+
# - {#read} reads AMQP binary data from the {IO} and generates events
|
25
|
+
# - {#tick} generates timing-related events
|
26
|
+
# - {#event} gets events to be dispatched to {Handler::MessagingHandler}s
|
27
|
+
# - {#write} writes AMQP binary data to the {IO}
|
28
|
+
#
|
29
|
+
# Thread safety: The {ConnectionDriver} is not thread safe but separate
|
30
|
+
# {ConnectionDriver} instances can be processed concurrently. The
|
31
|
+
# {Container} handles multiple connections concurrently in multiple threads.
|
32
|
+
#
|
33
|
+
class ConnectionDriver
|
34
|
+
# Create a {Connection} and {Transport} associated with +io+
|
35
|
+
# @param io [IO] An {IO} or {IO}-like object that responds
|
36
|
+
# to {IO#read_nonblock} and {IO#write_nonblock}
|
37
|
+
def initialize(io)
|
38
|
+
@impl = Cproton.pni_connection_driver or raise NoMemoryError
|
39
|
+
@io = io
|
40
|
+
@rbuf = "" # String for re-usable read buffer
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Connection]
|
44
|
+
def connection()
|
45
|
+
@connection ||= Connection.wrap(Cproton.pni_connection_driver_connection(@impl))
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Transport]
|
49
|
+
def transport()
|
50
|
+
@transport ||= Transport.wrap(Cproton.pni_connection_driver_transport(@impl))
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [IO] Allows ConnectionDriver to be passed directly to {IO#select}
|
54
|
+
def to_io() @io; end
|
55
|
+
|
56
|
+
# @return [Bool] True if the driver can read more data
|
57
|
+
def can_read?() Cproton.pni_connection_driver_read_size(@impl) > 0; end
|
58
|
+
|
59
|
+
# @return [Bool] True if the driver has data to write
|
60
|
+
def can_write?() Cproton.pni_connection_driver_write_size(@impl) > 0; end
|
61
|
+
|
62
|
+
# True if the ConnectionDriver has nothing left to do: both sides of the
|
63
|
+
# transport are closed and there are no events to dispatch.
|
64
|
+
def finished?() Cproton.pn_connection_driver_finished(@impl); end
|
65
|
+
|
66
|
+
# Get the next event to dispatch, nil if no events available
|
67
|
+
def event()
|
68
|
+
e = Cproton.pn_connection_driver_next_event(@impl)
|
69
|
+
Event.new(e) if e
|
70
|
+
end
|
71
|
+
|
72
|
+
# True if {#event} will return non-nil
|
73
|
+
def event?() Cproton.pn_connection_driver_has_event(@impl); end
|
74
|
+
|
75
|
+
# Iterator for all available events
|
76
|
+
def each_event()
|
77
|
+
while e = event
|
78
|
+
yield e
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Non-blocking read from {#io}, generate events for {#event}
|
83
|
+
# IO errors are returned as transport errors by {#event}, not raised
|
84
|
+
def read
|
85
|
+
size = Cproton.pni_connection_driver_read_size(@impl)
|
86
|
+
return if size <= 0
|
87
|
+
@io.read_nonblock(size, @rbuf) # Use the same string rbuf for reading each time
|
88
|
+
Cproton.pni_connection_driver_read_copy(@impl, @rbuf) unless @rbuf.empty?
|
89
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
|
90
|
+
# Try again later.
|
91
|
+
rescue EOFError # EOF is not an error
|
92
|
+
close_read
|
93
|
+
rescue IOError, SystemCallError => e
|
94
|
+
close e
|
95
|
+
end
|
96
|
+
|
97
|
+
# Non-blocking write to {#io}
|
98
|
+
# IO errors are returned as transport errors by {#event}, not raised
|
99
|
+
def write
|
100
|
+
data = Cproton.pn_connection_driver_write_buffer(@impl)
|
101
|
+
return unless data && data.size > 0
|
102
|
+
n = @io.write_nonblock(data)
|
103
|
+
Cproton.pn_connection_driver_write_done(@impl, n) if n > 0
|
104
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
|
105
|
+
# Try again later.
|
106
|
+
rescue IOError, SystemCallError => e
|
107
|
+
close e
|
108
|
+
end
|
109
|
+
|
110
|
+
# Handle time-related work, for example idle-timeout events.
|
111
|
+
# May generate events for {#event} and change {#can_read?}, {#can_write?}
|
112
|
+
#
|
113
|
+
# @param [Time] now the current time, defaults to {Time#now}.
|
114
|
+
#
|
115
|
+
# @return [Time] time of the next scheduled event, or nil if there are no
|
116
|
+
# scheduled events. If non-nil you must call {#tick} again no later than
|
117
|
+
# this time.
|
118
|
+
def tick(now=Time.now)
|
119
|
+
transport = Cproton.pni_connection_driver_transport(@impl)
|
120
|
+
ms = Cproton.pn_transport_tick(transport, (now.to_r * 1000).to_i)
|
121
|
+
return ms.zero? ? nil : Time.at(ms.to_r / 1000);
|
122
|
+
end
|
123
|
+
|
124
|
+
# Disconnect the write side of the transport, *without* sending an AMQP
|
125
|
+
# close frame. To close politely, you should use {Connection#close}, the
|
126
|
+
# transport will close itself once the protocol close is complete.
|
127
|
+
#
|
128
|
+
def close_write error=nil
|
129
|
+
set_error error
|
130
|
+
Cproton.pn_connection_driver_write_close(@impl)
|
131
|
+
@io.close_write rescue nil # Allow double-close
|
132
|
+
end
|
133
|
+
|
134
|
+
# Is the read side of the driver closed?
|
135
|
+
def read_closed?() Cproton.pn_connection_driver_read_closed(@impl); end
|
136
|
+
|
137
|
+
# Is the write side of the driver closed?
|
138
|
+
def write_closed?() Cproton.pn_connection_driver_read_closed(@impl); end
|
139
|
+
|
140
|
+
# Disconnect the read side of the transport, without waiting for an AMQP
|
141
|
+
# close frame. See comments on {#close_write}
|
142
|
+
def close_read error=nil
|
143
|
+
set_error error
|
144
|
+
Cproton.pn_connection_driver_read_close(@impl)
|
145
|
+
@io.close_read rescue nil # Allow double-close
|
146
|
+
end
|
147
|
+
|
148
|
+
# Disconnect both sides of the transport sending/waiting for AMQP close
|
149
|
+
# frames. See comments on {#close_write}
|
150
|
+
def close error=nil
|
151
|
+
close_write error
|
152
|
+
close_read
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def set_error err
|
158
|
+
transport.condition ||= Condition.convert(err, "proton:io") if err
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# A {ConnectionDriver} that feeds raw proton events to a handler.
|
163
|
+
class HandlerDriver < ConnectionDriver
|
164
|
+
# Combine an {IO} with a handler and provide
|
165
|
+
# a simplified way to run the driver via {#process}
|
166
|
+
#
|
167
|
+
# @param io [IO]
|
168
|
+
# @param handler [Handler::MessagingHandler] to receive raw events in {#dispatch} and {#process}
|
169
|
+
def initialize(io, handler)
|
170
|
+
super(io)
|
171
|
+
@handler = handler
|
172
|
+
@adapter = Handler::Adapter.adapt(handler)
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [MessagingHandler] The handler dispatched to by {#process}
|
176
|
+
attr_reader :handler
|
177
|
+
|
178
|
+
# Dispatch all available raw proton events from {#event} to {#handler}
|
179
|
+
def dispatch()
|
180
|
+
each_event do |e|
|
181
|
+
case e.method # Events that affect the driver
|
182
|
+
when :on_transport_tail_closed then close_read
|
183
|
+
when :on_transport_head_closed then close_write
|
184
|
+
end
|
185
|
+
e.dispatch @adapter
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Do {#read}, {#tick}, {#write} and {#dispatch} without blocking.
|
190
|
+
# @param [Time] now the current time
|
191
|
+
# @return [Time] Latest time to call {#process} again for scheduled events,
|
192
|
+
# or nil if there are no scheduled events
|
193
|
+
def process(now=Time.now)
|
194
|
+
read
|
195
|
+
next_tick = tick(now)
|
196
|
+
dispatch # Generate data for write
|
197
|
+
write
|
198
|
+
dispatch # Consume events generated by write
|
199
|
+
return next_tick
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,366 @@
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
3
|
+
# distributed with this work for additional information
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
6
|
+
# "License"); you may not use this file except in compliance
|
7
|
+
# with the License. You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
|
19
|
+
require 'thread'
|
20
|
+
require 'set'
|
21
|
+
require_relative 'listener'
|
22
|
+
|
23
|
+
module Qpid::Proton
|
24
|
+
# An AMQP container manages a set of {Listener}s and {Connection}s which
|
25
|
+
# contain {#Sender} and {#Receiver} links to transfer messages. Usually, each
|
26
|
+
# AMQP client or server process has a single container for all of its
|
27
|
+
# connections and links.
|
28
|
+
#
|
29
|
+
# One or more threads can call {#run}, events generated by all the listeners and
|
30
|
+
# connections will be dispatched in the {#run} threads.
|
31
|
+
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
|
89
|
+
|
90
|
+
# Error raised if the container is used after {#stop} has been called.
|
91
|
+
class StoppedError < RuntimeError
|
92
|
+
def initialize(*args) super("container has been stopped"); end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Create a new Container
|
96
|
+
# @overload initialize(id=nil)
|
97
|
+
# @param id [String,Symbol] A unique ID for this container, use random UUID if nil.
|
98
|
+
#
|
99
|
+
# @overload initialize(handler=nil, id=nil)
|
100
|
+
# @param id [String,Symbol] A unique ID for this container, use random UUID if nil.
|
101
|
+
# @param handler [MessagingHandler] Optional default handler for connections
|
102
|
+
# that do not have their own handler (see {#connect} and {#listen})
|
103
|
+
#
|
104
|
+
# *Note*: For multi-threaded code, it is recommended to use a separate
|
105
|
+
# handler instance for each connection, as a shared handler may be called
|
106
|
+
# concurrently.
|
107
|
+
#
|
108
|
+
def initialize(*args)
|
109
|
+
case args.size
|
110
|
+
when 2 then @handler, @id = args
|
111
|
+
when 1 then
|
112
|
+
@id = String.try_convert(args[0]) || (args[0].to_s if args[0].is_a? Symbol)
|
113
|
+
@handler = args[0] unless @id
|
114
|
+
else raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..2"
|
115
|
+
end
|
116
|
+
# Use an empty messaging adapter to give default behaviour if there's no global handler.
|
117
|
+
@adapter = Handler::Adapter.adapt(@handler) || Handler::MessagingAdapter.new(nil)
|
118
|
+
@id = (@id || SecureRandom.uuid).freeze
|
119
|
+
|
120
|
+
# Implementation note:
|
121
|
+
#
|
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
|
125
|
+
# - nil on the @work queue makes a #run thread exit
|
126
|
+
|
127
|
+
@work = Queue.new
|
128
|
+
@work << :start << self # Issue start and start start selecting
|
129
|
+
@wake = IO.pipe # Wakes #run thread in IO.select
|
130
|
+
@auto_stop = true # Stop when @active drops to 0
|
131
|
+
|
132
|
+
# Following instance variables protected by lock
|
133
|
+
@lock = Mutex.new
|
134
|
+
@active = 0 # All active tasks, in @selectable, @work or being processed
|
135
|
+
@selectable = Set.new # Tasks ready to block in IO.select
|
136
|
+
@running = 0 # Count of #run threads
|
137
|
+
@stopped = false # #stop called
|
138
|
+
@stop_err = nil # Optional error to pass to tasks, from #stop
|
139
|
+
end
|
140
|
+
|
141
|
+
# @return [MessagingHandler] The container-wide handler
|
142
|
+
attr_reader :handler
|
143
|
+
|
144
|
+
# @return [String] unique identifier for this container
|
145
|
+
attr_reader :id
|
146
|
+
|
147
|
+
# Auto-stop flag.
|
148
|
+
#
|
149
|
+
# True (the default) means that the container will stop automatically, as if {#stop}
|
150
|
+
# had been called, when the last listener or connection closes.
|
151
|
+
#
|
152
|
+
# False means {#run} will not return unless {#stop} is called.
|
153
|
+
#
|
154
|
+
# @return [Bool] auto-stop state
|
155
|
+
attr_accessor :auto_stop
|
156
|
+
|
157
|
+
# True if the container has been stopped and can no longer be used.
|
158
|
+
# @return [Bool] stopped state
|
159
|
+
attr_accessor :stopped
|
160
|
+
|
161
|
+
# Number of threads in {#run}
|
162
|
+
# @return [Bool] {#run} thread count
|
163
|
+
def running; @lock.synchronize { @running }; end
|
164
|
+
|
165
|
+
# Open an AMQP connection.
|
166
|
+
#
|
167
|
+
# @param url [String, URI] Open a {TCPSocket} to url.host, url.port.
|
168
|
+
# url.scheme must be "amqp" or "amqps", url.scheme.nil? is treated as "amqp"
|
169
|
+
# url.user, url.password are used as defaults if opts[:user], opts[:password] are nil
|
170
|
+
# @option (see Connection#open)
|
171
|
+
# @return [Connection] The new AMQP connection
|
172
|
+
def connect(url, opts=nil)
|
173
|
+
not_stopped
|
174
|
+
url = Qpid::Proton::uri url
|
175
|
+
opts ||= {}
|
176
|
+
if url.user || url.password
|
177
|
+
opts[:user] ||= url.user
|
178
|
+
opts[:password] ||= url.password
|
179
|
+
end
|
180
|
+
opts[:ssl_domain] ||= SSLDomain.new(SSLDomain::MODE_CLIENT) if url.scheme == "amqps"
|
181
|
+
connect_io(TCPSocket.new(url.host, url.port), opts)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Open an AMQP protocol connection on an existing {IO} object
|
185
|
+
# @param io [IO] An existing {IO} object, e.g. a {TCPSocket}
|
186
|
+
# @option (see Connection#open)
|
187
|
+
def connect_io(io, opts=nil)
|
188
|
+
not_stopped
|
189
|
+
cd = connection_driver(io, opts)
|
190
|
+
cd.connection.open()
|
191
|
+
add(cd)
|
192
|
+
cd.connection
|
193
|
+
end
|
194
|
+
|
195
|
+
# Listen for incoming AMQP connections
|
196
|
+
#
|
197
|
+
# @param url [String,URI] Listen on host:port of the AMQP URL
|
198
|
+
# @param handler [Listener::Handler] A {Listener::Handler} object that will be called
|
199
|
+
# with events for this listener and can generate a new set of options for each one.
|
200
|
+
# @return [Listener] The AMQP listener.
|
201
|
+
#
|
202
|
+
def listen(url, handler=Listener::Handler.new)
|
203
|
+
not_stopped
|
204
|
+
url = Qpid::Proton::uri url
|
205
|
+
# TODO aconway 2017-11-01: amqps, SSL
|
206
|
+
listen_io(TCPServer.new(url.host, url.port), handler)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Listen for incoming AMQP connections on an existing server socket.
|
210
|
+
# @param io A server socket, for example a {TCPServer}
|
211
|
+
# @param handler [Listener::Handler] Handler for events from this listener
|
212
|
+
#
|
213
|
+
def listen_io(io, handler=Listener::Handler.new)
|
214
|
+
not_stopped
|
215
|
+
l = ListenTask.new(io, handler, self)
|
216
|
+
add(l)
|
217
|
+
l
|
218
|
+
end
|
219
|
+
|
220
|
+
# Run the container: wait for IO activity, dispatch events to handlers.
|
221
|
+
#
|
222
|
+
# More than one thread can call {#run} concurrently, the container will use
|
223
|
+
# all the {#run} threads as a thread pool. Calls to
|
224
|
+
# {Handler::MessagingHandler} methods are serialized for each connection or
|
225
|
+
# listener, even if the container has multiple threads.
|
226
|
+
#
|
227
|
+
def run
|
228
|
+
@lock.synchronize do
|
229
|
+
@running += 1 # Note: ensure clause below will decrement @running
|
230
|
+
raise StoppedError if @stopped
|
231
|
+
end
|
232
|
+
while task = @work.pop
|
233
|
+
case task
|
234
|
+
|
235
|
+
when :start
|
236
|
+
@adapter.on_container_start(self) if @adapter.respond_to? :on_container_start
|
237
|
+
|
238
|
+
when Container
|
239
|
+
r, w = [@wake[0]], []
|
240
|
+
@lock.synchronize do
|
241
|
+
@selectable.each do |s|
|
242
|
+
r << s if s.send :can_read?
|
243
|
+
w << s if s.send :can_write?
|
244
|
+
end
|
245
|
+
end
|
246
|
+
r, w = IO.select(r, w)
|
247
|
+
selected = Set.new(r).merge(w)
|
248
|
+
drain_wake if selected.delete?(@wake[0])
|
249
|
+
stop_select = nil
|
250
|
+
@lock.synchronize do
|
251
|
+
if stop_select = @stopped # close everything
|
252
|
+
selected += @selectable
|
253
|
+
selected.each { |s| s.close @stop_err }
|
254
|
+
@wake.each { |fd| fd.close() }
|
255
|
+
end
|
256
|
+
@selectable -= selected # Remove selected tasks
|
257
|
+
end
|
258
|
+
selected.each { |s| @work << s } # Queue up tasks needing #process
|
259
|
+
@work << self unless stop_select
|
260
|
+
|
261
|
+
when ConnectionTask then
|
262
|
+
task.process
|
263
|
+
rearm task
|
264
|
+
|
265
|
+
when ListenTask then
|
266
|
+
io, opts = task.process
|
267
|
+
add(connection_driver(io, opts, true)) if io
|
268
|
+
rearm task
|
269
|
+
end
|
270
|
+
# TODO aconway 2017-10-26: scheduled tasks, heartbeats
|
271
|
+
end
|
272
|
+
ensure
|
273
|
+
@lock.synchronize do
|
274
|
+
if (@running -= 1) > 0
|
275
|
+
work_wake nil # Signal the next thread
|
276
|
+
else
|
277
|
+
@adapter.on_container_stop(self) if @adapter.respond_to? :on_container_stop
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Stop the container.
|
283
|
+
#
|
284
|
+
# Close all listeners and abort all connections without doing AMQP protocol close.
|
285
|
+
#
|
286
|
+
# {#stop} returns immediately, calls to {#run} will return when all activity
|
287
|
+
# is finished.
|
288
|
+
#
|
289
|
+
# The container can no longer be used, using a stopped container raises
|
290
|
+
# {StoppedError} on attempting. Create a new container if you want to
|
291
|
+
# resume activity.
|
292
|
+
#
|
293
|
+
# @param error [Condition] Optional transport/listener error condition
|
294
|
+
#
|
295
|
+
def stop(error=nil)
|
296
|
+
@lock.synchronize do
|
297
|
+
raise StoppedError if @stopped
|
298
|
+
@stopped = true
|
299
|
+
@stop_err = Condition.convert(error)
|
300
|
+
check_stop_lh
|
301
|
+
# NOTE: @stopped =>
|
302
|
+
# - no new run threads can join
|
303
|
+
# - no more select calls after next wakeup
|
304
|
+
# - once @active == 0, all threads will be stopped with nil
|
305
|
+
end
|
306
|
+
wake
|
307
|
+
end
|
308
|
+
|
309
|
+
protected
|
310
|
+
|
311
|
+
def wake; @wake[1].write_nonblock('x') rescue nil; end
|
312
|
+
|
313
|
+
# Normally if we add work we need to set a wakeup to ensure a single #run
|
314
|
+
# thread doesn't get stuck in select while there is other work on the queue.
|
315
|
+
def work_wake(task) @work << task; wake; end
|
316
|
+
|
317
|
+
def drain_wake
|
318
|
+
begin
|
319
|
+
@wake[0].read_nonblock(256) while true
|
320
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def connection_driver(io, opts=nil, server=false)
|
325
|
+
opts ||= {}
|
326
|
+
opts[:container] = self
|
327
|
+
opts[:handler] ||= @adapter
|
328
|
+
ConnectionTask.new(self, io, opts, server)
|
329
|
+
end
|
330
|
+
|
331
|
+
# All new tasks are added here
|
332
|
+
def add task
|
333
|
+
@lock.synchronize do
|
334
|
+
@active += 1
|
335
|
+
task.close @stop_err if @stopped
|
336
|
+
end
|
337
|
+
work_wake task
|
338
|
+
end
|
339
|
+
|
340
|
+
def rearm task
|
341
|
+
@lock.synchronize do
|
342
|
+
if task.finished?
|
343
|
+
@active -= 1
|
344
|
+
check_stop_lh
|
345
|
+
elsif @stopped
|
346
|
+
task.close @stop_err
|
347
|
+
work_wake task
|
348
|
+
else
|
349
|
+
@selectable << task
|
350
|
+
end
|
351
|
+
end
|
352
|
+
wake
|
353
|
+
end
|
354
|
+
|
355
|
+
def check_stop_lh
|
356
|
+
if @active.zero? && (@auto_stop || @stopped)
|
357
|
+
@stopped = true
|
358
|
+
work_wake nil # Signal threads to stop
|
359
|
+
true
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def not_stopped; raise StoppedError if @lock.synchronize { @stopped }; end
|
364
|
+
|
365
|
+
end
|
366
|
+
end
|