raktr 0.0.1

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.
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