tap 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/History +12 -0
  2. data/MIT-LICENSE +0 -2
  3. data/README +23 -32
  4. data/bin/rap +116 -0
  5. data/bin/tap +6 -9
  6. data/cgi/run.rb +67 -0
  7. data/cmd/console.rb +1 -1
  8. data/cmd/destroy.rb +4 -4
  9. data/cmd/generate.rb +4 -4
  10. data/cmd/manifest.rb +61 -53
  11. data/cmd/run.rb +8 -75
  12. data/doc/Class Reference +130 -121
  13. data/doc/Command Reference +76 -124
  14. data/doc/Syntax Reference +290 -0
  15. data/doc/Tutorial +305 -237
  16. data/lib/tap/app.rb +140 -467
  17. data/lib/tap/constants.rb +2 -2
  18. data/lib/tap/declarations.rb +211 -0
  19. data/lib/tap/env.rb +171 -193
  20. data/lib/tap/exe.rb +100 -21
  21. data/lib/tap/file_task.rb +3 -3
  22. data/lib/tap/generator/base.rb +1 -1
  23. data/lib/tap/generator/destroy.rb +10 -10
  24. data/lib/tap/generator/generate.rb +29 -18
  25. data/lib/tap/generator/generators/command/command_generator.rb +2 -2
  26. data/lib/tap/generator/generators/command/templates/command.erb +2 -2
  27. data/lib/tap/generator/generators/config/config_generator.rb +3 -3
  28. data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
  29. data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
  30. data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
  31. data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
  32. data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
  33. data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
  34. data/lib/tap/generator/generators/root/root_generator.rb +13 -13
  35. data/lib/tap/generator/generators/root/templates/README +0 -0
  36. data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
  37. data/lib/tap/generator/generators/root/templates/gemspec +4 -5
  38. data/lib/tap/generator/generators/root/templates/tapfile +11 -8
  39. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  40. data/lib/tap/generator/generators/task/task_generator.rb +1 -3
  41. data/lib/tap/generator/generators/task/templates/test.erb +1 -3
  42. data/lib/tap/patches/optparse/summarize.rb +62 -0
  43. data/lib/tap/root.rb +41 -29
  44. data/lib/tap/support/aggregator.rb +16 -3
  45. data/lib/tap/support/assignments.rb +10 -9
  46. data/lib/tap/support/audit.rb +58 -64
  47. data/lib/tap/support/class_configuration.rb +33 -44
  48. data/lib/tap/support/combinator.rb +125 -0
  49. data/lib/tap/support/configurable.rb +13 -14
  50. data/lib/tap/support/configurable_class.rb +21 -43
  51. data/lib/tap/support/configuration.rb +55 -9
  52. data/lib/tap/support/constant.rb +87 -13
  53. data/lib/tap/support/constant_manifest.rb +116 -0
  54. data/lib/tap/support/dependencies.rb +54 -0
  55. data/lib/tap/support/dependency.rb +44 -0
  56. data/lib/tap/support/executable.rb +247 -32
  57. data/lib/tap/support/executable_queue.rb +1 -1
  58. data/lib/tap/support/gems/rake.rb +29 -8
  59. data/lib/tap/support/gems.rb +10 -30
  60. data/lib/tap/support/instance_configuration.rb +29 -3
  61. data/lib/tap/support/intern.rb +46 -0
  62. data/lib/tap/support/join.rb +143 -0
  63. data/lib/tap/support/joins/fork.rb +19 -0
  64. data/lib/tap/support/joins/merge.rb +22 -0
  65. data/lib/tap/support/joins/sequence.rb +21 -0
  66. data/lib/tap/support/joins/switch.rb +25 -0
  67. data/lib/tap/support/joins/sync_merge.rb +63 -0
  68. data/lib/tap/support/joins.rb +15 -0
  69. data/lib/tap/support/lazy_attributes.rb +17 -2
  70. data/lib/tap/support/lazydoc/comment.rb +503 -0
  71. data/lib/tap/support/lazydoc/config.rb +17 -0
  72. data/lib/tap/support/lazydoc/definition.rb +36 -0
  73. data/lib/tap/support/lazydoc/document.rb +152 -0
  74. data/lib/tap/support/lazydoc/method.rb +24 -0
  75. data/lib/tap/support/lazydoc.rb +269 -343
  76. data/lib/tap/support/manifest.rb +121 -103
  77. data/lib/tap/support/minimap.rb +90 -0
  78. data/lib/tap/support/node.rb +56 -0
  79. data/lib/tap/support/parser.rb +436 -0
  80. data/lib/tap/support/schema.rb +359 -0
  81. data/lib/tap/support/shell_utils.rb +3 -5
  82. data/lib/tap/support/string_ext.rb +60 -0
  83. data/lib/tap/support/tdoc.rb +7 -2
  84. data/lib/tap/support/templater.rb +30 -16
  85. data/lib/tap/support/validation.rb +77 -8
  86. data/lib/tap/task.rb +431 -143
  87. data/lib/tap/tasks/dump.rb +15 -10
  88. data/lib/tap/tasks/load.rb +112 -0
  89. data/lib/tap/tasks/rake.rb +4 -41
  90. data/lib/tap/test/assertions.rb +38 -0
  91. data/lib/tap/test/env_vars.rb +1 -1
  92. data/lib/tap/test/extensions.rb +79 -0
  93. data/lib/tap/test/file_test.rb +420 -0
  94. data/lib/tap/test/file_test_class.rb +12 -0
  95. data/lib/tap/test/regexp_escape.rb +87 -0
  96. data/lib/tap/test/script_test.rb +46 -0
  97. data/lib/tap/test/script_tester.rb +115 -0
  98. data/lib/tap/test/subset_test.rb +260 -0
  99. data/lib/tap/test/subset_test_class.rb +99 -0
  100. data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
  101. data/lib/tap/test/utils.rb +231 -0
  102. data/lib/tap/test.rb +53 -26
  103. data/lib/tap.rb +3 -20
  104. metadata +50 -27
  105. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
  106. data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
  107. data/lib/tap/patches/rake/testtask.rb +0 -57
  108. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
  109. data/lib/tap/patches/ruby19/parsedate.rb +0 -16
  110. data/lib/tap/support/batchable.rb +0 -47
  111. data/lib/tap/support/batchable_class.rb +0 -107
  112. data/lib/tap/support/command_line.rb +0 -98
  113. data/lib/tap/support/comment.rb +0 -270
  114. data/lib/tap/support/constant_utils.rb +0 -127
  115. data/lib/tap/support/declarations.rb +0 -111
  116. data/lib/tap/support/framework.rb +0 -83
  117. data/lib/tap/support/framework_class.rb +0 -180
  118. data/lib/tap/support/run_error.rb +0 -39
  119. data/lib/tap/support/summary.rb +0 -30
  120. data/lib/tap/test/file_methods.rb +0 -377
  121. data/lib/tap/test/script_methods/script_test.rb +0 -98
  122. data/lib/tap/test/script_methods.rb +0 -107
  123. data/lib/tap/test/subset_methods.rb +0 -420
  124. 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. App is convenient for use within
10
- # scripts and, with Env, provides the basis for the 'tap' command line
11
- # application.
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
- # All tasks have an App (by default App.instance) through which tasks access
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
- # t1 = Task.new {|task, input| input += 1 }
20
- # t1.enq(0)
21
- # app.enq(t1, 1)
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(t1) # => [1, 2]
22
+ # app.results(t0) # => ['a.0', 'b.0']
25
23
  #
26
- # When a task completes, the results will either be passed to the task
27
- # <tt>on_complete</tt> block (if set) or be collected into an Aggregator;
28
- # aggregated results may be accessed per-task, as shown above. Task
29
- # <tt>on_complete</tt> blocks typically enque other tasks, allowing the
30
- # construction of workflows:
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
- # t2 = Task.new {|task, input| input += 10 }
36
- # t1.on_complete {|_result| t2.enq(_result) }
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(t1) # => []
43
- # app.results(t2) # => [11, 21]
38
+ # app.results(t0) # => []
39
+ # app.results(t1) # => ['c.0.1']
44
40
  #
45
- # Here t1 has no results because the on_complete block passed them to t2 in
41
+ # Here t0 has no results because the on_complete block passed them to t1 in
46
42
  # a simple sequence.
47
43
  #
48
- # ==== Batching
44
+ # === Dependencies
49
45
  #
50
- # Tasks can be batched, allowing the same input to be enqued to multiple
51
- # tasks at once.
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
- # t1 = Task.new {|task, input| input += 1 }
54
- # t2 = Task.new {|task, input| input += 10 }
55
- # Task.batch(t1, t2) # => [t1, t2]
49
+ # runlist = []
50
+ # t0 = Task.intern {|task| runlist << task }
51
+ # t1 = Task.intern {|task| runlist << task }
56
52
  #
57
- # t1.enq 0
53
+ # t0.depends_on(t1)
54
+ # t0.enq
58
55
  #
59
56
  # app.run
60
- # app.results(t1) # => [1]
61
- # app.results(t2) # => [10]
57
+ # runlist # => [t1, t0]
62
58
  #
63
- # ==== Multithreading
59
+ # Once a dependency is resolved, it will not execute again:
64
60
  #
65
- # App supports multithreading; multithreaded tasks execute cosynchronously,
66
- # each on their own thread.
61
+ # t0.enq
62
+ # app.run
63
+ # runlist # => [t1, t0, t0]
67
64
  #
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
65
+ # === Batching
77
66
  #
78
- # app.run
79
- # array.length # => 2
80
- # array[0] == array[1] # => false
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
- # 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.
73
+ # t0.batch_with(t1)
74
+ # t0.enq 'a'
75
+ # t1.enq 'b'
87
76
  #
88
- # ==== Executables
77
+ # app.run
78
+ # app.results(t0) # => ['a.0', 'b.0']
79
+ # app.results(t1) # => ['a.1', 'b.1']
89
80
  #
90
- # App can use any Executable object in place of a task. One way to initialize
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
- # The mq (method enq) method generates and enques the method in one step.
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
- # app.enq(m, 1)
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 generated by executable methods are audited to track how a given
110
- # input evolves during a workflow.
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
- # To illustrate auditing, consider a workflow that uses the 'add_one' method
113
- # to add one to an input until the result is 3, then adds five more with the
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
- # t1 = Tap::Task.new {|task, input| input += 1 }
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
- # _result._current < 3 ? t1.enq(_result) : t2.enq(_result)
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
- # t1.enq(0)
130
- # t1.enq(1)
131
- # t1.enq(2)
120
+ # add_one.enq(0)
121
+ # add_one.enq(1)
122
+ # add_one.enq(2)
132
123
  #
133
124
  # app.run
134
- # app.results(t2) # => [8,8,8]
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(t2).each do |_result|
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 <tt>on_complete</tt> block
182
+ # methods that have no on_complete block
194
183
  attr_reader :aggregator
195
184
 
196
- config :max_threads, 10, &c.integer # For multithread execution
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
- DEFAULT_LOGGER = Logger.new(STDOUT)
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) unless quiet
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
- synchronize do
287
- self.state = State::READY if self.run_thread == nil
288
- self
289
- end
262
+ @state = State::READY unless state == State::RUN
263
+ self
290
264
  end
291
265
 
292
- # Sequentially executes the methods (ie Executable objects) in queue; run
293
- # continues until the queue is empty and then returns self. An app can
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
- # 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.
269
+ # ==== Run State
331
270
  #
332
- # # this task will loop until app.terminate
333
- # Task.new {|task| while(true) task.check_terminate end }
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
- # # 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.
280
+ # Calls to run when the state is not State::READY do nothing and
281
+ # return immediately.
346
282
  def run
347
- synchronize do
348
- return self unless self.ready.state == State::READY
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
- execution_loop do
360
- break if block_given? && yield(self)
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. Currently executing
442
- # tasks will continue their execution uninterrupted.
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
- synchronize do
447
- self.state = State::STOP if self.state == State::RUN
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 executing tasks
453
- # by setting state = State::TERMINATE. When running tasks
454
- # reach a termination check, the task raises a TerminationError,
455
- # thus allowing executing tasks to invoke their specific
456
- # error handling code, perhaps performing rollbacks.
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
- synchronize do
465
- self.state = State::TERMINATE unless self.state == State::READY
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 thread_queue: 0 threads: 0 results: 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
- synchronize do
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, then each
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, Tap::Workflow
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 "Not a Task or Executable: #{task}"
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
- # t1 = Task.new {|task, input| input += 1 }
577
- # t2 = Task.new {|task, input| input += 10 }
578
- # t3 = t2.initialize_batch_obj
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
- # t1.enq(0)
581
- # t2.enq(1)
373
+ # t0.enq(0)
374
+ # t1.enq(1)
582
375
  #
583
376
  # app.run
584
- # app.results(t1, t2.batch) # => [1, 11, 11]
585
- # app.results(t2, t1) # => [11, 1]
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
- protected
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
- # Sets the state of the application
601
- 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
-
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