tap 0.7.9 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +28 -0
- data/MIT-LICENSE +1 -1
- data/README +71 -43
- data/Rakefile +81 -64
- data/Tutorial +235 -0
- data/bin/tap +80 -44
- data/lib/tap.rb +41 -12
- data/lib/tap/app.rb +243 -246
- data/lib/tap/file_task.rb +357 -118
- data/lib/tap/generator.rb +88 -29
- data/lib/tap/generator/generators/config/config_generator.rb +4 -2
- data/lib/tap/generator/generators/config/templates/config.erb +1 -2
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
- data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
- data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
- data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
- data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
- data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
- data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
- data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
- data/lib/tap/generator/generators/package/package_generator.rb +38 -0
- data/lib/tap/generator/generators/package/templates/package.erb +186 -0
- data/lib/tap/generator/generators/root/root_generator.rb +14 -9
- data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
- data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
- data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
- data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +17 -0
- data/lib/tap/generator/generators/script/templates/script.erb +42 -0
- data/lib/tap/generator/generators/task/task_generator.rb +1 -1
- data/lib/tap/generator/generators/task/templates/task.erb +24 -16
- data/lib/tap/generator/generators/task/templates/test.erb +13 -17
- data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
- data/lib/tap/root.rb +108 -146
- data/lib/tap/script.rb +362 -0
- data/lib/tap/script/console.rb +28 -0
- data/lib/tap/script/destroy.rb +13 -1
- data/lib/tap/script/generate.rb +13 -1
- data/lib/tap/script/run.rb +100 -57
- data/lib/tap/support/batch_queue.rb +0 -3
- data/lib/tap/support/logger.rb +6 -3
- data/lib/tap/support/rake.rb +54 -0
- data/lib/tap/support/task_configuration.rb +169 -0
- data/lib/tap/support/tdoc.rb +198 -0
- data/lib/tap/support/tdoc/config_attr.rb +338 -0
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
- data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
- data/lib/tap/support/versions.rb +33 -1
- data/lib/tap/task.rb +339 -227
- data/lib/tap/test.rb +86 -128
- data/lib/tap/test/env_vars.rb +16 -5
- data/lib/tap/test/file_methods.rb +373 -0
- data/lib/tap/test/subset_methods.rb +299 -180
- data/lib/tap/version.rb +2 -1
- data/lib/tap/workflow.rb +2 -0
- data/test/app/lib/app_test_task.rb +1 -0
- data/test/app_test.rb +327 -83
- data/test/check/binding_eval.rb +23 -0
- data/test/check/define_method_check.rb +22 -0
- data/test/check/dependencies_check.rb +175 -0
- data/test/check/inheritance_check.rb +22 -0
- data/test/file_task_test.rb +524 -291
- data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
- data/test/root/glob/two.txt +0 -0
- data/test/root_test.rb +330 -262
- data/test/script_test.rb +194 -0
- data/test/support/audit_test.rb +5 -2
- data/test/support/combinator_test.rb +10 -10
- data/test/support/rake_test.rb +35 -0
- data/test/support/task_configuration_test.rb +272 -0
- data/test/support/tdoc_test.rb +363 -0
- data/test/support/templater_test.rb +2 -2
- data/test/support/versions_test.rb +32 -0
- data/test/tap_test_helper.rb +39 -0
- data/test/task_base_test.rb +115 -0
- data/test/task_class_test.rb +56 -4
- data/test/task_execute_test.rb +29 -0
- data/test/task_test.rb +89 -70
- data/test/test/env_vars_test.rb +48 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
- data/test/test/file_methods_test.rb +204 -0
- data/test/test/subset_methods_test.rb +93 -33
- data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
- data/test/test/test_file_task_test/expected/one.txt +1 -0
- data/test/test/test_file_task_test/expected/two.txt +1 -0
- data/test/test/test_file_task_test/input/one.txt +1 -0
- data/test/test/test_file_task_test/input/two.txt +1 -0
- data/test/test_test.rb +143 -3
- data/test/workflow_test.rb +2 -0
- data/vendor/rails_generator.rb +56 -0
- data/vendor/rails_generator/base.rb +263 -0
- data/vendor/rails_generator/commands.rb +581 -0
- data/vendor/rails_generator/generated_attribute.rb +42 -0
- data/vendor/rails_generator/lookup.rb +209 -0
- data/vendor/rails_generator/manifest.rb +53 -0
- data/vendor/rails_generator/options.rb +143 -0
- data/vendor/rails_generator/scripts.rb +83 -0
- data/vendor/rails_generator/scripts/destroy.rb +7 -0
- data/vendor/rails_generator/scripts/generate.rb +7 -0
- data/vendor/rails_generator/scripts/update.rb +12 -0
- data/vendor/rails_generator/simple_logger.rb +46 -0
- data/vendor/rails_generator/spec.rb +44 -0
- metadata +180 -196
- data/lib/tap/generator/generators/root/templates/app.yml +0 -19
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
- data/lib/tap/script/server.rb +0 -12
- data/lib/tap/support/rap.rb +0 -38
- data/lib/tap/test/inference_methods.rb +0 -298
- data/test/task/config/task_with_config.yml +0 -1
- data/test/test/inference_methods_test.rb +0 -311
data/bin/tap
CHANGED
@@ -1,63 +1,99 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
Tap::
|
1
|
+
# usage: tap <command> {options} [args]
|
2
|
+
#
|
3
|
+
# examples:
|
4
|
+
# tap generate root /path/to/root # generates a root dir
|
5
|
+
# tap run taskname --option input # runs the 'taskname' task
|
6
|
+
#
|
7
|
+
# help:
|
8
|
+
# tap help # prints this help
|
9
|
+
# tap command --help # prints help for 'command'
|
10
|
+
|
11
|
+
require File.join( File.dirname(__FILE__), "../lib/tap.rb")
|
12
|
+
require "tap/script"
|
13
|
+
|
14
|
+
app = Tap::App.instance
|
15
|
+
script = Tap::Script.instance
|
16
|
+
|
17
|
+
begin
|
18
|
+
# configure the app to tap.yml if it exists
|
19
|
+
config_file = Tap::Script.config_filepath(Dir.pwd)
|
20
|
+
config = Tap::Script.read_config(config_file)
|
21
|
+
script.configure_app(config)
|
22
|
+
rescue(Exception)
|
23
|
+
# catch errors and exit gracefully
|
24
|
+
# (errors usu from gem loading errors)
|
25
|
+
puts "Configuration error: #{$!.message}"
|
26
|
+
puts "Check #{config_file} configurations"
|
27
|
+
exit(1)
|
28
|
+
end
|
29
|
+
|
30
|
+
# alert the user to the root directory if it's not Dir.pwd
|
31
|
+
unless app.options.quiet || app.root == File.expand_path(Dir.pwd)
|
32
|
+
puts "root: #{app.root}"
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# run before script
|
37
|
+
#
|
38
|
+
begin
|
39
|
+
eval(script.config.before.to_s)
|
40
|
+
rescue
|
41
|
+
puts "Error in before script."
|
42
|
+
if app.options.debug
|
43
|
+
raise
|
44
|
+
else
|
45
|
+
puts $!.message
|
46
|
+
exit(1)
|
47
|
+
end
|
21
48
|
end
|
22
49
|
|
23
50
|
begin
|
51
|
+
available_commands = script.config.scripts
|
24
52
|
command = ARGV.shift
|
53
|
+
|
25
54
|
case command
|
26
|
-
when "help", "?", nil
|
27
|
-
|
55
|
+
when "--help", "-h", "help", "?", nil
|
56
|
+
# give some help
|
57
|
+
puts Tap::Script.usage(__FILE__)
|
58
|
+
puts
|
28
59
|
puts "available commands:"
|
29
|
-
|
30
|
-
commands =
|
31
|
-
|
32
|
-
Tap::App.instance.relative_filepath(:script, script).chomp(".rb")
|
33
|
-
end
|
34
|
-
Tap::App.glob(lib_dir + "tap/script/**/*").each do |script|
|
35
|
-
next unless File.extname(script) == ".rb"
|
36
|
-
command = Tap::App.relative_filepath(lib_dir + "tap/script", script).chomp(".rb")
|
37
|
-
commands << command unless commands.include?(command)
|
38
|
-
end
|
60
|
+
|
61
|
+
commands = available_commands.keys
|
62
|
+
commands.unshift("help")
|
39
63
|
|
40
64
|
print " "
|
41
65
|
puts commands.sort.join("\n ")
|
42
66
|
puts
|
43
|
-
|
44
|
-
else
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
if File.exists?(app_script_filepath)
|
49
|
-
load app_script_filepath
|
50
|
-
elsif File.exists?(tap_script_filepath)
|
51
|
-
load tap_script_filepath
|
67
|
+
puts "version #{Tap::VERSION} -- #{Tap::HOMEPAGE}"
|
68
|
+
else
|
69
|
+
if available_commands.has_key?(command)
|
70
|
+
# run the script, if it exists
|
71
|
+
load available_commands[command]
|
52
72
|
else
|
53
|
-
|
73
|
+
puts "Unknown command: '#{command}'"
|
74
|
+
puts "Type 'tap help' for usage information."
|
54
75
|
end
|
55
76
|
end
|
56
77
|
rescue
|
57
|
-
if
|
78
|
+
if app.options.debug
|
79
|
+
raise
|
80
|
+
else
|
81
|
+
puts $!.message
|
82
|
+
puts "Type 'tap #{command} --help' for usage information."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# run after script
|
88
|
+
#
|
89
|
+
begin
|
90
|
+
eval(script.config.after.to_s)
|
91
|
+
rescue
|
92
|
+
puts "Error in after script."
|
93
|
+
if app.options.debug
|
58
94
|
raise
|
59
95
|
else
|
60
96
|
puts $!.message
|
61
|
-
|
97
|
+
exit(1)
|
62
98
|
end
|
63
99
|
end
|
data/lib/tap.rb
CHANGED
@@ -1,15 +1,44 @@
|
|
1
|
-
|
1
|
+
# gem_original_require (set in rubygems) is used here to speed up
|
2
|
+
# requires when possible... ok since I'm manually activating the
|
3
|
+
# neeeded gems.
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
gem_original_require 'yaml' # expensive to load
|
7
|
+
gem_original_require 'logger'
|
8
|
+
gem_original_require 'ostruct'
|
9
|
+
gem_original_require 'thread'
|
10
|
+
gem_original_require 'monitor'
|
11
|
+
gem_original_require 'erb'
|
12
|
+
|
13
|
+
# Loading activesupport piecemeal like this cuts the tap load time in half.
|
14
|
+
gem 'activesupport'
|
15
|
+
|
16
|
+
gem_original_require 'active_support/core_ext/array/extract_options.rb'
|
17
|
+
class Array #:nodoc:
|
18
|
+
include ActiveSupport::CoreExtensions::Array::ExtractOptions
|
19
|
+
end
|
20
|
+
gem_original_require 'active_support/core_ext/class.rb'
|
21
|
+
gem_original_require 'active_support/core_ext/module.rb'
|
22
|
+
gem_original_require 'active_support/core_ext/symbol.rb'
|
23
|
+
gem_original_require 'active_support/core_ext/string.rb'
|
24
|
+
gem_original_require 'active_support/core_ext/blank.rb'
|
25
|
+
gem_original_require 'active_support/core_ext/hash/keys.rb'
|
26
|
+
gem_original_require 'active_support/dependencies'
|
27
|
+
class Hash #:nodoc:
|
28
|
+
include ActiveSupport::CoreExtensions::Hash::Keys
|
29
|
+
end
|
2
30
|
|
3
31
|
$:.unshift File.dirname(__FILE__)
|
4
32
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
33
|
+
gem_original_require 'tap/support/audit'
|
34
|
+
gem_original_require 'tap/support/task_configuration'
|
35
|
+
gem_original_require 'tap/support/logger'
|
36
|
+
gem_original_require 'tap/support/templater'
|
37
|
+
gem_original_require 'tap/support/batch_queue'
|
38
|
+
gem_original_require 'tap/support/run_error'
|
39
|
+
gem_original_require 'tap/version'
|
40
|
+
gem_original_require 'tap/root'
|
41
|
+
gem_original_require 'tap/app'
|
42
|
+
gem_original_require 'tap/task'
|
43
|
+
gem_original_require 'tap/workflow'
|
44
|
+
gem_original_require 'tap/file_task'
|
data/lib/tap/app.rb
CHANGED
@@ -1,33 +1,23 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
require 'logger'
|
3
|
-
require 'ostruct'
|
4
|
-
require 'getoptlong'
|
5
|
-
require 'monitor'
|
6
|
-
require 'thread'
|
7
|
-
require 'thwait'
|
8
|
-
require 'erb'
|
9
|
-
|
10
1
|
module Tap
|
11
2
|
|
12
3
|
# == Overview
|
13
4
|
#
|
14
5
|
# App coordinates the setup and running of tasks, and provides an interface
|
15
6
|
# to the application directory structure. App is convenient for use within
|
16
|
-
# scripts, and
|
17
|
-
# utilites or to be integrated within a tap server.
|
7
|
+
# scripts, and provides the basis for the 'tap' command line application.
|
18
8
|
#
|
19
|
-
# All tasks have an App (by default App.instance) which helps
|
20
|
-
#
|
21
|
-
#
|
9
|
+
# All tasks have an App (by default App.instance) which helps initialize the
|
10
|
+
# task by loading configuration templates from the application directory.
|
11
|
+
# Task queue commands are passed to app, and tasks access application-
|
22
12
|
# wide resources like the logger and various options through App.
|
23
13
|
#
|
24
14
|
# task = Task.new {|task, input| input += 1 }
|
25
15
|
# task.app # => App.instance
|
26
|
-
# task.
|
16
|
+
# task.enq 1,2,3
|
27
17
|
#
|
28
|
-
# app.run
|
18
|
+
# task.app.run
|
29
19
|
#
|
30
|
-
# task.
|
20
|
+
# task.results.collect do |audit|
|
31
21
|
# audit._current
|
32
22
|
# end # => [2,3,4]
|
33
23
|
#
|
@@ -38,29 +28,32 @@ module Tap
|
|
38
28
|
# Multithreaded tasks execute cosynchronously, each on their own thread.
|
39
29
|
#
|
40
30
|
# Workflows can be achieved by setting the condition and on_complete blocks
|
41
|
-
# for a task. Tasks are skipped
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# tasks, and these tasks queued for execution.
|
31
|
+
# for a task. Tasks are skipped until their condition block is met by the
|
32
|
+
# queued inputs. When a task finishes, it executes its on_complete block,
|
33
|
+
# thereby allowing results to be dispatched to other tasks.
|
45
34
|
#
|
46
35
|
# In this system, the workflow logic exists among the tasks. App keeps on
|
47
|
-
# running as long as it finds executable tasks in the queue
|
36
|
+
# running as long as it finds executable tasks in the queue, or until it
|
37
|
+
# is stopped or terminated.
|
48
38
|
#
|
49
39
|
# === Error Handling
|
50
40
|
#
|
51
41
|
# When unhandled errors arise during a run, App enters a termination (rescue)
|
52
|
-
# routine. During termination a TerminationError is raised
|
53
|
-
# task so that the task
|
54
|
-
#
|
42
|
+
# routine. During termination a TerminationError is raised in each executing
|
43
|
+
# task so that the task exits, or begins executing its internal error handling
|
44
|
+
# code (perhaps performing rollbacks).
|
55
45
|
#
|
56
46
|
# Additional errors that arise during termination are collected and packaged
|
57
47
|
# with the orignal error into a RunError. By default all errors are logged
|
58
|
-
# and the run exits. If options.debug == true, then the RunError will be
|
59
|
-
# for further handling.
|
48
|
+
# and the run exits. If options.debug == true, then the RunError will be
|
49
|
+
# raised for further handling.
|
60
50
|
#
|
61
51
|
# Note: the task that caused the original unhandled error is no longer executing
|
62
52
|
# when termination begins and thus will not recieve a TerminationError.
|
63
53
|
#
|
54
|
+
#--
|
55
|
+
# === Auditing
|
56
|
+
#++
|
64
57
|
class App < Root
|
65
58
|
include MonitorMixin
|
66
59
|
|
@@ -68,8 +61,7 @@ module Tap
|
|
68
61
|
attr_writer :instance
|
69
62
|
|
70
63
|
# Returns the current instance of App. If no instance has been set,
|
71
|
-
# then a new App with the default configuration will be initialized
|
72
|
-
# to instance.
|
64
|
+
# then a new App with the default configuration will be initialized.
|
73
65
|
def instance
|
74
66
|
@instance ||= App.new
|
75
67
|
end
|
@@ -79,29 +71,31 @@ module Tap
|
|
79
71
|
instance.run(task, *inputs)
|
80
72
|
end
|
81
73
|
|
82
|
-
# Parses the input
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
86
|
-
|
74
|
+
# Parses the input string as YAML, if the string matches the YAML document
|
75
|
+
# specifier (ie it begins with "---\s*\n"). Otherwise returns the string.
|
76
|
+
#
|
77
|
+
# str = {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
|
78
|
+
# Tap::App.parse_yaml(str) # => {'key' => 'value'}
|
79
|
+
# Tap::App.parse_yaml("str") # => "str"
|
80
|
+
def parse_yaml(str)
|
81
|
+
str =~ /^---\s*\n/ ? YAML.load(str) : str
|
87
82
|
end
|
88
83
|
|
84
|
+
# Read the contents of filepath, templates these using ERB, and loads
|
85
|
+
# the result as YAML. Returns nil if filepath does not exist.
|
89
86
|
def read_erb_yaml(filepath)
|
90
|
-
return nil
|
87
|
+
return nil if !File.exists?(filepath) || File.directory?(filepath)
|
91
88
|
|
92
89
|
input = File.read(filepath)
|
93
90
|
input = ERB.new(input).result
|
94
91
|
YAML.load(input)
|
95
92
|
end
|
96
|
-
|
97
|
-
def make_config(task, config, version=nil)
|
98
|
-
|
99
|
-
end
|
100
93
|
end
|
101
94
|
|
102
95
|
attr_reader :options, :logger, :queue, :state
|
103
96
|
attr_accessor :map
|
104
97
|
|
98
|
+
# The constants defining the possible App states.
|
105
99
|
module State
|
106
100
|
READY = 0
|
107
101
|
RUN = 1
|
@@ -111,26 +105,26 @@ module Tap
|
|
111
105
|
|
112
106
|
DEFAULT_MAX_THREADS = 10
|
113
107
|
|
114
|
-
# Creates a new App with the given configuration.
|
115
|
-
# configuration options.
|
108
|
+
# Creates a new App with the given configuration.
|
109
|
+
# See reconfigure for configuration options.
|
116
110
|
def initialize(config={})
|
117
111
|
super()
|
118
112
|
|
119
113
|
@queue = Support::BatchQueue.new
|
120
|
-
@threads = []
|
114
|
+
@threads = [].extend(MonitorMixin)
|
121
115
|
@thread_queue = nil
|
122
116
|
@main_thread = nil
|
123
117
|
@state = State::READY
|
124
|
-
|
118
|
+
|
125
119
|
# defaults must be provided for options and logging to ensure
|
126
120
|
# that they will be initialized by reconfigure
|
127
121
|
self.reconfigure( {
|
128
|
-
:options => {}, :logger => {}, :map => {}
|
122
|
+
:options => {}, :logger => {}, :map => {}
|
129
123
|
}.merge(config) )
|
130
124
|
end
|
131
125
|
|
132
|
-
# Returns the
|
133
|
-
def config
|
126
|
+
# Returns the configuration of self.
|
127
|
+
def config
|
134
128
|
{:root => self.root,
|
135
129
|
:directories => self.directories,
|
136
130
|
:absolute_paths => self.absolute_paths,
|
@@ -138,18 +132,26 @@ module Tap
|
|
138
132
|
:logger => {
|
139
133
|
:device => self.logger.logdev.dev,
|
140
134
|
:level => self.logger.level,
|
141
|
-
:datetime_format => self.logger.datetime_format}
|
142
|
-
:load_paths => self.load_paths}
|
135
|
+
:datetime_format => self.logger.datetime_format}}
|
143
136
|
end
|
144
137
|
|
145
|
-
# Reconfigures
|
138
|
+
# Reconfigures self with the input configurations; other configurations are not affected.
|
139
|
+
#
|
140
|
+
# app = Tap::App.new :root => "/root", :directories => {:dir => 'path/to/dir'}
|
141
|
+
# app.reconfigure(
|
142
|
+
# :root => "./new/root",
|
143
|
+
# :logger => {:level => Logger::DEBUG})
|
144
|
+
#
|
145
|
+
# app.root # => File.expand_path("./new/root")
|
146
|
+
# app[:dir] # => File.expand_path("./new/root/path/to/dir")
|
147
|
+
# app.logger.level # => Logger::DEBUG
|
146
148
|
#
|
147
149
|
# Available configurations:
|
148
|
-
# root:: resets the root directory of
|
149
|
-
# directories:: resets
|
150
|
-
# are reset. use app[
|
151
|
-
# options:: resets the application options (note ALL options are reset.
|
152
|
-
# app.options.opt= to set a single option)
|
150
|
+
# root:: resets the root directory of self using root=
|
151
|
+
# directories:: resets directory aliases using directories= (note ALL
|
152
|
+
# aliases are reset. use app[da]= to set a single alias)
|
153
|
+
# options:: resets the application options (note ALL options are reset.
|
154
|
+
# use app.options.opt= to set a single option)
|
153
155
|
# logger:: creates and sets a new logger from the configuration
|
154
156
|
#
|
155
157
|
# Available logger configurations and defaults:
|
@@ -162,9 +164,9 @@ module Tap
|
|
162
164
|
# - Additional configurations may be added in subclasses using handle_configuration
|
163
165
|
def reconfigure(config={})
|
164
166
|
config = config.symbolize_keys
|
165
|
-
|
167
|
+
|
166
168
|
# ensure critical keys are evaluated in the proper order
|
167
|
-
keys = [:root, :directories, :absolute_paths, :options
|
169
|
+
keys = [:root, :directories, :absolute_paths, :options]
|
168
170
|
config.keys.each do |key|
|
169
171
|
keys << key unless keys.include?(key)
|
170
172
|
end
|
@@ -196,33 +198,18 @@ module Tap
|
|
196
198
|
self.logger = logger
|
197
199
|
when :map
|
198
200
|
self.map = value
|
199
|
-
when :load_paths
|
200
|
-
self.load_paths = value
|
201
|
-
when :loadable
|
202
|
-
value.each {|dir| self.load_paths << self[dir]}
|
203
201
|
else
|
204
|
-
|
202
|
+
unless handle_configuation(key, value)
|
203
|
+
if block_given?
|
204
|
+
yield(key, value)
|
205
|
+
else
|
206
|
+
raise ArgumentError.new("Unknown configuration: #{key}")
|
207
|
+
end
|
208
|
+
end
|
205
209
|
end
|
206
210
|
end
|
207
|
-
|
208
|
-
|
209
|
-
#
|
210
|
-
# Dependencies and reloading
|
211
|
-
#
|
212
|
-
|
213
|
-
def load_paths
|
214
|
-
Dependencies.load_paths
|
215
|
-
end
|
216
|
-
|
217
|
-
def load_paths=(paths)
|
218
|
-
Dependencies.load_paths = paths
|
219
|
-
end
|
220
|
-
|
221
|
-
def reload
|
222
|
-
# TODO - logging, maybe through Dependencies as
|
223
|
-
# RAILS_DEFAULT_LOGGER = logger (problem with this during server?)
|
224
|
-
# Dependencies.log_activity = true
|
225
|
-
Dependencies.clear
|
211
|
+
|
212
|
+
self
|
226
213
|
end
|
227
214
|
|
228
215
|
#
|
@@ -243,81 +230,95 @@ module Tap
|
|
243
230
|
logger.add(level, msg, action.to_s) unless options.quiet
|
244
231
|
end
|
245
232
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
unless options.quiet || !@monitoring
|
260
|
-
action.nil? ?
|
261
|
-
logger.tick :
|
262
|
-
logger.format_add(level, msg, action.to_s) {|format| "\n" + format}
|
233
|
+
# Formatted log. Works like log, but passes the current log format to the
|
234
|
+
# block and uses whatever format the block returns. The format recieves
|
235
|
+
# the following arguments like so:
|
236
|
+
#
|
237
|
+
# format % [severity, timestamp, (action || '--'), msg]
|
238
|
+
#
|
239
|
+
# By default, if you don't specify a block, flog just chomps a newline off
|
240
|
+
# the format, so your log will be inline.
|
241
|
+
def flog(action="", msg="", level=Logger::INFO) # :yields: format
|
242
|
+
unless options.quiet
|
243
|
+
logger.format_add(level, msg, action) do |format|
|
244
|
+
block_given? ? yield(format) : format.chomp("\n")
|
245
|
+
end
|
263
246
|
end
|
264
247
|
end
|
265
248
|
|
266
|
-
def
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
249
|
+
# def monitor(action="beginning", msg="", &block)
|
250
|
+
# if options.quiet
|
251
|
+
# yield
|
252
|
+
# nil
|
253
|
+
# else
|
254
|
+
# @monitoring = true
|
255
|
+
# result = logger.monitor(action, msg, &block)
|
256
|
+
# @monitoring = false
|
257
|
+
# result
|
258
|
+
# end
|
259
|
+
# end
|
260
|
+
#
|
261
|
+
# def tick_monitor(action=nil, msg="", level=Logger::INFO)
|
262
|
+
# unless options.quiet || !@monitoring
|
263
|
+
# action.nil? ?
|
264
|
+
# logger.tick :
|
265
|
+
# logger.format_add(level, msg, action.to_s) {|format| "\n" + format}
|
266
|
+
# end
|
267
|
+
# end
|
268
|
+
#
|
269
|
+
# def monitor_stats(hash)
|
270
|
+
# unless options.quiet || !@monitoring
|
271
|
+
# logger << "\n"
|
272
|
+
# logger << hash.stringify_keys.to_yaml
|
273
|
+
# end
|
274
|
+
# end
|
272
275
|
|
273
276
|
#
|
274
277
|
# Task methods
|
275
278
|
#
|
276
279
|
|
277
|
-
#
|
278
|
-
#
|
279
|
-
# t = Task.new :key => 'value'
|
280
|
-
# t = app.task(t, :key => 'another')
|
281
|
-
# t.config[:key] # => 'another'
|
282
|
-
#
|
283
|
-
# If a task name is given, then the appropriate class will be loaded and
|
284
|
-
# instantiated into a task with given name. The class is the de-versioned
|
285
|
-
# and camelized name, or the class as specified in map.
|
280
|
+
# Instantiates the specifed task with config (if provided).
|
286
281
|
#
|
287
|
-
# app.map
|
282
|
+
# app.map = {"mapped-task" => "AnotherTask"}
|
288
283
|
#
|
289
284
|
# t = app.task('mapped-task-1.0', :key => 'value')
|
290
|
-
# t.class # =>
|
285
|
+
# t.class # => AnotherTask
|
291
286
|
# t.name # => "mapped-task-1.0"
|
292
287
|
# t.config[:key] # => 'value'
|
293
288
|
#
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
289
|
+
# A new task is instantiated for each call to task; tasks may share the
|
290
|
+
# same name. The task class is the de-versioned and camelized task name,
|
291
|
+
# or the class specified in map. The task class will be auto-loaded using
|
292
|
+
# Dependencies, if needed.
|
293
|
+
#
|
294
|
+
# A LookupError is raised if the task class cannot be found.
|
295
|
+
def task(task_name, config=nil)
|
296
|
+
begin
|
298
297
|
# lookup the corresponding class and instantiate
|
299
|
-
constants = task_class_name(
|
298
|
+
constants = task_class_name(task_name).split('::')
|
300
299
|
task_class = constants.inject(Object) do |klass, const|
|
301
|
-
|
302
|
-
klass.const_get(const)
|
303
|
-
rescue(NameError)
|
304
|
-
raise "unknown task '#{td}'"
|
305
|
-
end
|
300
|
+
klass.const_get(const)
|
306
301
|
end
|
307
|
-
task_class.new(
|
302
|
+
task_class.new(task_name, config)
|
303
|
+
rescue(NameError)
|
304
|
+
raise LookupError.new("unknown task '#{task_name}'")
|
308
305
|
end
|
309
|
-
|
310
|
-
# reconfigure if a config is provided
|
311
|
-
t.config = config unless config.nil?
|
312
|
-
t
|
313
306
|
end
|
314
307
|
|
308
|
+
# Returns the class name of the specified task. If a task descriptor
|
309
|
+
# is given, task_class_name returns the de-versioned, camelized
|
310
|
+
# descriptor, or the class name as specified in map.
|
311
|
+
#
|
312
|
+
# t = Task.new
|
313
|
+
# app.map = {"mapped-task" => "AnotherTask"}
|
314
|
+
# app.task_class_name(t) # => "Tap::Task"
|
315
|
+
# app.task_class_name('mapped-task-1.0') # => "AnotherTask"
|
315
316
|
def task_class_name(td)
|
316
317
|
case td
|
317
|
-
when Tap::Task then td.class.to_s
|
318
|
+
when Tap::Task::Base then td.class.to_s
|
318
319
|
else
|
319
320
|
# de-version and resolve using map
|
320
|
-
name, version = deversion(td)
|
321
|
+
name, version = deversion(td.to_s)
|
321
322
|
map.has_key?(name) ? map[name] : name.camelize
|
322
323
|
end
|
323
324
|
end
|
@@ -352,55 +353,55 @@ module Tap
|
|
352
353
|
#
|
353
354
|
# Execution methods
|
354
355
|
#
|
355
|
-
|
356
|
+
|
356
357
|
# Dequeues the task and inputs and executes the task with the current task inputs.
|
357
|
-
#
|
358
|
-
#
|
358
|
+
#
|
359
|
+
# Execute differs from run in several significant ways:
|
360
|
+
# - Only the provided task will be executed (ie the task batch will not be executed)
|
361
|
+
# - Execution is on the current thread even if the task is multithreaded.
|
362
|
+
# - No error handling is performed
|
359
363
|
#
|
360
|
-
#
|
361
|
-
#
|
362
|
-
|
363
|
-
|
364
|
-
#log(:execute, task.to_dir, Logger::DEBUG) if options.debug
|
365
|
-
|
366
|
-
unless state == State::READY || state == State::RUN
|
367
|
-
raise "cannot execute unless application state is READY or RUN"
|
368
|
-
end
|
369
|
-
|
370
|
-
if deq = queue.deq(task)
|
371
|
-
task, inputs = deq
|
372
|
-
task.execute(*inputs)
|
373
|
-
end
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
# Enqueues the task and inputs (if provided), then iterates through the queue
|
378
|
-
# executing tasks using this execution cycle:
|
379
|
-
#
|
380
|
-
# for each enqueued task:
|
381
|
-
# next unless executable?(task)
|
364
|
+
# Execute can only run if the application state is READY or RUN.
|
365
|
+
# def execute(task)
|
366
|
+
# synchronize do
|
367
|
+
# #log(:execute, task.to_dir, Logger::DEBUG) if options.debug
|
382
368
|
#
|
383
|
-
#
|
384
|
-
#
|
385
|
-
#
|
386
|
-
#
|
387
|
-
#
|
388
|
-
#
|
389
|
-
#
|
390
|
-
#
|
391
|
-
#
|
392
|
-
#
|
369
|
+
# unless state == State::READY || state == State::RUN
|
370
|
+
# raise "cannot execute unless application state is READY or RUN"
|
371
|
+
# end
|
372
|
+
#
|
373
|
+
# if deq = queue.deq(task)
|
374
|
+
# task, inputs = deq
|
375
|
+
# task.execute(*inputs)
|
376
|
+
# end
|
377
|
+
# end
|
378
|
+
# end
|
379
|
+
|
380
|
+
# Enqueues the task and inputs (if provided), then begins the run cycle.
|
381
|
+
#
|
382
|
+
# During the run cycle, the app will iterate through the queue and execute
|
383
|
+
# tasks. Tasks will be executed ONLY if task.executable? returns true with
|
384
|
+
# the currently enqueued inputs. Tasks are executed on their own thread if
|
385
|
+
# task.multithread? is true.
|
386
|
+
#
|
387
|
+
# As a result of these rules, the queue may still contain unexecuted tasks
|
388
|
+
# a the end of run. These tasks require additional inputs or conditions
|
389
|
+
# in order to execute. Execution errors are handled as described above.
|
390
|
+
#
|
391
|
+
# Tasks may be specified as a task descriptor (ex: "sample/task") or provided
|
392
|
+
# directly. Task descriptors are resolved using task.
|
393
|
+
#
|
394
|
+
# run returns the specified task on completion.
|
393
395
|
def run(td=nil, *inputs)
|
394
396
|
synchronize do
|
395
397
|
# TODO: log starting run
|
396
398
|
|
397
399
|
# lookup and enqueue the task, if provided
|
398
|
-
specified_task =
|
400
|
+
specified_task = (td == nil || td.kind_of?(Tap::Task::Base) ? td : task(td))
|
399
401
|
queue.enq(specified_task, *inputs) if specified_task
|
400
402
|
|
401
403
|
# generate threading variables
|
402
404
|
self.state = State::RUN
|
403
|
-
self.main_thread = Thread.current
|
404
405
|
max_threads = options.max_threads || DEFAULT_MAX_THREADS
|
405
406
|
self.thread_queue = max_threads > 0 ? Queue.new : nil
|
406
407
|
|
@@ -461,8 +462,8 @@ module Tap
|
|
461
462
|
# executed. collect any errors that arise during
|
462
463
|
# termination.
|
463
464
|
clear_thread_queue
|
464
|
-
errors =
|
465
|
-
|
465
|
+
errors = clear_threads(false)
|
466
|
+
|
466
467
|
# handle the errors accordingly
|
467
468
|
if options.debug
|
468
469
|
raise Tap::Support::RunError.new($!, errors)
|
@@ -477,7 +478,6 @@ module Tap
|
|
477
478
|
|
478
479
|
# reset run variables
|
479
480
|
self.thread_queue = nil
|
480
|
-
self.main_thread = nil
|
481
481
|
self.state = State::READY
|
482
482
|
|
483
483
|
# TODO: log run complete
|
@@ -495,26 +495,20 @@ module Tap
|
|
495
495
|
self.state = State::STOP if state == State::RUN
|
496
496
|
end
|
497
497
|
|
498
|
-
#
|
499
|
-
#
|
500
|
-
#
|
501
|
-
#
|
502
|
-
#
|
503
|
-
#
|
504
|
-
#
|
505
|
-
#
|
506
|
-
#
|
507
|
-
# rollbacks. (ie terminate should be used as a graceful
|
508
|
-
# exit)
|
498
|
+
# Signals a running application to terminate executing tasks
|
499
|
+
# by setting state = State::TERMINATE. When running tasks
|
500
|
+
# reach a termination check, the task will raise a Termination
|
501
|
+
# error, thus allowing executing tasks to invoke their specific
|
502
|
+
# error handling code, perhaps performing rollbacks.
|
503
|
+
#
|
504
|
+
# Termination checks can be manually specified in a task
|
505
|
+
# using the check_terminate method. Termination checks
|
506
|
+
# automatically occur before each task execution.
|
509
507
|
#
|
510
508
|
# Does nothing unless state is State::RUN or State::STOP.
|
511
|
-
def terminate(error=TerminateError.new)
|
509
|
+
def terminate#(error=TerminateError.new)
|
512
510
|
if state == State::RUN || state == State::STOP
|
513
511
|
self.state = State::TERMINATE
|
514
|
-
|
515
|
-
# the terminate error must be raised on the main thread
|
516
|
-
# so that the App will enter the termination (rescue) block
|
517
|
-
main_thread.raise(error)
|
518
512
|
end
|
519
513
|
end
|
520
514
|
|
@@ -542,7 +536,7 @@ module Tap
|
|
542
536
|
|
543
537
|
# Sets the condition block for the task. If the task is batched,
|
544
538
|
# then the condition will be set for each task in the batch.
|
545
|
-
def condition(task, &block) # :yields: task,
|
539
|
+
def condition(task, &block) # :yields: task, audited_inputs
|
546
540
|
task.batch.each {|t| t.condition(&block)}
|
547
541
|
end
|
548
542
|
|
@@ -565,8 +559,7 @@ module Tap
|
|
565
559
|
# The condition block between these tasks will be set to the
|
566
560
|
# input block, if provided. Batched tasks will have the pattern
|
567
561
|
# set for each task in the batch.
|
568
|
-
def sequence(*tasks, &block) # :yields: task,
|
569
|
-
tasks.collect! {|td| task(td)}
|
562
|
+
def sequence(*tasks, &block) # :yields: task, audited_inputs
|
570
563
|
current_task = tasks.shift
|
571
564
|
|
572
565
|
tasks.each do |next_task|
|
@@ -582,7 +575,7 @@ module Tap
|
|
582
575
|
# source completes. The condition block for the targets will
|
583
576
|
# be set to the input block, if provided. Batched tasks will have
|
584
577
|
# the pattern set for each task in the batch.
|
585
|
-
def fork(source, *targets, &block) # :yields: task,
|
578
|
+
def fork(source, *targets, &block) # :yields: task, audited_inputs
|
586
579
|
on_complete(source) do |results|
|
587
580
|
targets.each do |target|
|
588
581
|
# forking requires new audit trails because the same
|
@@ -601,7 +594,7 @@ module Tap
|
|
601
594
|
# completes. The condition block for the target will be set to the
|
602
595
|
# input block, if provided. Batched tasks will have the pattern set
|
603
596
|
# for each task in the batch.
|
604
|
-
def merge(target, *sources, &block) # :yields: task,
|
597
|
+
def merge(target, *sources, &block) # :yields: task, audited_inputs
|
605
598
|
sources.each do |source|
|
606
599
|
# merging can use the existing audit trails... each distinct
|
607
600
|
# input is getting sent to one place (the target)
|
@@ -620,7 +613,7 @@ module Tap
|
|
620
613
|
end
|
621
614
|
|
622
615
|
attr_writer :state
|
623
|
-
attr_accessor :thread_queue, :
|
616
|
+
attr_accessor :thread_queue, :threads
|
624
617
|
|
625
618
|
private
|
626
619
|
|
@@ -635,6 +628,11 @@ module Tap
|
|
635
628
|
# terminated but still running. Raise another
|
636
629
|
# termination error to enter the termination
|
637
630
|
# (rescue) code.
|
631
|
+
|
632
|
+
# BUG - if things just work out that way, terminate can
|
633
|
+
# be set from a thread that had an error, then control
|
634
|
+
# switches such that this is raised on the main thread.
|
635
|
+
# in this case it looks like THIS is the original error.
|
638
636
|
raise TerminateError.new
|
639
637
|
end
|
640
638
|
|
@@ -662,75 +660,74 @@ module Tap
|
|
662
660
|
queue.priority_enq(task, *inputs) unless task.nil?
|
663
661
|
end
|
664
662
|
end
|
665
|
-
|
666
|
-
def clear_threads
|
667
|
-
|
663
|
+
|
664
|
+
def clear_threads(raise_errors=true)
|
665
|
+
threads.synchronize do
|
666
|
+
errors = []
|
667
|
+
return errors if threads.empty?
|
668
668
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
669
|
+
# clears threads gracefully by enqueuing nils, to break
|
670
|
+
# the threads out of their loops, then waiting for the
|
671
|
+
# threads to work through the queue to the nils
|
672
|
+
#
|
673
|
+
threads.size.times { thread_queue.enq nil }
|
674
|
+
while true
|
675
|
+
# TODO -- add a time out?
|
676
|
+
|
677
|
+
threads.dup.each do |thread|
|
678
|
+
next if thread.alive?
|
679
|
+
threads.delete(thread)
|
680
|
+
error = thread["error"]
|
681
|
+
|
682
|
+
next if error.nil?
|
683
|
+
raise error if raise_errors
|
684
|
+
|
685
|
+
errors << error
|
686
|
+
end
|
687
|
+
|
688
|
+
break if threads.empty?
|
689
|
+
Thread.pass
|
690
|
+
end
|
691
|
+
|
692
|
+
errors
|
675
693
|
end
|
676
|
-
threads.clear
|
677
694
|
end
|
678
695
|
|
679
696
|
def start_thread
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
#
|
697
|
+
threads.synchronize do
|
698
|
+
# start a new thread and add it to threads.
|
699
|
+
# threads simply loop and wait for a task to
|
700
|
+
# be queued. the thread will block until a
|
701
|
+
# task is available (due to thread_queue.deq)
|
702
|
+
#
|
703
|
+
# TODO -- track thread index like?
|
704
|
+
# thread["index"] = threads.length
|
705
|
+
threads << Thread.new do
|
706
|
+
# TODO - log thread start
|
686
707
|
|
687
|
-
execution_loop do
|
688
|
-
task, inputs = thread_queue.deq
|
689
|
-
break if task.nil?
|
690
|
-
|
691
|
-
# TODO: log execute task on thread #
|
692
708
|
begin
|
693
|
-
|
709
|
+
execution_loop do
|
710
|
+
task, inputs = thread_queue.deq
|
711
|
+
break if task.nil?
|
712
|
+
|
713
|
+
# TODO: log execute task on thread #
|
714
|
+
task.execute(*inputs)
|
715
|
+
end
|
694
716
|
rescue
|
695
717
|
# unless you're already terminating,
|
696
718
|
# an unhandled error should immediately
|
697
719
|
# terminate all threads
|
698
|
-
|
699
|
-
|
700
|
-
else
|
701
|
-
terminate($!)
|
702
|
-
end
|
720
|
+
terminate unless state == State::TERMINATE
|
721
|
+
Thread.current["error"] = $! unless $!.kind_of?(TerminateError)
|
703
722
|
end
|
704
|
-
end
|
705
|
-
end
|
706
|
-
end
|
707
|
-
|
708
|
-
def terminate_threads
|
709
|
-
# terminate each thread by raising an exception
|
710
|
-
# wait for the thread to exit and gather any
|
711
|
-
# errors that arise that are not from the
|
712
|
-
# termination exception.
|
713
|
-
errors = []
|
714
|
-
threads.each_with_index do |thread, index|
|
715
|
-
next unless thread.alive?
|
716
|
-
|
717
|
-
begin
|
718
|
-
thread.raise TerminateError.new
|
719
|
-
thread.join
|
720
|
-
rescue TerminateError
|
721
|
-
# do not track these as errors
|
722
|
-
# arising from termination
|
723
|
-
rescue
|
724
|
-
# track the thread of the error
|
725
|
-
# with the error itself
|
726
|
-
errors[index] = $!
|
727
723
|
end
|
728
724
|
end
|
729
|
-
threads.clear
|
730
|
-
|
731
|
-
errors
|
732
725
|
end
|
733
726
|
|
727
|
+
# LookupErrors are raised for errors during dependency lookup
|
728
|
+
class LookupError < RuntimeError # :nodoc:
|
729
|
+
end
|
730
|
+
|
734
731
|
# TerminateErrors are raised to kill executing tasks when terminate
|
735
732
|
# is called on an running App.
|
736
733
|
class TerminateError < RuntimeError
|