bahuvrihi-tap 0.10.3 → 0.10.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/tap +1 -1
- data/cgi/run.rb +43 -22
- data/cmd/run.rb +3 -6
- data/cmd/server.rb +4 -0
- data/doc/Class Reference +1 -10
- data/lib/tap.rb +1 -3
- data/lib/tap/app.rb +172 -341
- data/lib/tap/constants.rb +1 -1
- data/lib/tap/exe.rb +48 -3
- data/lib/tap/generator/base.rb +4 -0
- data/lib/tap/generator/destroy.rb +6 -2
- data/lib/tap/generator/generate.rb +22 -16
- data/lib/tap/generator/generators/root/root_generator.rb +3 -3
- data/lib/tap/parser.rb +435 -0
- data/lib/tap/support/combinator.rb +73 -0
- data/lib/tap/support/declarations.rb +15 -11
- data/lib/tap/support/dependable.rb +73 -0
- data/lib/tap/support/executable.rb +16 -62
- data/lib/tap/support/gems/rack.rb +33 -19
- data/lib/tap/support/parsers/server.rb +39 -9
- data/lib/tap/support/templater.rb +1 -1
- data/lib/tap/task.rb +46 -6
- data/lib/tap/test/tap_methods.rb +1 -0
- data/template/index.erb +2 -1
- metadata +4 -3
- data/lib/tap/support/parsers/command_line.rb +0 -90
- data/lib/tap/support/run_error.rb +0 -39
data/bin/tap
CHANGED
data/cgi/run.rb
CHANGED
@@ -6,31 +6,52 @@
|
|
6
6
|
############################
|
7
7
|
require 'cgi'
|
8
8
|
require "#{File.dirname(__FILE__)}/../vendor/url_encoded_pair_parser"
|
9
|
-
require "tap/support/server
|
9
|
+
require "tap/support/parsers/server"
|
10
10
|
|
11
|
-
|
11
|
+
server = Tap::Env.instance
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
require 'tap/tasks/dump'
|
14
|
+
task_attributes = {
|
15
|
+
:tasc => Tap::Tasks::Dump,
|
16
|
+
:config => {},
|
17
|
+
:inputs => []
|
18
|
+
}
|
15
19
|
|
20
|
+
cgi = CGI.new("html3") # add HTML generation methods
|
16
21
|
cgi.out() do
|
17
|
-
cgi.
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
case cgi.request_method
|
23
|
+
when /GET/i
|
24
|
+
server.cgi_template('run',
|
25
|
+
:server => server,
|
26
|
+
:workflow => {},
|
27
|
+
:tasks => [task_attributes])
|
28
|
+
|
29
|
+
when /POST/i
|
30
|
+
argh = UrlEncodedPairParser.new(cgi.params.to_a).result
|
31
|
+
argh = Tap::Support::Parsers::Server.compact(argh)
|
32
|
+
parser = Tap::Support::Parsers::Server.new(argh)
|
33
|
+
|
34
|
+
case cgi['action']
|
35
|
+
when /ADD/i
|
36
|
+
cgi.pre { parser.to_yaml }
|
37
|
+
|
38
|
+
when /REMOVE/i
|
39
|
+
"remove"
|
40
|
+
when /RUN/i
|
41
|
+
"run"
|
42
|
+
|
43
|
+
# argh = UrlEncodedPairParser.new(cgi.params.to_a).result
|
44
|
+
# queues = Tap::Support::Parsers::Server.new(argh).build(Tap::Env.instance, Tap::App.instance)
|
45
|
+
|
46
|
+
# if queues.empty?
|
47
|
+
# end
|
48
|
+
|
49
|
+
# queues.each_with_index do |queue, i|
|
50
|
+
# app.queue.concat(queue)
|
51
|
+
# app.run
|
52
|
+
# end
|
53
|
+
else raise ArgumentError, "unhandled request action: #{cgi['action']}"
|
34
54
|
end
|
55
|
+
else raise ArgumentError, "unhandled request method: #{cgi.request_method}"
|
35
56
|
end
|
36
|
-
end
|
57
|
+
end
|
data/cmd/run.rb
CHANGED
@@ -47,9 +47,8 @@ end.parse!(ARGV)
|
|
47
47
|
#
|
48
48
|
# handle options for each specified task
|
49
49
|
#
|
50
|
-
require 'tap/support/parsers/command_line'
|
51
50
|
|
52
|
-
queues =
|
51
|
+
queues = env.build(ARGV)
|
53
52
|
ARGV.clear
|
54
53
|
|
55
54
|
if queues.empty?
|
@@ -100,7 +99,5 @@ end
|
|
100
99
|
# enque tasks and run!
|
101
100
|
#
|
102
101
|
|
103
|
-
queues
|
104
|
-
|
105
|
-
app.run
|
106
|
-
end
|
102
|
+
env.run(queues)
|
103
|
+
|
data/cmd/server.rb
CHANGED
data/doc/Class Reference
CHANGED
@@ -241,16 +241,7 @@ While simple, this ability to reference files using aliases is useful, powerful,
|
|
241
241
|
|
242
242
|
http://tap.rubyforge.org/images/ExecutableQueue.png
|
243
243
|
|
244
|
-
Apps coordinate the execution of tasks through a queue. The queue is just a stack of Executable objects, basically methods, and the inputs to those methods; during a run the enqued methods are sequentially executed with the inputs.
|
245
|
-
|
246
|
-
Normally the methods execute one after the other on the main thread, however the methods can be set to multithread. In this case all sequential, multithreadable methods are executed on their own thread (up to a configurable maximum number of threads). A non-multithreadable method blocks until all these threads finish, and then the sequential, single thread execution resumes.
|
247
|
-
|
248
|
-
For instance, assuming non-multithreadable executables [a,b,c] and multithread executables [m1, m2, m3], a queue of [a, m1, m2, m3, b, c] executes like:
|
249
|
-
|
250
|
-
a... done
|
251
|
-
(m1, m2, m3)... done
|
252
|
-
b... done
|
253
|
-
c... done
|
244
|
+
Apps coordinate the execution of tasks through a queue. The queue is just a stack of Executable objects, basically methods, and the inputs to those methods; during a run the enqued methods are sequentially executed with the inputs.
|
254
245
|
|
255
246
|
==== Tap::Support::Audit
|
256
247
|
|
data/lib/tap.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'yaml' # expensive to load
|
2
|
-
require 'thread'
|
3
2
|
|
4
3
|
# Apply version-specific patches
|
5
4
|
case RUBY_VERSION
|
@@ -12,8 +11,7 @@ $:.unshift File.expand_path(File.dirname(__FILE__))
|
|
12
11
|
require 'tap/constants'
|
13
12
|
|
14
13
|
# require in order...
|
15
|
-
require 'tap/
|
16
|
-
require 'tap/app'
|
14
|
+
require 'tap/exe'
|
17
15
|
require 'tap/task'
|
18
16
|
require 'tap/file_task'
|
19
17
|
require 'tap/workflow'
|
data/lib/tap/app.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'logger'
|
2
|
-
require 'tap/support/run_error'
|
3
2
|
require 'tap/support/aggregator'
|
4
3
|
require 'tap/support/executable_queue'
|
5
4
|
|
6
5
|
module Tap
|
6
|
+
module Support
|
7
|
+
autoload(:Combinator, 'tap/support/combinator')
|
8
|
+
end
|
7
9
|
|
8
10
|
# App coordinates the setup and running of tasks, and provides an interface
|
9
11
|
# to the application directory structure. App is convenient for use within
|
@@ -27,7 +29,7 @@ module Tap
|
|
27
29
|
# <tt>on_complete</tt> block (if set) or be collected into an Aggregator;
|
28
30
|
# aggregated results may be accessed per-task, as shown above. Task
|
29
31
|
# <tt>on_complete</tt> blocks typically enque other tasks, allowing the
|
30
|
-
# construction of workflows:
|
32
|
+
# construction of imperative workflows:
|
31
33
|
#
|
32
34
|
# # clear the previous results
|
33
35
|
# app.aggregator.clear
|
@@ -45,6 +47,29 @@ module Tap
|
|
45
47
|
# Here t1 has no results because the on_complete block passed them to t2 in
|
46
48
|
# a simple sequence.
|
47
49
|
#
|
50
|
+
# ==== Dependencies
|
51
|
+
#
|
52
|
+
# Tasks allow the construction of dependency-based workflows as well; tasks
|
53
|
+
# may be set to depend on other tasks such that the dependent task only
|
54
|
+
# executes after the dependencies have been resolved (ie executed with a
|
55
|
+
# given set of inputs).
|
56
|
+
#
|
57
|
+
# array = []
|
58
|
+
# t1 = Task.new {|task, *inputs| array << inputs }
|
59
|
+
# t2 = Task.new {|task, *inputs| array << inputs }
|
60
|
+
#
|
61
|
+
# t1.depends_on(t2,1,2,3)
|
62
|
+
# t1.enq(4,5,6)
|
63
|
+
#
|
64
|
+
# app.run
|
65
|
+
# array # => [[1,2,3], [4,5,6]]
|
66
|
+
#
|
67
|
+
# Once a dependency is resolved, it will not execute again:
|
68
|
+
#
|
69
|
+
# t1.enq(7,8)
|
70
|
+
# app.run
|
71
|
+
# array # => [[1,2,3], [4,5,6], [7,8]]
|
72
|
+
#
|
48
73
|
# ==== Batching
|
49
74
|
#
|
50
75
|
# Tasks can be batched, allowing the same input to be enqued to multiple
|
@@ -60,31 +85,6 @@ module Tap
|
|
60
85
|
# app.results(t1) # => [1]
|
61
86
|
# app.results(t2) # => [10]
|
62
87
|
#
|
63
|
-
# ==== Multithreading
|
64
|
-
#
|
65
|
-
# App supports multithreading; multithreaded tasks execute cosynchronously,
|
66
|
-
# each on their own thread.
|
67
|
-
#
|
68
|
-
# lock = Mutex.new
|
69
|
-
# array = []
|
70
|
-
# t1 = Task.new {|task| lock.synchronize { array << Thread.current.object_id }; sleep 0.1 }
|
71
|
-
# t2 = Task.new {|task| lock.synchronize { array << Thread.current.object_id }; sleep 0.1 }
|
72
|
-
#
|
73
|
-
# t1.multithread = true
|
74
|
-
# t1.enq
|
75
|
-
# t2.multithread = true
|
76
|
-
# t2.enq
|
77
|
-
#
|
78
|
-
# app.run
|
79
|
-
# array.length # => 2
|
80
|
-
# array[0] == array[1] # => false
|
81
|
-
#
|
82
|
-
# Naturally, it is up to you to make sure each task is thread safe. Note
|
83
|
-
# that for the most part Tap::App is NOT thread safe; only run and
|
84
|
-
# run-related methods (ready, stop, terminate, info) are synchronized.
|
85
|
-
# Methods enq and results act on thread-safe objects ExecutableQueue and
|
86
|
-
# Aggregator, and should be ok to use from multiple threads.
|
87
|
-
#
|
88
88
|
# ==== Executables
|
89
89
|
#
|
90
90
|
# App can use any Executable object in place of a task. One way to initialize
|
@@ -167,8 +167,6 @@ module Tap
|
|
167
167
|
#
|
168
168
|
# See Tap::Support::Audit for more details.
|
169
169
|
class App < Root
|
170
|
-
include MonitorMixin
|
171
|
-
|
172
170
|
class << self
|
173
171
|
# Sets the current app instance
|
174
172
|
attr_writer :instance
|
@@ -193,11 +191,11 @@ module Tap
|
|
193
191
|
# methods that have no <tt>on_complete</tt> block
|
194
192
|
attr_reader :aggregator
|
195
193
|
|
196
|
-
config :max_threads, 10, &c.integer # For multithread execution
|
197
194
|
config :debug, false, &c.flag # Flag debugging
|
198
195
|
config :force, false, &c.flag # Force execution at checkpoints
|
199
196
|
config :quiet, false, &c.flag # Suppress logging
|
200
|
-
|
197
|
+
config :verbose, false, &c.flag # Enables extra logging (overrides quiet)
|
198
|
+
|
201
199
|
# The constants defining the possible App states.
|
202
200
|
module State
|
203
201
|
READY = 0
|
@@ -222,10 +220,6 @@ module Tap
|
|
222
220
|
super()
|
223
221
|
|
224
222
|
@state = State::READY
|
225
|
-
@threads = [].extend(MonitorMixin)
|
226
|
-
@thread_queue = nil
|
227
|
-
@run_thread = nil
|
228
|
-
|
229
223
|
@queue = Support::ExecutableQueue.new
|
230
224
|
@aggregator = Support::Aggregator.new
|
231
225
|
|
@@ -233,7 +227,8 @@ module Tap
|
|
233
227
|
self.logger = logger
|
234
228
|
end
|
235
229
|
|
236
|
-
|
230
|
+
# The default App logger writes to $stdout at level INFO.
|
231
|
+
DEFAULT_LOGGER = Logger.new($stdout)
|
237
232
|
DEFAULT_LOGGER.level = Logger::INFO
|
238
233
|
DEFAULT_LOGGER.formatter = lambda do |severity, time, progname, msg|
|
239
234
|
" %s[%s] %18s %s\n" % [severity[0,1], time.strftime('%H:%M:%S') , progname || '--' , msg]
|
@@ -257,7 +252,7 @@ module Tap
|
|
257
252
|
# Logs the action and message at the input level (default INFO).
|
258
253
|
# Logging is suppressed if quiet is true.
|
259
254
|
def log(action, msg="", level=Logger::INFO)
|
260
|
-
logger.add(level, msg, action.to_s)
|
255
|
+
logger.add(level, msg, action.to_s) if !quiet || verbose
|
261
256
|
end
|
262
257
|
|
263
258
|
# Returns the configuration filepath for the specified task name,
|
@@ -280,157 +275,38 @@ module Tap
|
|
280
275
|
_result
|
281
276
|
end
|
282
277
|
|
283
|
-
# Sets state = State::READY unless the app
|
284
|
-
# (ie the app is running). Returns self.
|
278
|
+
# Sets state = State::READY unless the app is running. Returns self.
|
285
279
|
def ready
|
286
|
-
|
287
|
-
|
288
|
-
self
|
289
|
-
end
|
280
|
+
self.state = State::READY unless self.state == State::RUN
|
281
|
+
self
|
290
282
|
end
|
291
283
|
|
292
|
-
# Sequentially
|
293
|
-
# continues until the queue is empty and then returns self.
|
294
|
-
#
|
295
|
-
# run returns immediately.
|
296
|
-
#
|
297
|
-
# === The Run Cycle
|
298
|
-
# Run can execute methods in sequential or multithreaded mode. In sequential
|
299
|
-
# mode, run executes enqued methods in order and on the current thread. Run
|
300
|
-
# continues until it reaches a method marked with multithread = true, at which
|
301
|
-
# point run switches into multithreading mode.
|
302
|
-
#
|
303
|
-
# When multithreading, run shifts methods off of the queue and executes each
|
304
|
-
# on their own thread (launching up to max_threads threads at one time).
|
305
|
-
# Multithread execution continues until run reaches a non-multithread method,
|
306
|
-
# at which point run blocks, waits for the threads to complete, and switches
|
307
|
-
# back into sequential mode.
|
308
|
-
#
|
309
|
-
# Run never executes multithreaded and non-multithreaded methods at the same
|
310
|
-
# time.
|
284
|
+
# Sequentially calls execute with the Executable methods and inputs in
|
285
|
+
# queue; run continues until the queue is empty and then returns self.
|
286
|
+
# Calls to run when already running will return immediately.
|
311
287
|
#
|
312
|
-
# ==== Checks
|
313
288
|
# Run checks the state of self before executing a method. If the state is
|
314
289
|
# changed to State::STOP, then no more methods will be executed; currently
|
315
290
|
# running methods will continute to completion. If the state is changed to
|
316
|
-
# State::TERMINATE then no more methods will be executed and currently
|
317
|
-
# methods will be discontinued as described
|
318
|
-
#
|
319
|
-
# ==== Error Handling and Termination
|
320
|
-
# When unhandled errors arise during run, run enters a termination routine.
|
321
|
-
# During termination a TerminationError is raised in each executing method so
|
322
|
-
# that the method exits or begins executing its internal error handling code
|
323
|
-
# (perhaps performing rollbacks).
|
324
|
-
#
|
325
|
-
# The TerminationError can ONLY be raised by the method itself, usually via a
|
326
|
-
# call to Tap::Support::Framework#check_terminate. <tt>check_terminate</tt>
|
327
|
-
# is available to all Framework objects (ex Task and Workflow), but not to
|
328
|
-
# Executable methods generated by _method. These methods need to check the
|
329
|
-
# state of app themselves; otherwise they will continue on to completion even
|
330
|
-
# when app is in State::TERMINATE.
|
331
|
-
#
|
332
|
-
# # this task will loop until app.terminate
|
333
|
-
# Task.new {|task| while(true) task.check_terminate end }
|
334
|
-
#
|
335
|
-
# # this task will NEVER terminate
|
336
|
-
# Task.new {|task| while(true) end; task.check_terminate }
|
337
|
-
#
|
338
|
-
# Additional errors that arise during termination are collected and packaged
|
339
|
-
# with the orignal error into a RunError. By default all errors are logged
|
340
|
-
# and run exits. If debug? is true, then the RunError will be raised for
|
341
|
-
# further handling.
|
342
|
-
#
|
343
|
-
# Note: the method that caused the original unhandled error is no longer
|
344
|
-
# executing when termination begins and thus will not recieve a
|
345
|
-
# TerminationError.
|
291
|
+
# State::TERMINATE then no more methods will be executed and currently
|
292
|
+
# running methods will be discontinued as described in terminate.
|
346
293
|
def run
|
347
|
-
|
348
|
-
|
294
|
+
return self unless state == State::READY
|
295
|
+
self.state = State::RUN
|
349
296
|
|
350
|
-
self.run_thread = Thread.current
|
351
|
-
self.state = State::RUN
|
352
|
-
end
|
353
|
-
|
354
|
-
# generate threading variables
|
355
|
-
self.thread_queue = max_threads > 0 ? Queue.new : nil
|
356
|
-
|
357
297
|
# TODO: log starting run
|
358
|
-
begin
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
# if no tasks were in the queue
|
363
|
-
# then clear the threads and
|
364
|
-
# check for tasks again
|
365
|
-
if queue.empty?
|
366
|
-
clear_threads
|
367
|
-
# break -- no executable task was found
|
368
|
-
break if queue.empty?
|
369
|
-
end
|
370
|
-
|
371
|
-
m, inputs = queue.deq
|
372
|
-
|
373
|
-
if thread_queue && m.multithread
|
374
|
-
# TODO: log enqueuing task to thread
|
375
|
-
|
376
|
-
# generate threads as needed and allowed
|
377
|
-
# to execute the threads in the thread queue
|
378
|
-
start_thread if threads.size < max_threads
|
379
|
-
|
380
|
-
# NOTE: the producer-consumer relationship of execution
|
381
|
-
# threads and the thread_queue means that tasks will sit
|
382
|
-
# waiting until an execution thread opens up. in the most
|
383
|
-
# extreme case all executing tasks and all tasks in the
|
384
|
-
# task_queue could be the same task, each with different
|
385
|
-
# inputs. this deviates from the idea of batch processing,
|
386
|
-
# but should be rare and not at all fatal given execute
|
387
|
-
# synchronization.
|
388
|
-
thread_queue.enq [m, inputs]
|
389
|
-
|
390
|
-
else
|
391
|
-
# TODO: log execute task
|
392
|
-
|
393
|
-
# wait for threads to complete
|
394
|
-
# before executing the main thread
|
395
|
-
clear_threads
|
396
|
-
execute(m, inputs)
|
397
|
-
end
|
398
|
-
end
|
399
|
-
|
400
|
-
# if the run loop exited due to a STOP state,
|
401
|
-
# tasks may still be in the thread queue and/or
|
402
|
-
# running. be sure these are cleared
|
403
|
-
clear_thread_queue
|
404
|
-
clear_threads
|
405
|
-
|
406
|
-
rescue
|
407
|
-
# when an error is generated, be sure to terminate
|
408
|
-
# all threads so they can clean up after themselves.
|
409
|
-
# clear the thread queue first so no more tasks are
|
410
|
-
# executed. collect any errors that arise during
|
411
|
-
# termination.
|
412
|
-
clear_thread_queue
|
413
|
-
errors = [$!] + clear_threads(false)
|
414
|
-
errors.delete_if {|error| error.kind_of?(TerminateError) }
|
415
|
-
|
416
|
-
# handle the errors accordingly
|
417
|
-
case
|
418
|
-
when debug?
|
419
|
-
raise Tap::Support::RunError.new(errors)
|
420
|
-
else
|
421
|
-
errors.each_with_index do |err, index|
|
422
|
-
log("RunError [#{index}] #{err.class}", err.message)
|
423
|
-
end
|
298
|
+
begin
|
299
|
+
until queue.empty? || state != State::RUN
|
300
|
+
execute(*queue.deq)
|
424
301
|
end
|
302
|
+
rescue(TerminateError)
|
303
|
+
# gracefully fail for termination errors
|
304
|
+
rescue(Exception)
|
305
|
+
# handle other errors accordingly
|
306
|
+
raise if debug?
|
307
|
+
log($!.class, $!.message)
|
425
308
|
ensure
|
426
|
-
|
427
|
-
# reset run variables
|
428
|
-
self.thread_queue = nil
|
429
|
-
|
430
|
-
synchronize do
|
431
|
-
self.run_thread = nil
|
432
|
-
self.state = State::READY
|
433
|
-
end
|
309
|
+
self.state = State::READY
|
434
310
|
end
|
435
311
|
|
436
312
|
# TODO: log run complete
|
@@ -438,52 +314,38 @@ module Tap
|
|
438
314
|
end
|
439
315
|
|
440
316
|
# Signals a running application to stop executing tasks in the
|
441
|
-
# queue by setting state = State::STOP.
|
442
|
-
#
|
317
|
+
# queue by setting state = State::STOP. The task currently
|
318
|
+
# executing will continue uninterrupted to completion.
|
443
319
|
#
|
444
320
|
# Does nothing unless state is State::RUN.
|
445
321
|
def stop
|
446
|
-
|
447
|
-
|
448
|
-
self
|
449
|
-
end
|
322
|
+
self.state = State::STOP if self.state == State::RUN
|
323
|
+
self
|
450
324
|
end
|
451
325
|
|
452
|
-
# Signals a running application to terminate
|
453
|
-
#
|
454
|
-
#
|
455
|
-
#
|
456
|
-
#
|
457
|
-
#
|
458
|
-
# Termination checks can be manually specified in a task
|
459
|
-
# using the check_terminate method (see Tap::Task#check_terminate).
|
460
|
-
# Termination checks automatically occur before each task execution.
|
326
|
+
# Signals a running application to terminate execution by setting
|
327
|
+
# state = State::TERMINATE. In this state, an executing task
|
328
|
+
# will then raise a TerminateError upon check_terminate, thus
|
329
|
+
# allowing the invocation of task-specific termination, perhaps
|
330
|
+
# performing rollbacks. (see Tap::Task#check_terminate).
|
461
331
|
#
|
462
332
|
# Does nothing if state == State::READY.
|
463
333
|
def terminate
|
464
|
-
|
465
|
-
|
466
|
-
self
|
467
|
-
end
|
334
|
+
self.state = State::TERMINATE unless self.state == State::READY
|
335
|
+
self
|
468
336
|
end
|
469
337
|
|
470
338
|
# Returns an information string for the App.
|
471
339
|
#
|
472
|
-
# App.instance.info # => 'state: 0 (READY) queue: 0
|
340
|
+
# App.instance.info # => 'state: 0 (READY) queue: 0 results: 0'
|
473
341
|
#
|
474
342
|
# Provided information:
|
475
343
|
#
|
476
344
|
# state:: the integer and string values of self.state
|
477
345
|
# queue:: the number of methods currently in the queue
|
478
|
-
# thread_queue:: number of objects in the thread queue, waiting
|
479
|
-
# to be run on an execution thread (methods, and
|
480
|
-
# perhaps nils to signal threads to clear)
|
481
|
-
# threads:: the number of execution threads
|
482
346
|
# results:: the total number of results in aggregator
|
483
347
|
def info
|
484
|
-
|
485
|
-
"state: #{state} (#{State.state_str(state)}) queue: #{queue.size} thread_queue: #{thread_queue ? thread_queue.size : 0} threads: #{threads.size} results: #{aggregator.size}"
|
486
|
-
end
|
348
|
+
"state: #{state} (#{State.state_str(state)}) queue: #{queue.size} results: #{aggregator.size}"
|
487
349
|
end
|
488
350
|
|
489
351
|
# Enques the task with the inputs. If the task is batched, then each
|
@@ -493,12 +355,12 @@ module Tap
|
|
493
355
|
def enq(task, *inputs)
|
494
356
|
case task
|
495
357
|
when Tap::Task
|
496
|
-
raise "not assigned to enqueing app: #{task}" unless task.app == self
|
358
|
+
raise ArgumentError, "not assigned to enqueing app: #{task}" unless task.app == self
|
497
359
|
task.enq(*inputs)
|
498
360
|
when Support::Executable
|
499
361
|
queue.enq(task, inputs)
|
500
362
|
else
|
501
|
-
raise "
|
363
|
+
raise ArgumentError, "not a Task or Executable: #{task}"
|
502
364
|
end
|
503
365
|
task
|
504
366
|
end
|
@@ -510,14 +372,15 @@ module Tap
|
|
510
372
|
enq(m, *inputs)
|
511
373
|
end
|
512
374
|
|
513
|
-
# Sets a sequence workflow pattern for the tasks
|
514
|
-
#
|
515
|
-
# Batched tasks will have the pattern set for each task in the
|
516
|
-
# batch. The current audited results are yielded to the block,
|
517
|
-
# if given, before the next task is enqued.
|
375
|
+
# Sets a sequence workflow pattern for the tasks; each task will enque
|
376
|
+
# the next task with it's results.
|
518
377
|
#
|
519
|
-
#
|
520
|
-
|
378
|
+
# Notes:
|
379
|
+
# - Batched tasks will have the pattern set for each task in the batch
|
380
|
+
# - The current audited results are yielded to the block, if given,
|
381
|
+
# before the next task is enqued.
|
382
|
+
# - Executables may provided as well as tasks.
|
383
|
+
def sequence(tasks) # :yields: _result
|
521
384
|
current_task = tasks.shift
|
522
385
|
tasks.each do |next_task|
|
523
386
|
# simply pass results from one task to the next.
|
@@ -529,14 +392,15 @@ module Tap
|
|
529
392
|
end
|
530
393
|
end
|
531
394
|
|
532
|
-
# Sets a fork workflow pattern for the
|
533
|
-
#
|
534
|
-
# source completes. Batched tasks will have the pattern set for each
|
535
|
-
# task in the batch. The source audited results are yielded to the
|
536
|
-
# block, if given, before the targets are enqued.
|
395
|
+
# Sets a fork workflow pattern for the source task; each target
|
396
|
+
# will enque the results of source.
|
537
397
|
#
|
538
|
-
#
|
539
|
-
|
398
|
+
# Notes:
|
399
|
+
# - Batched tasks will have the pattern set for each task in the batch
|
400
|
+
# - The current audited results are yielded to the block, if given,
|
401
|
+
# before the next task is enqued.
|
402
|
+
# - Executables may provided as well as tasks.
|
403
|
+
def fork(source, targets) # :yields: _result
|
540
404
|
source.on_complete do |_result|
|
541
405
|
targets.each do |target|
|
542
406
|
yield(_result) if block_given?
|
@@ -544,15 +408,17 @@ module Tap
|
|
544
408
|
end
|
545
409
|
end
|
546
410
|
end
|
547
|
-
|
548
|
-
# Sets a merge workflow pattern for the tasks
|
549
|
-
#
|
550
|
-
#
|
551
|
-
# task in the batch. The source audited results are yielded to
|
552
|
-
# the block, if given, before the target is enqued.
|
411
|
+
|
412
|
+
# Sets a simple merge workflow pattern for the source tasks. Each source
|
413
|
+
# enques target with it's result; no synchronization occurs, nor are
|
414
|
+
# results grouped before being sent to the target.
|
553
415
|
#
|
554
|
-
#
|
555
|
-
|
416
|
+
# Notes:
|
417
|
+
# - Batched tasks will have the pattern set for each task in the batch
|
418
|
+
# - The current audited results are yielded to the block, if given,
|
419
|
+
# before the next task is enqued.
|
420
|
+
# - Executables may provided as well as tasks.
|
421
|
+
def merge(target, sources) # :yields: _result
|
556
422
|
sources.each do |source|
|
557
423
|
# merging can use the existing audit trails... each distinct
|
558
424
|
# input is getting sent to one place (the target)
|
@@ -563,6 +429,83 @@ module Tap
|
|
563
429
|
end
|
564
430
|
end
|
565
431
|
|
432
|
+
# Sets a synchronized merge workflow for the source tasks. Results from
|
433
|
+
# each source task are collected and enqued as a single group to the target.
|
434
|
+
# The target is not enqued until all sources have completed. Raises an
|
435
|
+
# error if a source returns twice before the target is enqued.
|
436
|
+
#
|
437
|
+
# Notes:
|
438
|
+
# - Batched tasks will have the pattern set for each task in the batch
|
439
|
+
# - The current audited results are yielded to the block, if given,
|
440
|
+
# before the next task is enqued.
|
441
|
+
# - Executables may provided as well as tasks.
|
442
|
+
#
|
443
|
+
#-- TODO: add notes on testing and the way results are received
|
444
|
+
# (ie as a single object)
|
445
|
+
def sync_merge(target, sources) # :yields: _result
|
446
|
+
group = Array.new(sources.length, nil)
|
447
|
+
sources.each_with_index do |source, index|
|
448
|
+
batch_map = Hash.new(0)
|
449
|
+
batch_length = if source.kind_of?(Support::Batchable)
|
450
|
+
source.batch.each_with_index {|obj, i| batch_map[obj] = i }
|
451
|
+
source.batch.length
|
452
|
+
else
|
453
|
+
1
|
454
|
+
end
|
455
|
+
|
456
|
+
group[index] = Array.new(batch_length, nil)
|
457
|
+
|
458
|
+
source.on_complete do |_result|
|
459
|
+
batch_index = batch_map[_result._current_source]
|
460
|
+
|
461
|
+
if group[index][batch_index] != nil
|
462
|
+
raise "sync_merge collision... already got a result for #{_result._current_source}"
|
463
|
+
end
|
464
|
+
|
465
|
+
group[index][batch_index] = _result
|
466
|
+
|
467
|
+
unless group.flatten.include?(nil)
|
468
|
+
Support::Combinator.new(*group).each do |*combination|
|
469
|
+
# merge the source audits
|
470
|
+
_group_result = Support::Audit.merge(*combination)
|
471
|
+
|
472
|
+
yield(_group_result) if block_given?
|
473
|
+
target.enq(_group_result)
|
474
|
+
end
|
475
|
+
|
476
|
+
# reset the group array
|
477
|
+
group.collect! {|i| nil }
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# Sets a choice workflow pattern for the source task. When the
|
484
|
+
# source task completes, switch yields the audited result to the
|
485
|
+
# block which then returns the index of the target to enque with
|
486
|
+
# the results. No target will be enqued if the index is false or
|
487
|
+
# nil; an error is raised if no target can be found for the
|
488
|
+
# specified index.
|
489
|
+
#
|
490
|
+
# Notes:
|
491
|
+
# - Batched tasks will have the pattern set for each task in the batch
|
492
|
+
# - The current audited results are yielded to the block, if given,
|
493
|
+
# before the next task is enqued.
|
494
|
+
# - Executables may provided as well as tasks.
|
495
|
+
def switch(source, targets) # :yields: _result
|
496
|
+
source.on_complete do |_result|
|
497
|
+
if index = yield(_result)
|
498
|
+
unless target = targets[index]
|
499
|
+
raise "no switch target for index: #{index}"
|
500
|
+
end
|
501
|
+
|
502
|
+
enq(target, _result)
|
503
|
+
else
|
504
|
+
aggregator.store(_result)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
566
509
|
# Returns all aggregated, audited results for the specified tasks.
|
567
510
|
# Results are joined into a single array. Arrays of tasks are
|
568
511
|
# allowed as inputs. See results.
|
@@ -599,121 +542,9 @@ module Tap
|
|
599
542
|
|
600
543
|
# Sets the state of the application
|
601
544
|
attr_writer :state
|
602
|
-
|
603
|
-
# The thread on which run is executing tasks.
|
604
|
-
attr_accessor :run_thread
|
605
|
-
|
606
|
-
# An array containing the execution threads in use by run.
|
607
|
-
attr_accessor :threads
|
608
|
-
|
609
|
-
# A Queue containing multithread tasks waiting to be run
|
610
|
-
# on the execution threads. Nil if max_threads == 0
|
611
|
-
attr_accessor :thread_queue
|
612
|
-
|
613
|
-
private
|
614
|
-
|
615
|
-
def execution_loop
|
616
|
-
while true
|
617
|
-
case state
|
618
|
-
when State::STOP
|
619
|
-
break
|
620
|
-
when State::TERMINATE
|
621
|
-
# if an execution thread handles the termination error,
|
622
|
-
# then the thread may end up here -- terminated but still
|
623
|
-
# running. Raise another termination error to enter the
|
624
|
-
# termination (rescue) code.
|
625
|
-
raise TerminateError.new
|
626
|
-
end
|
627
|
-
|
628
|
-
yield
|
629
|
-
end
|
630
|
-
end
|
631
|
-
|
632
|
-
def clear_thread_queue
|
633
|
-
return unless thread_queue
|
634
|
-
|
635
|
-
# clear the queue and enque the thread complete
|
636
|
-
# signals, so that the thread will exit normally
|
637
|
-
dequeued = []
|
638
|
-
while !thread_queue.empty?
|
639
|
-
dequeued << thread_queue.deq
|
640
|
-
end
|
641
|
-
|
642
|
-
# add dequeued tasks back, in order, to the task
|
643
|
-
# queue so no tasks get lost due to the stop
|
644
|
-
#
|
645
|
-
# BUG: this will result in an already-newly-queued
|
646
|
-
# task being promoted along with it's inputs
|
647
|
-
dequeued.reverse_each do |task, inputs|
|
648
|
-
# TODO: log about not executing
|
649
|
-
queue.unshift(task, inputs) unless task.nil?
|
650
|
-
end
|
651
|
-
end
|
652
|
-
|
653
|
-
def clear_threads(raise_errors=true)
|
654
|
-
threads.synchronize do
|
655
|
-
errors = []
|
656
|
-
return errors if threads.empty?
|
657
|
-
|
658
|
-
# clears threads gracefully by enqueuing nils, to break
|
659
|
-
# the threads out of their loops, then waiting for the
|
660
|
-
# threads to work through the queue to the nils
|
661
|
-
#
|
662
|
-
threads.size.times { thread_queue.enq nil }
|
663
|
-
while true
|
664
|
-
# TODO -- add a time out?
|
665
|
-
|
666
|
-
threads.dup.each do |thread|
|
667
|
-
next if thread.alive?
|
668
|
-
threads.delete(thread)
|
669
|
-
error = thread["error"]
|
670
|
-
|
671
|
-
next if error.nil?
|
672
|
-
raise error if raise_errors
|
673
|
-
|
674
|
-
errors << error
|
675
|
-
end
|
676
|
-
|
677
|
-
break if threads.empty?
|
678
|
-
Thread.pass
|
679
|
-
end
|
680
|
-
|
681
|
-
errors
|
682
|
-
end
|
683
|
-
end
|
684
|
-
|
685
|
-
def start_thread
|
686
|
-
threads.synchronize do
|
687
|
-
# start a new thread and add it to threads.
|
688
|
-
# threads simply loop and wait for a task to
|
689
|
-
# be queued. the thread will block until a
|
690
|
-
# task is available (due to thread_queue.deq)
|
691
|
-
#
|
692
|
-
# TODO -- track thread index like?
|
693
|
-
# thread["index"] = threads.length
|
694
|
-
threads << Thread.new do
|
695
|
-
# TODO - log thread start
|
696
|
-
|
697
|
-
begin
|
698
|
-
execution_loop do
|
699
|
-
m, inputs = thread_queue.deq
|
700
|
-
break if m.nil?
|
701
|
-
|
702
|
-
# TODO: log execute task on thread #
|
703
|
-
execute(m, inputs)
|
704
|
-
end
|
705
|
-
rescue
|
706
|
-
# an unhandled error should immediately
|
707
|
-
# terminate all threads
|
708
|
-
terminate
|
709
|
-
Thread.current["error"] = $!
|
710
|
-
end
|
711
|
-
end
|
712
|
-
end
|
713
|
-
end
|
714
545
|
|
715
|
-
# TerminateErrors are raised to kill executing tasks when terminate
|
716
|
-
#
|
546
|
+
# TerminateErrors are raised to kill executing tasks when terminate is
|
547
|
+
# called on an running App. They are handled by the run rescue code.
|
717
548
|
class TerminateError < RuntimeError
|
718
549
|
end
|
719
550
|
end
|