raktr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE.md +29 -0
  4. data/README.md +77 -0
  5. data/Rakefile +53 -0
  6. data/lib/raktr/connection/callbacks.rb +71 -0
  7. data/lib/raktr/connection/error.rb +120 -0
  8. data/lib/raktr/connection/peer_info.rb +90 -0
  9. data/lib/raktr/connection/tls.rb +164 -0
  10. data/lib/raktr/connection.rb +339 -0
  11. data/lib/raktr/global.rb +24 -0
  12. data/lib/raktr/iterator.rb +249 -0
  13. data/lib/raktr/queue.rb +89 -0
  14. data/lib/raktr/tasks/base.rb +57 -0
  15. data/lib/raktr/tasks/delayed.rb +33 -0
  16. data/lib/raktr/tasks/one_off.rb +30 -0
  17. data/lib/raktr/tasks/periodic.rb +58 -0
  18. data/lib/raktr/tasks/persistent.rb +29 -0
  19. data/lib/raktr/tasks.rb +105 -0
  20. data/lib/raktr/version.rb +13 -0
  21. data/lib/raktr.rb +707 -0
  22. data/spec/raktr/connection/tls_spec.rb +348 -0
  23. data/spec/raktr/connection_spec.rb +74 -0
  24. data/spec/raktr/iterator_spec.rb +203 -0
  25. data/spec/raktr/queue_spec.rb +91 -0
  26. data/spec/raktr/tasks/base.rb +8 -0
  27. data/spec/raktr/tasks/delayed_spec.rb +71 -0
  28. data/spec/raktr/tasks/one_off_spec.rb +66 -0
  29. data/spec/raktr/tasks/periodic_spec.rb +57 -0
  30. data/spec/raktr/tasks/persistent_spec.rb +54 -0
  31. data/spec/raktr/tasks_spec.rb +155 -0
  32. data/spec/raktr_spec.rb +20 -0
  33. data/spec/raktr_tls_spec.rb +20 -0
  34. data/spec/spec_helper.rb +17 -0
  35. data/spec/support/fixtures/handlers/echo_client.rb +34 -0
  36. data/spec/support/fixtures/handlers/echo_client_tls.rb +10 -0
  37. data/spec/support/fixtures/handlers/echo_server.rb +12 -0
  38. data/spec/support/fixtures/handlers/echo_server_tls.rb +8 -0
  39. data/spec/support/fixtures/pems/cacert.pem +37 -0
  40. data/spec/support/fixtures/pems/client/cert.pem +37 -0
  41. data/spec/support/fixtures/pems/client/foo-cert.pem +39 -0
  42. data/spec/support/fixtures/pems/client/foo-key.pem +51 -0
  43. data/spec/support/fixtures/pems/client/key.pem +51 -0
  44. data/spec/support/fixtures/pems/server/cert.pem +37 -0
  45. data/spec/support/fixtures/pems/server/key.pem +51 -0
  46. data/spec/support/helpers/paths.rb +23 -0
  47. data/spec/support/helpers/utilities.rb +135 -0
  48. data/spec/support/lib/server_option_parser.rb +29 -0
  49. data/spec/support/lib/servers/runner.rb +13 -0
  50. data/spec/support/lib/servers.rb +133 -0
  51. data/spec/support/servers/echo.rb +14 -0
  52. data/spec/support/servers/echo_tls.rb +22 -0
  53. data/spec/support/servers/echo_unix.rb +14 -0
  54. data/spec/support/servers/echo_unix_tls.rb +22 -0
  55. data/spec/support/shared/connection.rb +696 -0
  56. data/spec/support/shared/raktr.rb +834 -0
  57. data/spec/support/shared/task.rb +21 -0
  58. metadata +140 -0
@@ -0,0 +1,339 @@
1
+ =begin
2
+
3
+ This file is part of the Raktr project and may be subject to
4
+ redistribution and commercial restrictions. Please see the Raktr
5
+ web site for more information on licensing and terms of use.
6
+
7
+ =end
8
+
9
+ require_relative 'connection/error'
10
+ require_relative 'connection/callbacks'
11
+ require_relative 'connection/peer_info'
12
+ require_relative 'connection/tls'
13
+
14
+ class Raktr
15
+
16
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
17
+ class Connection
18
+ include Callbacks
19
+
20
+ # Maximum amount of data to be written or read at a time.
21
+ #
22
+ # We set this to the same max block size as the OpenSSL buffers because more
23
+ # than this tends to cause SSL errors and broken #select behavior --
24
+ # 1024 * 16 at the time of writing.
25
+ BLOCK_SIZE = OpenSSL::Buffering::BLOCK_SIZE
26
+
27
+ # @return [Socket]
28
+ # Ruby `Socket` associated with this connection.
29
+ attr_reader :socket
30
+
31
+ # @return [Reactor]
32
+ # Reactor associated with this connection.
33
+ attr_accessor :raktr
34
+
35
+ # @return [Symbol]
36
+ # `:client` or `:server`
37
+ attr_reader :role
38
+
39
+ # @return [Bool, nil]
40
+ # `true` when using a UNIX-domain socket, `nil` if no {#socket} is
41
+ # available, `false` otherwise.
42
+ def unix?
43
+ return @is_unix if !@is_unix.nil?
44
+ return if !to_io
45
+ return false if !Raktr.supports_unix_sockets?
46
+
47
+ @is_unix = to_io.is_a?( UNIXServer ) || to_io.is_a?( UNIXSocket )
48
+ end
49
+
50
+ # @return [Bool]
51
+ # `true` when using an Internet socket, `nil` if no {#socket} is
52
+ # available, `false` otherwise.
53
+ def inet?
54
+ return @is_inet if !@is_inet.nil?
55
+ return if !to_io
56
+
57
+ @is_inet = to_io.is_a?( TCPServer ) || to_io.is_a?( TCPSocket ) || to_io.is_a?( Socket )
58
+ end
59
+
60
+ # @return [IO, nil]
61
+ # IO stream or `nil` if no {#socket} is available.
62
+ def to_io
63
+ return if !@socket
64
+ @socket.to_io
65
+ end
66
+
67
+ # @return [Bool]
68
+ # `true` if the connection is a server listener.
69
+ def listener?
70
+ return @is_listener if !@is_listener.nil?
71
+ return if !to_io
72
+
73
+ @is_listener = to_io.is_a?( TCPServer ) || (unix? && to_io.is_a?( UNIXServer ))
74
+ end
75
+
76
+ # @note The data will be buffered and sent in future {Reactor} ticks.
77
+ #
78
+ # @param [String] data
79
+ # Data to send to the peer.
80
+ def write( data )
81
+ @raktr.schedule do
82
+ write_buffer << data
83
+ end
84
+ end
85
+
86
+ # @return [Bool]
87
+ # `true` if the connection is {Reactor#attached?} to a {#raktr},
88
+ # `false` otherwise.
89
+ def attached?
90
+ @raktr && @raktr.attached?( self )
91
+ end
92
+
93
+ # @return [Bool]
94
+ # `true` if the connection is not {Reactor#attached?} to a {#raktr},
95
+ # `false` otherwise.
96
+ def detached?
97
+ !attached?
98
+ end
99
+
100
+ # @note Will first detach if already {#attached?}.
101
+ # @note Sets {#raktr}.
102
+ # @note Calls {#on_attach}.
103
+ #
104
+ # @param [Reactor] reactor
105
+ # {Reactor} to which to attach {Reactor#attach}.
106
+ #
107
+ # @return [Bool]
108
+ # `true` if the connection was attached, `nil` if the connection was
109
+ # already attached.
110
+ def attach( raktr )
111
+ return if raktr.attached?( self )
112
+ detach if attached?
113
+
114
+ raktr.attach self
115
+
116
+ true
117
+ end
118
+
119
+ # @note Removes {#raktr}.
120
+ # @note Calls {#on_detach}.
121
+ #
122
+ # {Reactor#detach Detaches} `self` from the {#raktr}.
123
+ #
124
+ # @return [Bool]
125
+ # `true` if the connection was detached, `nil` if the connection was
126
+ # already detached.
127
+ def detach
128
+ return if detached?
129
+
130
+ @raktr.detach self
131
+
132
+ true
133
+ end
134
+
135
+ # @note Will not call {#on_close}.
136
+ #
137
+ # Closes the connection and {#detach detaches} it from the {Reactor}.
138
+ def close_without_callback
139
+ return if closed?
140
+ @closed = true
141
+
142
+ if listener? && unix? && (path = to_io.path) && File.exist?( path )
143
+ File.delete( path )
144
+ end
145
+
146
+ if @socket
147
+ @socket.close rescue nil
148
+ end
149
+
150
+ detach
151
+
152
+ nil
153
+ end
154
+
155
+ # @return [Bool]
156
+ # `true` if the connection has been {#close closed}, `false` otherwise.
157
+ def closed?
158
+ !!@closed
159
+ end
160
+
161
+ # @return [Bool]
162
+ # `true` if the connection has {#write outgoing data} that have not
163
+ # yet been {#write written}, `false` otherwise.
164
+ def has_outgoing_data?
165
+ !write_buffer.empty?
166
+ end
167
+
168
+ # @note Will call {#on_close} right before closing the socket and detaching
169
+ # from the Reactor.
170
+ #
171
+ # Closes the connection and {Reactor#detach detaches} it from the {Reactor}.
172
+ #
173
+ # @param [Exception] reason
174
+ # Reason for the close.
175
+ def close( reason = nil )
176
+ return if closed?
177
+
178
+ on_close reason
179
+ close_without_callback
180
+ nil
181
+ end
182
+
183
+ # Accepts a new client connection.
184
+ #
185
+ # @return [Connection, nil]
186
+ # New connection or `nil` if the socket isn't ready to accept new
187
+ # connections yet.
188
+ #
189
+ # @private
190
+ def accept
191
+ return if !(accepted = socket_accept)
192
+
193
+ connection = @server_handler.call
194
+ connection.configure socket: accepted, role: :server
195
+ @raktr.attach connection
196
+ connection
197
+ end
198
+
199
+ # @param [Socket] socket
200
+ # Ruby `Socket` associated with this connection.
201
+ # @param [Symbol] role
202
+ # `:server` or `:client`.
203
+ # @param [Block] server_handler
204
+ # Block that generates a handler as specified in {Reactor#listen}.
205
+ #
206
+ # @private
207
+ def configure( options = {} )
208
+ @socket = options[:socket]
209
+ @role = options[:role]
210
+ @host = options[:host]
211
+ @port = options[:port]
212
+ @server_handler = options[:server_handler]
213
+
214
+ # If we're a server without a handler then we're an accepted connection.
215
+ if unix? || role == :server
216
+ @connected = true
217
+ on_connect
218
+ end
219
+
220
+ nil
221
+ end
222
+
223
+ def connected?
224
+ !!@connected
225
+ end
226
+
227
+ # @private
228
+ def _connect
229
+ return true if unix? || connected?
230
+
231
+ begin
232
+ Error.translate do
233
+ socket.connect_nonblock( Socket.sockaddr_in( @port, @host ) )
234
+ end
235
+ # Already connected. :)
236
+ rescue Errno::EISCONN, Errno::EALREADY
237
+ end
238
+
239
+ @connected = true
240
+ on_connect
241
+
242
+ true
243
+ rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
244
+ rescue Error => e
245
+ close e
246
+ end
247
+
248
+ # @note If this is a server {#listener?} it will delegate to {#accept}.
249
+ # @note If this is a normal socket it will read {BLOCK_SIZE} amount of data.
250
+ # and pass it to {#on_read}.
251
+ #
252
+ # Processes a `read` event for this connection.
253
+ #
254
+ # @private
255
+ def _read
256
+ return _connect if !listener? && !connected?
257
+ return accept if listener?
258
+
259
+ Error.translate do
260
+ on_read @socket.read_nonblock( BLOCK_SIZE )
261
+ end
262
+
263
+ # Not ready to read or write yet, we'll catch it on future Reactor ticks.
264
+ rescue IO::WaitReadable, IO::WaitWritable
265
+ rescue Error => e
266
+ close e
267
+ end
268
+
269
+ # @note Will call {#on_write} every time any of the buffer is consumed,
270
+ # can be multiple times when performing partial writes.
271
+ # @note Will call {#on_flush} once all of the buffer has been consumed.
272
+ #
273
+ # Processes a `write` event for this connection.
274
+ #
275
+ # Consumes and writes {BLOCK_SIZE} amount of data from the the beginning of
276
+ # the {#write} buffer to the socket.
277
+ #
278
+ # @return [Integer]
279
+ # Amount of the buffer consumed.
280
+ #
281
+ # @private
282
+ def _write
283
+ return _connect if !connected?
284
+
285
+ chunk = write_buffer.byteslice( 0, BLOCK_SIZE )
286
+ total_written = 0
287
+
288
+ begin
289
+ Error.translate do
290
+ # Send out the chunk, **all** of it, or at least try to.
291
+ loop do
292
+ total_written += written = @socket.write_nonblock( chunk )
293
+ @write_buffer = @write_buffer.byteslice( written..-1 )
294
+
295
+ # Call #on_write every time any of the buffer is consumed.
296
+ on_write
297
+
298
+ break if written == chunk.bytesize
299
+ chunk = chunk.byteslice( written..-1 )
300
+ end
301
+ end
302
+
303
+ # Not ready to read or write yet, we'll catch it on future Reactor ticks.
304
+ rescue IO::WaitReadable, IO::WaitWritable
305
+ end
306
+
307
+ if write_buffer.empty?
308
+ @socket.flush
309
+ on_flush
310
+ end
311
+
312
+ total_written
313
+ rescue Error => e
314
+ close e
315
+ end
316
+
317
+ private
318
+
319
+ def write_buffer
320
+ @write_buffer ||= ''
321
+ end
322
+
323
+ # Accepts a new client connection.
324
+ #
325
+ # @return [Socket, nil]
326
+ # New connection or `nil` if the socket isn't ready to accept new
327
+ # connections yet.
328
+ #
329
+ # @private
330
+ def socket_accept
331
+ begin
332
+ @socket.accept_nonblock
333
+ rescue IO::WaitReadable, IO::WaitWritable
334
+ end
335
+ end
336
+
337
+ end
338
+
339
+ end
@@ -0,0 +1,24 @@
1
+ =begin
2
+
3
+ This file is part of the Raktr project and may be subject to
4
+ redistribution and commercial restrictions. Please see the Raktr
5
+ web site for more information on licensing and terms of use.
6
+
7
+ =end
8
+
9
+ require 'singleton'
10
+
11
+ class Raktr
12
+
13
+ # **Do not use directly!**
14
+ #
15
+ # Use the {Reactor} class methods to manage a globally accessible {Reactor}
16
+ # instance.
17
+ #
18
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
19
+ # @private
20
+ class Global < Raktr
21
+ include Singleton
22
+ end
23
+
24
+ end
@@ -0,0 +1,249 @@
1
+
2
+ =begin
3
+
4
+ This file is part of the Raktr project and may be subject to
5
+ redistribution and commercial restrictions. Please see the Raktr
6
+ web site for more information on licensing and terms of use.
7
+
8
+ =end
9
+
10
+ class Raktr
11
+
12
+ # @note Pretty much an `EventMachine::Iterator` rip-off.
13
+ #
14
+ # A simple iterator for concurrent asynchronous work.
15
+ #
16
+ # Unlike Ruby's built-in iterators, the end of the current iteration cycle is
17
+ # signaled manually, instead of happening automatically after the yielded block
18
+ # finishes executing.
19
+ #
20
+ # @example Direct initialization.
21
+ #
22
+ # Iterator.new( reactor, 0..10 ).each { |num, iterator| iterator.next }
23
+ #
24
+ # @example Reactor factory.
25
+ #
26
+ # raktr.create_iterator( 0..10 ).each { |num, iterator| iterator.next }
27
+ #
28
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
29
+ class Iterator
30
+
31
+ # @return [Reactor]
32
+ attr_reader :raktr
33
+
34
+ # @return [Integer]
35
+ attr_reader :concurrency
36
+
37
+ # @example Create a new parallel async iterator with specified concurrency.
38
+ #
39
+ # i = Iterator.new( reactor, 1..100, 10 )
40
+ #
41
+ # @param [Reactor] reactor
42
+ # @param [#to_a] list
43
+ # List to iterate.
44
+ # @param [Integer] concurrency
45
+ # Parallel workers to spawn.
46
+ def initialize( reactor, list, concurrency = 1 )
47
+ raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a)
48
+ raise ArgumentError, 'concurrency must be bigger than zero' unless concurrency > 0
49
+
50
+ @raktr = reactor
51
+ @list = list.to_a.dup
52
+ @concurrency = concurrency
53
+
54
+ @started = false
55
+ @ended = false
56
+ end
57
+
58
+ # Change the concurrency of this iterator. Workers will automatically be
59
+ # spawned or destroyed to accommodate the new concurrency level.
60
+ #
61
+ # @param [Integer] val
62
+ # New concurrency.
63
+ def concurrency=( val )
64
+ old = @concurrency
65
+ @concurrency = val
66
+
67
+ spawn_workers if val > old && @started && !@ended
68
+
69
+ val
70
+ end
71
+
72
+ # @example Iterate over a set of items using the specified block or proc.
73
+ #
74
+ # Iterator.new( reactor, 1..100 ).each do |num, iterator|
75
+ # puts num
76
+ # iterator.next
77
+ # end
78
+ #
79
+ # @example An optional second proc is invoked after the iteration is complete.
80
+ #
81
+ # Iterator.new( reactor, 1..100 ).each(
82
+ # proc { |num, iterator| iterator.next },
83
+ # proc { puts 'all done' }
84
+ # )
85
+ def each( foreach = nil, after = nil, &block )
86
+ raise ArgumentError, 'Proc or Block required for iteration.' unless foreach ||= block
87
+ raise RuntimeError, 'Cannot iterate over an iterator more than once.' if @started or @ended
88
+
89
+ @started = true
90
+ @pending = 0
91
+ @workers = 0
92
+
93
+ all_done = proc do
94
+ after.call if after && @ended && @pending == 0
95
+ end
96
+
97
+ @process_next = proc do
98
+ if @ended || @workers > @concurrency
99
+ @workers -= 1
100
+ else
101
+ if @list.empty?
102
+ @ended = true
103
+ @workers -= 1
104
+
105
+ all_done.call
106
+ else
107
+ item = @list.shift
108
+ @pending += 1
109
+
110
+ is_done = false
111
+ on_done = proc do
112
+ raise RuntimeError, 'Already completed this iteration.' if is_done
113
+ is_done = true
114
+
115
+ @pending -= 1
116
+
117
+ if @ended
118
+ all_done.call
119
+ else
120
+ @raktr.next_tick(&@process_next)
121
+ end
122
+ end
123
+
124
+ class << on_done
125
+ alias :next :call
126
+ end
127
+
128
+ foreach.call(item, on_done)
129
+ end
130
+ end
131
+ end
132
+
133
+ spawn_workers
134
+
135
+ self
136
+ end
137
+
138
+ # @example Collect the results of an asynchronous iteration into an array.
139
+ #
140
+ # Iterator.new( reactor, %w(one two three four), 2 ).map(
141
+ # proc do |string, iterator|
142
+ # iterator.return( string.size )
143
+ # end,
144
+ # proc do |results|
145
+ # p results
146
+ # end
147
+ # )
148
+ #
149
+ # @param [Proc] foreach
150
+ # `Proc` to handle each entry.
151
+ # @param [Proc] after
152
+ # `Proc` to handle the results.
153
+ def map( foreach, after )
154
+ index = 0
155
+
156
+ inject( [],
157
+ proc do |results, item, iter|
158
+ i = index
159
+ index += 1
160
+
161
+ is_done = false
162
+ on_done = proc do |res|
163
+ raise RuntimeError, 'Already returned a value for this iteration.' if is_done
164
+ is_done = true
165
+
166
+ results[i] = res
167
+ iter.return(results)
168
+ end
169
+
170
+ class << on_done
171
+ alias :return :call
172
+ def next
173
+ raise NoMethodError, 'Must call #return on a map iterator.'
174
+ end
175
+ end
176
+
177
+ foreach.call( item, on_done )
178
+ end,
179
+
180
+ proc do |results|
181
+ after.call(results)
182
+ end
183
+ )
184
+ end
185
+
186
+ # @example Inject the results of an asynchronous iteration onto a given object.
187
+ #
188
+ # Iterator.new( reactor, %w(one two three four), 2 ).inject( {},
189
+ # proc do |hash, string, iterator|
190
+ # hash.merge!( string => string.size )
191
+ # iterator.return( hash )
192
+ # end,
193
+ # proc do |results|
194
+ # p results
195
+ # end
196
+ # )
197
+ #
198
+ # @param [Object] object
199
+ # @param [Proc] foreach
200
+ # `Proc` to handle each entry.
201
+ # @param [Proc] after
202
+ # `Proc` to handle the results.
203
+ def inject( object, foreach, after )
204
+ each(
205
+ proc do |item, iter|
206
+ is_done = false
207
+ on_done = proc do |res|
208
+ raise RuntimeError, 'Already returned a value for this iteration.' if is_done
209
+ is_done = true
210
+
211
+ object = res
212
+ iter.next
213
+ end
214
+
215
+ class << on_done
216
+ alias :return :call
217
+ def next
218
+ raise NoMethodError, 'Must call #return on an inject iterator.'
219
+ end
220
+ end
221
+
222
+ foreach.call( object, item, on_done )
223
+ end,
224
+
225
+ proc do
226
+ after.call(object)
227
+ end
228
+ )
229
+ end
230
+
231
+ private
232
+
233
+ # Spawn workers to consume items from the iterator's enumerator based on the
234
+ # current concurrency level.
235
+ def spawn_workers
236
+ @raktr.next_tick( &proc { |task|
237
+ next if @workers >= @concurrency || @ended
238
+
239
+ @workers += 1
240
+ @process_next.call
241
+ @raktr.next_tick(&task.to_proc)
242
+ })
243
+
244
+ nil
245
+ end
246
+
247
+ end
248
+
249
+ end
@@ -0,0 +1,89 @@
1
+
2
+ =begin
3
+
4
+ This file is part of the Raktr project and may be subject to
5
+ redistribution and commercial restrictions. Please see the Raktr
6
+ web site for more information on licensing and terms of use.
7
+
8
+ =end
9
+
10
+ class Raktr
11
+
12
+ # @note Pretty much an `EventMachine::Queue` rip-off.
13
+ #
14
+ # A cross thread, {Raktr#schedule Raktr scheduled}, linear queue.
15
+ #
16
+ # This class provides a simple queue abstraction on top of the
17
+ # {Raktr#schedule scheduler}.
18
+ #
19
+ # It services two primary purposes:
20
+ #
21
+ # * API sugar for stateful protocols.
22
+ # * Pushing processing onto the {Raktr#thread reactor thread}.
23
+ #
24
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
25
+ class Queue
26
+
27
+ # @return [Raktr]
28
+ attr_reader :raktr
29
+
30
+ # @param [Reactor] reactor
31
+ def initialize( reactor )
32
+ @raktr = reactor
33
+ @items = []
34
+ @waiting = []
35
+ end
36
+
37
+ # @param [Block] block
38
+ # Block to be {Reactor#schedule scheduled} by the {Reactor} and passed
39
+ # an item from the queue as soon as one becomes available.
40
+ def pop( &block )
41
+ @raktr.schedule do
42
+ if @items.empty?
43
+ @waiting << block
44
+ else
45
+ block.call @items.shift
46
+ end
47
+ end
48
+
49
+ nil
50
+ end
51
+
52
+ # @param [Object] item
53
+ # {Reactor#schedule Schedules} an item for addition to the queue.
54
+ def push( item )
55
+ @raktr.schedule do
56
+ @items.push( item )
57
+ @waiting.shift.call @items.shift until @items.empty? || @waiting.empty?
58
+ end
59
+
60
+ nil
61
+ end
62
+ alias :<< :push
63
+
64
+ # @note This is a peek, it's not thread safe, and may only tend toward accuracy.
65
+ #
66
+ # @return [Boolean]
67
+ def empty?
68
+ @items.empty?
69
+ end
70
+
71
+ # @note This is a peek, it's not thread safe, and may only tend toward accuracy.
72
+ #
73
+ # @return [Integer]
74
+ # Queue size.
75
+ def size
76
+ @items.size
77
+ end
78
+
79
+ # @note Accuracy cannot be guaranteed.
80
+ #
81
+ # @return [Integer]
82
+ # Number of jobs that are currently waiting on the Queue for items to appear.
83
+ def num_waiting
84
+ @waiting.size
85
+ end
86
+
87
+ end
88
+
89
+ end