qpid_proton 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cproton/cproton.c +863 -75
  3. data/lib/codec/data.rb +589 -815
  4. data/lib/codec/mapping.rb +142 -126
  5. data/lib/core/condition.rb +89 -0
  6. data/lib/core/connection.rb +188 -228
  7. data/lib/core/connection_driver.rb +202 -0
  8. data/lib/core/container.rb +366 -0
  9. data/lib/core/delivery.rb +76 -251
  10. data/lib/core/disposition.rb +21 -35
  11. data/lib/core/endpoint.rb +21 -53
  12. data/lib/core/event.rb +156 -0
  13. data/lib/core/exceptions.rb +109 -106
  14. data/lib/core/link.rb +24 -49
  15. data/lib/core/listener.rb +82 -0
  16. data/lib/core/message.rb +59 -155
  17. data/lib/core/messaging_handler.rb +190 -0
  18. data/lib/core/receiver.rb +38 -7
  19. data/lib/core/sasl.rb +43 -46
  20. data/lib/core/sender.rb +55 -32
  21. data/lib/core/session.rb +58 -58
  22. data/lib/core/ssl.rb +5 -13
  23. data/lib/core/ssl_details.rb +1 -2
  24. data/lib/core/ssl_domain.rb +5 -8
  25. data/lib/core/terminus.rb +62 -30
  26. data/lib/core/tracker.rb +45 -0
  27. data/lib/core/transfer.rb +121 -0
  28. data/lib/core/transport.rb +62 -97
  29. data/lib/core/uri.rb +73 -0
  30. data/lib/core/url.rb +11 -7
  31. data/lib/handler/adapter.rb +78 -0
  32. data/lib/handler/messaging_adapter.rb +127 -0
  33. data/lib/handler/messaging_handler.rb +128 -178
  34. data/lib/handler/reactor_messaging_adapter.rb +158 -0
  35. data/lib/messenger/messenger.rb +9 -8
  36. data/lib/messenger/subscription.rb +1 -2
  37. data/lib/messenger/tracker.rb +1 -2
  38. data/lib/messenger/tracker_status.rb +1 -2
  39. data/lib/qpid_proton.rb +36 -66
  40. data/lib/reactor/container.rb +40 -234
  41. data/lib/types/array.rb +73 -130
  42. data/lib/types/described.rb +2 -44
  43. data/lib/types/hash.rb +19 -56
  44. data/lib/types/strings.rb +1 -2
  45. data/lib/types/type.rb +68 -0
  46. data/lib/util/{handler.rb → deprecation.rb} +22 -15
  47. data/lib/util/error_handler.rb +4 -25
  48. data/lib/util/timeout.rb +1 -2
  49. data/lib/util/version.rb +1 -2
  50. data/lib/util/wrapper.rb +58 -38
  51. metadata +16 -33
  52. data/lib/core/base_handler.rb +0 -31
  53. data/lib/core/selectable.rb +0 -130
  54. data/lib/event/collector.rb +0 -148
  55. data/lib/event/event.rb +0 -318
  56. data/lib/event/event_base.rb +0 -91
  57. data/lib/event/event_type.rb +0 -71
  58. data/lib/handler/acking.rb +0 -70
  59. data/lib/handler/c_adaptor.rb +0 -47
  60. data/lib/handler/c_flow_controller.rb +0 -33
  61. data/lib/handler/endpoint_state_handler.rb +0 -217
  62. data/lib/handler/incoming_message_handler.rb +0 -74
  63. data/lib/handler/outgoing_message_handler.rb +0 -100
  64. data/lib/handler/wrapped_handler.rb +0 -76
  65. data/lib/reactor/acceptor.rb +0 -41
  66. data/lib/reactor/backoff.rb +0 -41
  67. data/lib/reactor/connector.rb +0 -115
  68. data/lib/reactor/global_overrides.rb +0 -44
  69. data/lib/reactor/link_option.rb +0 -90
  70. data/lib/reactor/reactor.rb +0 -196
  71. data/lib/reactor/session_per_connection.rb +0 -45
  72. data/lib/reactor/ssl_config.rb +0 -41
  73. data/lib/reactor/task.rb +0 -39
  74. data/lib/reactor/urls.rb +0 -45
  75. data/lib/util/class_wrapper.rb +0 -54
  76. data/lib/util/condition.rb +0 -47
  77. data/lib/util/constants.rb +0 -85
  78. data/lib/util/engine.rb +0 -82
  79. data/lib/util/reactor.rb +0 -32
  80. data/lib/util/swig_helper.rb +0 -114
  81. 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