tap 0.10.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +12 -0
- data/MIT-LICENSE +0 -2
- data/README +23 -32
- data/bin/rap +116 -0
- data/bin/tap +6 -9
- data/cgi/run.rb +67 -0
- data/cmd/console.rb +1 -1
- data/cmd/destroy.rb +4 -4
- data/cmd/generate.rb +4 -4
- data/cmd/manifest.rb +61 -53
- data/cmd/run.rb +8 -75
- data/doc/Class Reference +130 -121
- data/doc/Command Reference +76 -124
- data/doc/Syntax Reference +290 -0
- data/doc/Tutorial +305 -237
- data/lib/tap/app.rb +140 -467
- data/lib/tap/constants.rb +2 -2
- data/lib/tap/declarations.rb +211 -0
- data/lib/tap/env.rb +171 -193
- data/lib/tap/exe.rb +100 -21
- data/lib/tap/file_task.rb +3 -3
- data/lib/tap/generator/base.rb +1 -1
- data/lib/tap/generator/destroy.rb +10 -10
- data/lib/tap/generator/generate.rb +29 -18
- data/lib/tap/generator/generators/command/command_generator.rb +2 -2
- data/lib/tap/generator/generators/command/templates/command.erb +2 -2
- data/lib/tap/generator/generators/config/config_generator.rb +3 -3
- data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
- data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
- data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
- data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
- data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
- data/lib/tap/generator/generators/root/root_generator.rb +13 -13
- data/lib/tap/generator/generators/root/templates/README +0 -0
- data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
- data/lib/tap/generator/generators/root/templates/gemspec +4 -5
- data/lib/tap/generator/generators/root/templates/tapfile +11 -8
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
- data/lib/tap/generator/generators/task/task_generator.rb +1 -3
- data/lib/tap/generator/generators/task/templates/test.erb +1 -3
- data/lib/tap/patches/optparse/summarize.rb +62 -0
- data/lib/tap/root.rb +41 -29
- data/lib/tap/support/aggregator.rb +16 -3
- data/lib/tap/support/assignments.rb +10 -9
- data/lib/tap/support/audit.rb +58 -64
- data/lib/tap/support/class_configuration.rb +33 -44
- data/lib/tap/support/combinator.rb +125 -0
- data/lib/tap/support/configurable.rb +13 -14
- data/lib/tap/support/configurable_class.rb +21 -43
- data/lib/tap/support/configuration.rb +55 -9
- data/lib/tap/support/constant.rb +87 -13
- data/lib/tap/support/constant_manifest.rb +116 -0
- data/lib/tap/support/dependencies.rb +54 -0
- data/lib/tap/support/dependency.rb +44 -0
- data/lib/tap/support/executable.rb +247 -32
- data/lib/tap/support/executable_queue.rb +1 -1
- data/lib/tap/support/gems/rake.rb +29 -8
- data/lib/tap/support/gems.rb +10 -30
- data/lib/tap/support/instance_configuration.rb +29 -3
- data/lib/tap/support/intern.rb +46 -0
- data/lib/tap/support/join.rb +143 -0
- data/lib/tap/support/joins/fork.rb +19 -0
- data/lib/tap/support/joins/merge.rb +22 -0
- data/lib/tap/support/joins/sequence.rb +21 -0
- data/lib/tap/support/joins/switch.rb +25 -0
- data/lib/tap/support/joins/sync_merge.rb +63 -0
- data/lib/tap/support/joins.rb +15 -0
- data/lib/tap/support/lazy_attributes.rb +17 -2
- data/lib/tap/support/lazydoc/comment.rb +503 -0
- data/lib/tap/support/lazydoc/config.rb +17 -0
- data/lib/tap/support/lazydoc/definition.rb +36 -0
- data/lib/tap/support/lazydoc/document.rb +152 -0
- data/lib/tap/support/lazydoc/method.rb +24 -0
- data/lib/tap/support/lazydoc.rb +269 -343
- data/lib/tap/support/manifest.rb +121 -103
- data/lib/tap/support/minimap.rb +90 -0
- data/lib/tap/support/node.rb +56 -0
- data/lib/tap/support/parser.rb +436 -0
- data/lib/tap/support/schema.rb +359 -0
- data/lib/tap/support/shell_utils.rb +3 -5
- data/lib/tap/support/string_ext.rb +60 -0
- data/lib/tap/support/tdoc.rb +7 -2
- data/lib/tap/support/templater.rb +30 -16
- data/lib/tap/support/validation.rb +77 -8
- data/lib/tap/task.rb +431 -143
- data/lib/tap/tasks/dump.rb +15 -10
- data/lib/tap/tasks/load.rb +112 -0
- data/lib/tap/tasks/rake.rb +4 -41
- data/lib/tap/test/assertions.rb +38 -0
- data/lib/tap/test/env_vars.rb +1 -1
- data/lib/tap/test/extensions.rb +79 -0
- data/lib/tap/test/file_test.rb +420 -0
- data/lib/tap/test/file_test_class.rb +12 -0
- data/lib/tap/test/regexp_escape.rb +87 -0
- data/lib/tap/test/script_test.rb +46 -0
- data/lib/tap/test/script_tester.rb +115 -0
- data/lib/tap/test/subset_test.rb +260 -0
- data/lib/tap/test/subset_test_class.rb +99 -0
- data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
- data/lib/tap/test/utils.rb +231 -0
- data/lib/tap/test.rb +53 -26
- data/lib/tap.rb +3 -20
- metadata +50 -27
- data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
- data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
- data/lib/tap/patches/rake/testtask.rb +0 -57
- data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
- data/lib/tap/patches/ruby19/parsedate.rb +0 -16
- data/lib/tap/support/batchable.rb +0 -47
- data/lib/tap/support/batchable_class.rb +0 -107
- data/lib/tap/support/command_line.rb +0 -98
- data/lib/tap/support/comment.rb +0 -270
- data/lib/tap/support/constant_utils.rb +0 -127
- data/lib/tap/support/declarations.rb +0 -111
- data/lib/tap/support/framework.rb +0 -83
- data/lib/tap/support/framework_class.rb +0 -180
- data/lib/tap/support/run_error.rb +0 -39
- data/lib/tap/support/summary.rb +0 -30
- data/lib/tap/test/file_methods.rb +0 -377
- data/lib/tap/test/script_methods/script_test.rb +0 -98
- data/lib/tap/test/script_methods.rb +0 -107
- data/lib/tap/test/subset_methods.rb +0 -420
- data/lib/tap/workflow.rb +0 -200
data/lib/tap/app.rb
CHANGED
@@ -1,103 +1,96 @@
|
|
1
1
|
require 'logger'
|
2
|
-
require 'tap/support/run_error'
|
3
2
|
require 'tap/support/aggregator'
|
3
|
+
require 'tap/support/dependencies'
|
4
4
|
require 'tap/support/executable_queue'
|
5
5
|
|
6
6
|
module Tap
|
7
7
|
|
8
8
|
# App coordinates the setup and running of tasks, and provides an interface
|
9
|
-
# to the application directory structure.
|
10
|
-
#
|
11
|
-
#
|
9
|
+
# to the application directory structure. All tasks have an App (by default
|
10
|
+
# App.instance) through which tasks access access application-wide resources
|
11
|
+
# like the logger, executable queue, aggregator, and dependencies.
|
12
12
|
#
|
13
13
|
# === Running Tasks
|
14
14
|
#
|
15
|
-
#
|
16
|
-
# access application-wide resources like the logger. Additionally, task
|
17
|
-
# enque command are forwarded to App#enq:
|
15
|
+
# Task enque command are forwarded to App#enq:
|
18
16
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# app.enq(
|
17
|
+
# t0 = Task.intern {|task, input| "#{input}.0" }
|
18
|
+
# t0.enq('a')
|
19
|
+
# app.enq(t0, 'b')
|
22
20
|
#
|
23
21
|
# app.run
|
24
|
-
# app.results(
|
22
|
+
# app.results(t0) # => ['a.0', 'b.0']
|
25
23
|
#
|
26
|
-
# When a task completes, the results will
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
24
|
+
# When a task completes, the results will be passed to the task on_complete
|
25
|
+
# block, if set, or be collected into an Aggregator (aggregated results may
|
26
|
+
# be accessed per-task, as shown above); on_complete blocks typically
|
27
|
+
# execute or enque other tasks, allowing the construction of imperative
|
28
|
+
# workflows:
|
31
29
|
#
|
32
30
|
# # clear the previous results
|
33
31
|
# app.aggregator.clear
|
34
32
|
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# t1.enq 0
|
39
|
-
# t1.enq 10
|
33
|
+
# t1 = Task.intern {|task, input| "#{input}.1" }
|
34
|
+
# t0.on_complete {|_result| t1.enq(_result) }
|
35
|
+
# t0.enq 'c'
|
40
36
|
#
|
41
37
|
# app.run
|
42
|
-
# app.results(
|
43
|
-
# app.results(
|
38
|
+
# app.results(t0) # => []
|
39
|
+
# app.results(t1) # => ['c.0.1']
|
44
40
|
#
|
45
|
-
# Here
|
41
|
+
# Here t0 has no results because the on_complete block passed them to t1 in
|
46
42
|
# a simple sequence.
|
47
43
|
#
|
48
|
-
#
|
44
|
+
# === Dependencies
|
49
45
|
#
|
50
|
-
# Tasks
|
51
|
-
#
|
46
|
+
# Tasks allow the construction of dependency-based workflows such that a
|
47
|
+
# dependent task only executes after its dependencies have been resolved.
|
52
48
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# Task.
|
49
|
+
# runlist = []
|
50
|
+
# t0 = Task.intern {|task| runlist << task }
|
51
|
+
# t1 = Task.intern {|task| runlist << task }
|
56
52
|
#
|
57
|
-
# t1
|
53
|
+
# t0.depends_on(t1)
|
54
|
+
# t0.enq
|
58
55
|
#
|
59
56
|
# app.run
|
60
|
-
#
|
61
|
-
# app.results(t2) # => [10]
|
57
|
+
# runlist # => [t1, t0]
|
62
58
|
#
|
63
|
-
#
|
59
|
+
# Once a dependency is resolved, it will not execute again:
|
64
60
|
#
|
65
|
-
#
|
66
|
-
#
|
61
|
+
# t0.enq
|
62
|
+
# app.run
|
63
|
+
# runlist # => [t1, t0, t0]
|
67
64
|
#
|
68
|
-
#
|
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
|
65
|
+
# === Batching
|
77
66
|
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
67
|
+
# Tasks can be batched, allowing the same input to be enqued to multiple
|
68
|
+
# tasks at once.
|
69
|
+
#
|
70
|
+
# t0 = Task.intern {|task, input| "#{input}.0" }
|
71
|
+
# t1 = Task.intern {|task, input| "#{input}.1" }
|
81
72
|
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
# Methods enq and results act on thread-safe objects ExecutableQueue and
|
86
|
-
# Aggregator, and should be ok to use from multiple threads.
|
73
|
+
# t0.batch_with(t1)
|
74
|
+
# t0.enq 'a'
|
75
|
+
# t1.enq 'b'
|
87
76
|
#
|
88
|
-
#
|
77
|
+
# app.run
|
78
|
+
# app.results(t0) # => ['a.0', 'b.0']
|
79
|
+
# app.results(t1) # => ['a.1', 'b.1']
|
89
80
|
#
|
90
|
-
#
|
91
|
-
# an Executable for a method is to use the Object#_method defined by Tap. The
|
92
|
-
# result can be enqued and incorporated into workflows, but they cannot be
|
93
|
-
# batched.
|
81
|
+
# === Executables
|
94
82
|
#
|
95
|
-
#
|
83
|
+
# App can enque and run any Executable object. Arbitrary methods may be
|
84
|
+
# made into Executables using Object#_method. The mq (method enq) method
|
85
|
+
# generates and enques methods in one step.
|
96
86
|
#
|
97
87
|
# array = []
|
88
|
+
#
|
89
|
+
# # longhand
|
98
90
|
# m = array._method(:push)
|
99
|
-
#
|
100
|
-
#
|
91
|
+
# m.enq(1)
|
92
|
+
#
|
93
|
+
# # shorthand
|
101
94
|
# app.mq(array, :push, 2)
|
102
95
|
#
|
103
96
|
# array.empty? # => true
|
@@ -106,32 +99,30 @@ module Tap
|
|
106
99
|
#
|
107
100
|
# === Auditing
|
108
101
|
#
|
109
|
-
# All results
|
110
|
-
#
|
102
|
+
# All results are audited to track how a given input evolves during a workflow.
|
103
|
+
# To illustrate auditing, consider and addition workflow that ends in eights.
|
111
104
|
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
# 'add_five' method. The final result should always be 8.
|
105
|
+
# add_one = Tap::Task.intern({}, 'add_one') {|task, input| input += 1 }
|
106
|
+
# add_five = Tap::Task.intern({}, 'add_five') {|task, input| input += 5 }
|
115
107
|
#
|
116
|
-
#
|
117
|
-
# t1.name = "add_one"
|
118
|
-
#
|
119
|
-
# t2 = Tap::Task.new {|task, input| input += 5 }
|
120
|
-
# t2.name = "add_five"
|
121
|
-
#
|
122
|
-
# t1.on_complete do |_result|
|
108
|
+
# add_one.on_complete do |_result|
|
123
109
|
# # _result is the audit; use the _current method
|
124
110
|
# # to get the current value in the audit trail
|
111
|
+
# current_value = _result._current
|
125
112
|
#
|
126
|
-
#
|
113
|
+
# if current_value < 3
|
114
|
+
# add_one.enq(_result)
|
115
|
+
# else
|
116
|
+
# add_five.enq(_result)
|
117
|
+
# end
|
127
118
|
# end
|
128
119
|
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
120
|
+
# add_one.enq(0)
|
121
|
+
# add_one.enq(1)
|
122
|
+
# add_one.enq(2)
|
132
123
|
#
|
133
124
|
# app.run
|
134
|
-
# app.results(
|
125
|
+
# app.results(add_five) # => [8,8,8]
|
135
126
|
#
|
136
127
|
# Although the results are indistinguishable, each achieved the final value
|
137
128
|
# through a different series of tasks. With auditing you can see how each
|
@@ -139,7 +130,7 @@ module Tap
|
|
139
130
|
#
|
140
131
|
# # app.results returns the actual result values
|
141
132
|
# # app._results returns the audits for these values
|
142
|
-
# app._results(
|
133
|
+
# app._results(add_five).each do |_result|
|
143
134
|
# puts "How #{_result._original} became #{_result._current}:"
|
144
135
|
# puts _result._to_s
|
145
136
|
# puts
|
@@ -167,8 +158,6 @@ module Tap
|
|
167
158
|
#
|
168
159
|
# See Tap::Support::Audit for more details.
|
169
160
|
class App < Root
|
170
|
-
include MonitorMixin
|
171
|
-
|
172
161
|
class << self
|
173
162
|
# Sets the current app instance
|
174
163
|
attr_writer :instance
|
@@ -190,14 +179,17 @@ module Tap
|
|
190
179
|
attr_reader :state
|
191
180
|
|
192
181
|
# A Tap::Support::Aggregator to collect the results of
|
193
|
-
# methods that have no
|
182
|
+
# methods that have no on_complete block
|
194
183
|
attr_reader :aggregator
|
195
184
|
|
196
|
-
|
185
|
+
# A Tap::Support::Dependencies to track dependencies.
|
186
|
+
attr_reader :dependencies
|
187
|
+
|
197
188
|
config :debug, false, &c.flag # Flag debugging
|
198
189
|
config :force, false, &c.flag # Force execution at checkpoints
|
199
190
|
config :quiet, false, &c.flag # Suppress logging
|
200
|
-
|
191
|
+
config :verbose, false, &c.flag # Enables extra logging (overrides quiet)
|
192
|
+
|
201
193
|
# The constants defining the possible App states.
|
202
194
|
module State
|
203
195
|
READY = 0
|
@@ -222,18 +214,16 @@ module Tap
|
|
222
214
|
super()
|
223
215
|
|
224
216
|
@state = State::READY
|
225
|
-
@threads = [].extend(MonitorMixin)
|
226
|
-
@thread_queue = nil
|
227
|
-
@run_thread = nil
|
228
|
-
|
229
217
|
@queue = Support::ExecutableQueue.new
|
230
218
|
@aggregator = Support::Aggregator.new
|
219
|
+
@dependencies = Support::Dependencies.new
|
231
220
|
|
232
221
|
initialize_config(config)
|
233
222
|
self.logger = logger
|
234
223
|
end
|
235
224
|
|
236
|
-
|
225
|
+
# The default App logger writes to $stdout at level INFO.
|
226
|
+
DEFAULT_LOGGER = Logger.new($stdout)
|
237
227
|
DEFAULT_LOGGER.level = Logger::INFO
|
238
228
|
DEFAULT_LOGGER.formatter = lambda do |severity, time, progname, msg|
|
239
229
|
" %s[%s] %18s %s\n" % [severity[0,1], time.strftime('%H:%M:%S') , progname || '--' , msg]
|
@@ -257,7 +247,7 @@ module Tap
|
|
257
247
|
# Logs the action and message at the input level (default INFO).
|
258
248
|
# Logging is suppressed if quiet is true.
|
259
249
|
def log(action, msg="", level=Logger::INFO)
|
260
|
-
logger.add(level, msg, action.to_s)
|
250
|
+
logger.add(level, msg, action.to_s) if !quiet || verbose
|
261
251
|
end
|
262
252
|
|
263
253
|
# Returns the configuration filepath for the specified task name,
|
@@ -267,170 +257,46 @@ module Tap
|
|
267
257
|
name == nil ? nil : filepath('config', "#{name}.yml")
|
268
258
|
end
|
269
259
|
|
270
|
-
#
|
271
|
-
# Execution methods
|
272
|
-
#
|
273
|
-
|
274
|
-
# Executes the input Executable with the inputs. Stores the result in
|
275
|
-
# aggregator unless an on_complete block is set. Returns the audited
|
276
|
-
# result.
|
277
|
-
def execute(m, inputs)
|
278
|
-
_result = m._execute(*inputs)
|
279
|
-
aggregator.store(_result) unless m.on_complete_block
|
280
|
-
_result
|
281
|
-
end
|
282
|
-
|
283
|
-
# Sets state = State::READY unless the app has a run_thread
|
284
|
-
# (ie the app is running). Returns self.
|
260
|
+
# Sets state = State::READY unless the app is running. Returns self.
|
285
261
|
def ready
|
286
|
-
|
287
|
-
|
288
|
-
self
|
289
|
-
end
|
262
|
+
@state = State::READY unless state == State::RUN
|
263
|
+
self
|
290
264
|
end
|
291
265
|
|
292
|
-
# Sequentially
|
293
|
-
# continues until the queue is empty and then returns self.
|
294
|
-
# only run on one thread at a time. If run is called when already running,
|
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.
|
311
|
-
#
|
312
|
-
# ==== Checks
|
313
|
-
# Run checks the state of self before executing a method. If the state is
|
314
|
-
# changed to State::STOP, then no more methods will be executed; currently
|
315
|
-
# running methods will continute to completion. If the state is changed to
|
316
|
-
# State::TERMINATE then no more methods will be executed and currently running
|
317
|
-
# methods will be discontinued as described below.
|
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).
|
266
|
+
# Sequentially calls execute with the [executable, inputs] pairs in
|
267
|
+
# queue; run continues until the queue is empty and then returns self.
|
324
268
|
#
|
325
|
-
#
|
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.
|
269
|
+
# ==== Run State
|
331
270
|
#
|
332
|
-
#
|
333
|
-
#
|
271
|
+
# Run checks the state of self before executing a method. If state
|
272
|
+
# changes from State::RUN, the following behaviors result:
|
273
|
+
#
|
274
|
+
# State::STOP:: No more executables will be executed; the current
|
275
|
+
# executable will continute to completion.
|
276
|
+
# State::TERMINATE:: No more executables will be executed and the
|
277
|
+
# currently running executable will be
|
278
|
+
# discontinued as described in terminate.
|
334
279
|
#
|
335
|
-
#
|
336
|
-
#
|
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.
|
280
|
+
# Calls to run when the state is not State::READY do nothing and
|
281
|
+
# return immediately.
|
346
282
|
def run
|
347
|
-
|
348
|
-
|
283
|
+
return self unless state == State::READY
|
284
|
+
@state = State::RUN
|
349
285
|
|
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
286
|
# 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
|
287
|
+
begin
|
288
|
+
until queue.empty? || state != State::RUN
|
289
|
+
executable, inputs = queue.deq
|
290
|
+
executable._execute(*inputs)
|
424
291
|
end
|
292
|
+
rescue(TerminateError)
|
293
|
+
# gracefully fail for termination errors
|
294
|
+
rescue(Exception)
|
295
|
+
# handle other errors accordingly
|
296
|
+
raise if debug?
|
297
|
+
log($!.class, $!.message)
|
425
298
|
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
|
299
|
+
@state = State::READY
|
434
300
|
end
|
435
301
|
|
436
302
|
# TODO: log run complete
|
@@ -438,67 +304,46 @@ module Tap
|
|
438
304
|
end
|
439
305
|
|
440
306
|
# Signals a running application to stop executing tasks in the
|
441
|
-
# queue by setting state = State::STOP.
|
442
|
-
#
|
307
|
+
# queue by setting state = State::STOP. The task currently
|
308
|
+
# executing will continue uninterrupted to completion.
|
443
309
|
#
|
444
310
|
# Does nothing unless state is State::RUN.
|
445
311
|
def stop
|
446
|
-
|
447
|
-
|
448
|
-
self
|
449
|
-
end
|
312
|
+
@state = State::STOP if state == State::RUN
|
313
|
+
self
|
450
314
|
end
|
451
315
|
|
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.
|
316
|
+
# Signals a running application to terminate execution by setting
|
317
|
+
# state = State::TERMINATE. In this state, an executing task
|
318
|
+
# will then raise a TerminateError upon check_terminate, thus
|
319
|
+
# allowing the invocation of task-specific termination, perhaps
|
320
|
+
# performing rollbacks. (see Tap::Support::Executable#check_terminate).
|
461
321
|
#
|
462
322
|
# Does nothing if state == State::READY.
|
463
323
|
def terminate
|
464
|
-
|
465
|
-
|
466
|
-
self
|
467
|
-
end
|
324
|
+
@state = State::TERMINATE unless state == State::READY
|
325
|
+
self
|
468
326
|
end
|
469
327
|
|
470
328
|
# Returns an information string for the App.
|
471
329
|
#
|
472
|
-
# App.instance.info # => 'state: 0 (READY) queue: 0
|
330
|
+
# App.instance.info # => 'state: 0 (READY) queue: 0 results: 0'
|
473
331
|
#
|
474
|
-
# Provided information:
|
475
|
-
#
|
476
|
-
# state:: the integer and string values of self.state
|
477
|
-
# 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
|
-
# results:: the total number of results in aggregator
|
483
332
|
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
|
333
|
+
"state: #{state} (#{State.state_str(state)}) queue: #{queue.size} results: #{aggregator.size}"
|
487
334
|
end
|
488
335
|
|
489
|
-
# Enques the task with the inputs. If the task is batched,
|
490
|
-
# task in task.batch will be enqued with the inputs. Returns task.
|
491
|
-
#
|
492
|
-
# An Executable may provided instead of a task.
|
336
|
+
# Enques the task (or Executable) with the inputs. If the task is batched,
|
337
|
+
# then each task in task.batch will be enqued with the inputs. Returns task.
|
493
338
|
def enq(task, *inputs)
|
494
339
|
case task
|
495
|
-
when Tap::Task
|
496
|
-
raise "not assigned to enqueing app: #{task}" unless task.app == self
|
340
|
+
when Tap::Task
|
341
|
+
raise ArgumentError, "not assigned to enqueing app: #{task}" unless task.app == self
|
497
342
|
task.enq(*inputs)
|
498
343
|
when Support::Executable
|
499
344
|
queue.enq(task, inputs)
|
500
345
|
else
|
501
|
-
raise "
|
346
|
+
raise ArgumentError, "not a Task or Executable: #{task}"
|
502
347
|
end
|
503
348
|
task
|
504
349
|
end
|
@@ -509,60 +354,7 @@ module Tap
|
|
509
354
|
m = object._method(method_name)
|
510
355
|
enq(m, *inputs)
|
511
356
|
end
|
512
|
-
|
513
|
-
# Sets a sequence workflow pattern for the tasks such that the
|
514
|
-
# completion of a task enqueues the next task with it's results.
|
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.
|
518
|
-
#
|
519
|
-
# Executables may provided as well as tasks.
|
520
|
-
def sequence(*tasks) # :yields: _result
|
521
|
-
current_task = tasks.shift
|
522
|
-
tasks.each do |next_task|
|
523
|
-
# simply pass results from one task to the next.
|
524
|
-
current_task.on_complete do |_result|
|
525
|
-
yield(_result) if block_given?
|
526
|
-
enq(next_task, _result)
|
527
|
-
end
|
528
|
-
current_task = next_task
|
529
|
-
end
|
530
|
-
end
|
531
|
-
|
532
|
-
# Sets a fork workflow pattern for the tasks such that each of the
|
533
|
-
# targets will be enqueued with the results of the source when the
|
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.
|
537
|
-
#
|
538
|
-
# Executables may provided as well as tasks.
|
539
|
-
def fork(source, *targets) # :yields: _result
|
540
|
-
source.on_complete do |_result|
|
541
|
-
targets.each do |target|
|
542
|
-
yield(_result) if block_given?
|
543
|
-
enq(target, _result)
|
544
|
-
end
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
|
-
# Sets a merge workflow pattern for the tasks such that the results
|
549
|
-
# of each source will be enqueued to the target when the source
|
550
|
-
# completes. Batched tasks will have the pattern set for each
|
551
|
-
# task in the batch. The source audited results are yielded to
|
552
|
-
# the block, if given, before the target is enqued.
|
553
|
-
#
|
554
|
-
# Executables may provided as well as tasks.
|
555
|
-
def merge(target, *sources) # :yields: _result
|
556
|
-
sources.each do |source|
|
557
|
-
# merging can use the existing audit trails... each distinct
|
558
|
-
# input is getting sent to one place (the target)
|
559
|
-
source.on_complete do |_result|
|
560
|
-
yield(_result) if block_given?
|
561
|
-
enq(target, _result)
|
562
|
-
end
|
563
|
-
end
|
564
|
-
end
|
565
|
-
|
357
|
+
|
566
358
|
# Returns all aggregated, audited results for the specified tasks.
|
567
359
|
# Results are joined into a single array. Arrays of tasks are
|
568
360
|
# allowed as inputs. See results.
|
@@ -573,147 +365,28 @@ module Tap
|
|
573
365
|
# Returns all aggregated results for the specified tasks. Results are
|
574
366
|
# joined into a single array. Arrays of tasks are allowed as inputs.
|
575
367
|
#
|
576
|
-
#
|
577
|
-
#
|
578
|
-
#
|
368
|
+
# t0 = Task.intern {|task, input| "#{input}.0" }
|
369
|
+
# t1 = Task.intern {|task, input| "#{input}.1" }
|
370
|
+
# t2 = Task.intern {|task, input| "#{input}.2" }
|
371
|
+
# t1.batch_with(t2)
|
579
372
|
#
|
580
|
-
#
|
581
|
-
#
|
373
|
+
# t0.enq(0)
|
374
|
+
# t1.enq(1)
|
582
375
|
#
|
583
376
|
# app.run
|
584
|
-
# app.results(
|
585
|
-
# app.results(
|
377
|
+
# app.results(t0, t1.batch) # => ["0.0", "1.1", "1.2"]
|
378
|
+
# app.results(t1, t0) # => ["1.1", "0.0"]
|
586
379
|
#
|
587
380
|
def results(*tasks)
|
588
381
|
_results(tasks).collect {|_result| _result._current}
|
589
382
|
end
|
590
383
|
|
591
|
-
|
592
|
-
|
593
|
-
# A hook for handling unknown configurations in subclasses, called from
|
594
|
-
# configure. If handle_configuration evaluates to false, then configure
|
595
|
-
# raises an error.
|
596
|
-
def handle_configuation(key, value)
|
597
|
-
false
|
384
|
+
def inspect
|
385
|
+
"#<#{self.class.to_s}:#{object_id} root: #{root} >"
|
598
386
|
end
|
599
387
|
|
600
|
-
#
|
601
|
-
|
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
|
-
|
715
|
-
# TerminateErrors are raised to kill executing tasks when terminate
|
716
|
-
# is called on an running App. They are handled by the run rescue code.
|
388
|
+
# TerminateErrors are raised to kill executing tasks when terminate is
|
389
|
+
# called on an running App. They are handled by the run rescue code.
|
717
390
|
class TerminateError < RuntimeError
|
718
391
|
end
|
719
392
|
end
|