raktr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE.md +29 -0
- data/README.md +77 -0
- data/Rakefile +53 -0
- data/lib/raktr/connection/callbacks.rb +71 -0
- data/lib/raktr/connection/error.rb +120 -0
- data/lib/raktr/connection/peer_info.rb +90 -0
- data/lib/raktr/connection/tls.rb +164 -0
- data/lib/raktr/connection.rb +339 -0
- data/lib/raktr/global.rb +24 -0
- data/lib/raktr/iterator.rb +249 -0
- data/lib/raktr/queue.rb +89 -0
- data/lib/raktr/tasks/base.rb +57 -0
- data/lib/raktr/tasks/delayed.rb +33 -0
- data/lib/raktr/tasks/one_off.rb +30 -0
- data/lib/raktr/tasks/periodic.rb +58 -0
- data/lib/raktr/tasks/persistent.rb +29 -0
- data/lib/raktr/tasks.rb +105 -0
- data/lib/raktr/version.rb +13 -0
- data/lib/raktr.rb +707 -0
- data/spec/raktr/connection/tls_spec.rb +348 -0
- data/spec/raktr/connection_spec.rb +74 -0
- data/spec/raktr/iterator_spec.rb +203 -0
- data/spec/raktr/queue_spec.rb +91 -0
- data/spec/raktr/tasks/base.rb +8 -0
- data/spec/raktr/tasks/delayed_spec.rb +71 -0
- data/spec/raktr/tasks/one_off_spec.rb +66 -0
- data/spec/raktr/tasks/periodic_spec.rb +57 -0
- data/spec/raktr/tasks/persistent_spec.rb +54 -0
- data/spec/raktr/tasks_spec.rb +155 -0
- data/spec/raktr_spec.rb +20 -0
- data/spec/raktr_tls_spec.rb +20 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/fixtures/handlers/echo_client.rb +34 -0
- data/spec/support/fixtures/handlers/echo_client_tls.rb +10 -0
- data/spec/support/fixtures/handlers/echo_server.rb +12 -0
- data/spec/support/fixtures/handlers/echo_server_tls.rb +8 -0
- data/spec/support/fixtures/pems/cacert.pem +37 -0
- data/spec/support/fixtures/pems/client/cert.pem +37 -0
- data/spec/support/fixtures/pems/client/foo-cert.pem +39 -0
- data/spec/support/fixtures/pems/client/foo-key.pem +51 -0
- data/spec/support/fixtures/pems/client/key.pem +51 -0
- data/spec/support/fixtures/pems/server/cert.pem +37 -0
- data/spec/support/fixtures/pems/server/key.pem +51 -0
- data/spec/support/helpers/paths.rb +23 -0
- data/spec/support/helpers/utilities.rb +135 -0
- data/spec/support/lib/server_option_parser.rb +29 -0
- data/spec/support/lib/servers/runner.rb +13 -0
- data/spec/support/lib/servers.rb +133 -0
- data/spec/support/servers/echo.rb +14 -0
- data/spec/support/servers/echo_tls.rb +22 -0
- data/spec/support/servers/echo_unix.rb +14 -0
- data/spec/support/servers/echo_unix_tls.rb +22 -0
- data/spec/support/shared/connection.rb +696 -0
- data/spec/support/shared/raktr.rb +834 -0
- data/spec/support/shared/task.rb +21 -0
- 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
|
data/lib/raktr/global.rb
ADDED
@@ -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
|
data/lib/raktr/queue.rb
ADDED
@@ -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
|