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