abid 0.3.0.pre.alpha.3 → 0.3.0.pre.alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/abid.gemspec +1 -2
- data/exe/abidsc +1 -1
- data/lib/abid.rb +3 -30
- data/lib/abid/application.rb +83 -13
- data/lib/abid/cli/assume.rb +7 -8
- data/lib/abid/cli/list.rb +2 -2
- data/lib/abid/cli/migrate.rb +1 -1
- data/lib/abid/cli/revoke.rb +9 -10
- data/lib/abid/config.rb +2 -0
- data/lib/abid/dsl/abid_job.rb +58 -0
- data/lib/abid/dsl/actions.rb +36 -0
- data/lib/abid/dsl/job.rb +58 -0
- data/lib/abid/dsl/job_manager.rb +53 -0
- data/lib/abid/dsl/mixin.rb +52 -0
- data/lib/abid/dsl/params_spec.rb +64 -0
- data/lib/abid/dsl/play.rb +35 -0
- data/lib/abid/dsl/play_core.rb +354 -0
- data/lib/abid/dsl/rake_job.rb +41 -0
- data/lib/abid/dsl/syntax.rb +34 -0
- data/lib/abid/dsl/task.rb +53 -0
- data/lib/abid/engine.rb +36 -6
- data/lib/abid/engine/executor.rb +30 -48
- data/lib/abid/engine/process.rb +102 -68
- data/lib/abid/engine/process_manager.rb +72 -28
- data/lib/abid/engine/scheduler.rb +24 -30
- data/lib/abid/engine/waiter.rb +20 -30
- data/lib/abid/engine/worker_manager.rb +26 -60
- data/lib/abid/environment.rb +12 -27
- data/lib/abid/error.rb +2 -0
- data/lib/abid/params_format.rb +29 -12
- data/lib/abid/rake_extensions.rb +11 -3
- data/lib/abid/state_manager.rb +40 -0
- data/lib/abid/state_manager/state.rb +52 -114
- data/lib/abid/state_manager/state_service.rb +88 -0
- data/lib/abid/status.rb +63 -0
- data/lib/abid/version.rb +1 -1
- metadata +19 -32
- data/lib/Abidfile.rb +0 -1
- data/lib/abid/dsl_definition.rb +0 -29
- data/lib/abid/job.rb +0 -67
- data/lib/abid/job_manager.rb +0 -22
- data/lib/abid/mixin_task.rb +0 -29
- data/lib/abid/params_parser.rb +0 -50
- data/lib/abid/play.rb +0 -66
- data/lib/abid/play_core.rb +0 -53
- data/lib/abid/rake_extensions/task.rb +0 -41
- data/lib/abid/state_manager/database.rb +0 -40
- data/lib/abid/state_manager/state_proxy.rb +0 -65
- data/lib/abid/task.rb +0 -123
- data/lib/abid/task_manager.rb +0 -61
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'abid/dsl/job'
|
2
|
+
|
3
|
+
module Abid
|
4
|
+
module DSL
|
5
|
+
# Rake::Task wrapper.
|
6
|
+
class RakeJob < Job
|
7
|
+
def volatile?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def worker
|
12
|
+
:default
|
13
|
+
end
|
14
|
+
|
15
|
+
def params
|
16
|
+
{}
|
17
|
+
end
|
18
|
+
|
19
|
+
def concerned?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def needed?
|
24
|
+
task.needed?
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute(args)
|
28
|
+
task.application.trace "** Execute (dry run) #{task.name}" if dryrun?
|
29
|
+
return if dryrun? || preview?
|
30
|
+
|
31
|
+
task.execute(args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def prerequisites
|
35
|
+
task.prerequisite_tasks.map do |preq|
|
36
|
+
task.application.job_manager.bind(preq.name, {})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Abid
|
2
|
+
module DSL
|
3
|
+
module Syntax
|
4
|
+
def play(*args, &block)
|
5
|
+
Task.define_task(*args, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def mixin(*args, &block)
|
9
|
+
MixinTask.define_task(*args, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def define_worker(name, thread_count)
|
13
|
+
Abid.global.engine.worker_manager.define(name, thread_count)
|
14
|
+
end
|
15
|
+
|
16
|
+
def global_mixin(&block)
|
17
|
+
Abid.application.global_mixin.class_eval(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def invoke(task, *args, **params)
|
21
|
+
Abid.global.engine.invoke(task, *args, **params)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @yieldparam top_level_tasks [Array<String>]
|
25
|
+
# @yieldparam summary [Hash]
|
26
|
+
# @yieldparam errors [Array<Exception>]
|
27
|
+
def after_all(&block)
|
28
|
+
Abid.application.after_all_actions << block
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
extend Abid::DSL::Syntax
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Abid
|
2
|
+
module DSL
|
3
|
+
# `play` task is defined as an instance of Abid::DSL::Task.
|
4
|
+
#
|
5
|
+
# play(:foo) { ... } #=> #<Abid::DSL::Task ...>
|
6
|
+
#
|
7
|
+
class Task < Rake::Task
|
8
|
+
attr_reader :internal
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super
|
12
|
+
initialize_internal
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_internal
|
16
|
+
@internal = Play.create(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def enhance(deps = nil, &block)
|
20
|
+
@internal.module_eval(&block) if block_given?
|
21
|
+
super(deps)
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@internal_definitions.clear
|
26
|
+
initialize_internal
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def bind(params = {})
|
31
|
+
AbidJob.new(self, params)
|
32
|
+
end
|
33
|
+
|
34
|
+
def params_spec
|
35
|
+
internal.params_spec
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# `mixin` task is defined as an instance of Abid::DSL::MixinTask.
|
40
|
+
#
|
41
|
+
# mixin(:foo) { ... } #=> #<Abid::DSL::MixinTask ...>
|
42
|
+
#
|
43
|
+
class MixinTask < Task
|
44
|
+
def initialize_internal
|
45
|
+
@internal = Mixin.create(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
def bind(*_args)
|
49
|
+
raise 'mixin is not executable'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/abid/engine.rb
CHANGED
@@ -1,13 +1,43 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'abid/engine/executor'
|
4
|
+
require 'abid/engine/process_manager'
|
5
|
+
require 'abid/engine/process'
|
6
|
+
require 'abid/engine/scheduler'
|
7
|
+
require 'abid/engine/worker_manager'
|
8
|
+
require 'abid/engine/waiter'
|
9
|
+
|
1
10
|
module Abid
|
2
11
|
# Engine module operates task execution.
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
12
|
+
class Engine
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def initialize(env)
|
16
|
+
@env = env
|
17
|
+
@process_manager = ProcessManager.new(self)
|
18
|
+
@worker_manager = WorkerManager.new(self)
|
19
|
+
end
|
20
|
+
attr_reader :process_manager, :worker_manager
|
21
|
+
def_delegators :@env, :options, :state_manager, :logger
|
22
|
+
def_delegators :process_manager, :summary, :errors
|
23
|
+
|
24
|
+
# @param name [String] task name
|
25
|
+
# @param params [Hash]
|
26
|
+
# @param args [Array]
|
27
|
+
# @return [(Symbol, Exception)] pair of result and error.
|
28
|
+
def invoke(name, *args, **params)
|
29
|
+
task = @env.application.job_manager[name, params]
|
30
|
+
process_manager.invoke(task, args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def kill(error)
|
34
|
+
worker_manager.kill
|
35
|
+
process_manager.kill(error)
|
7
36
|
end
|
8
37
|
|
9
|
-
def
|
10
|
-
|
38
|
+
def shutdown
|
39
|
+
process_manager.shutdown
|
40
|
+
worker_manager.shutdown
|
11
41
|
end
|
12
42
|
end
|
13
43
|
end
|
data/lib/abid/engine/executor.rb
CHANGED
@@ -1,21 +1,22 @@
|
|
1
1
|
module Abid
|
2
|
-
|
2
|
+
class Engine
|
3
3
|
# @!visibility private
|
4
4
|
|
5
|
-
# Executor operates each
|
5
|
+
# Executor operates each job execution.
|
6
6
|
class Executor
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(process, args)
|
8
|
+
@process = process
|
9
|
+
@job = process.job
|
9
10
|
@args = args
|
10
11
|
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
12
|
+
@state = @process.state_service.find
|
13
|
+
@prerequisites = process.prerequisites
|
14
|
+
@worker = @process.engine.worker_manager[@job.worker]
|
14
15
|
end
|
15
16
|
|
16
|
-
# Check if the
|
17
|
+
# Check if the job should be executed.
|
17
18
|
#
|
18
|
-
# @return [Boolean] false if the
|
19
|
+
# @return [Boolean] false if the job should not be executed.
|
19
20
|
def prepare
|
20
21
|
return unless @process.prepare
|
21
22
|
return false if precheck_to_cancel
|
@@ -23,11 +24,11 @@ module Abid
|
|
23
24
|
true
|
24
25
|
end
|
25
26
|
|
26
|
-
# Start processing the
|
27
|
+
# Start processing the job.
|
27
28
|
#
|
28
|
-
# The
|
29
|
+
# The job is executed asynchronously.
|
29
30
|
#
|
30
|
-
# @return [Boolean] false if the
|
31
|
+
# @return [Boolean] false if the job is not executed
|
31
32
|
def start
|
32
33
|
return false unless @prerequisites.all?(&:complete?)
|
33
34
|
|
@@ -39,37 +40,28 @@ module Abid
|
|
39
40
|
true
|
40
41
|
end
|
41
42
|
|
42
|
-
def capture_exception
|
43
|
-
yield
|
44
|
-
rescue StandardError, ScriptError => error
|
45
|
-
@process.quit(error)
|
46
|
-
rescue Exception => exception
|
47
|
-
# TODO: exit immediately when fatal error occurs.
|
48
|
-
@process.quit(exception)
|
49
|
-
end
|
50
|
-
|
51
43
|
private
|
52
44
|
|
53
|
-
# Cancel the
|
45
|
+
# Cancel the job if it should be.
|
54
46
|
# @return [Boolean] true if cancelled
|
55
47
|
def precheck_to_cancel
|
56
|
-
return false if @job.
|
48
|
+
return false if @job.repair?
|
57
49
|
return false unless @state.failed?
|
58
|
-
return false if @
|
50
|
+
return false if @process.root?
|
59
51
|
@process.cancel(Error.new('task has been failed'))
|
60
52
|
end
|
61
53
|
|
62
|
-
# Skip the
|
54
|
+
# Skip the job if it should be.
|
63
55
|
# @return [Boolean] true if skipped
|
64
56
|
def precheck_to_skip
|
65
|
-
return @process.skip unless @job.
|
57
|
+
return @process.skip unless @job.concerned?
|
66
58
|
|
67
|
-
return false if @job.
|
59
|
+
return false if @job.repair? && !@prerequisites.empty?
|
68
60
|
return false unless @state.successed?
|
69
61
|
@process.skip
|
70
62
|
end
|
71
63
|
|
72
|
-
# Cancel the
|
64
|
+
# Cancel the job if it should be.
|
73
65
|
# @return [Boolean] true if cancelled
|
74
66
|
def check_to_cancel
|
75
67
|
return false if @prerequisites.empty?
|
@@ -77,50 +69,40 @@ module Abid
|
|
77
69
|
@process.cancel
|
78
70
|
end
|
79
71
|
|
80
|
-
# Skip the
|
72
|
+
# Skip the job if it should be.
|
81
73
|
# @return [Boolean] true if skipped
|
82
74
|
def check_to_skip
|
83
|
-
return @process.skip unless @job.
|
75
|
+
return @process.skip unless @job.needed?
|
84
76
|
|
85
77
|
return false if @prerequisites.empty?
|
86
|
-
return false unless @job.
|
78
|
+
return false unless @job.repair?
|
87
79
|
return false if @prerequisites.any?(&:successed?)
|
88
80
|
@process.skip
|
89
81
|
end
|
90
82
|
|
91
|
-
# Post the
|
92
|
-
#
|
83
|
+
# Post the job if no external process executing the same job, wait the
|
84
|
+
# job finished otherwise.
|
93
85
|
def execute_or_wait
|
94
|
-
if @
|
95
|
-
@
|
86
|
+
if @process.state_service.try_start
|
87
|
+
@worker.post { @process.capture_exception { execute } }
|
96
88
|
else
|
97
|
-
Waiter.new(@
|
89
|
+
Waiter.new(@process).wait
|
98
90
|
end
|
99
91
|
end
|
100
92
|
|
101
93
|
def execute
|
102
94
|
_, error = safe_execute
|
103
95
|
|
104
|
-
|
105
|
-
@job.state.finish(error)
|
96
|
+
@process.state_service.finish(error)
|
106
97
|
@process.finish(error)
|
107
98
|
end
|
108
99
|
|
109
100
|
def safe_execute
|
110
|
-
@job.
|
111
|
-
@job.task.execute(@args)
|
101
|
+
@job.execute(@args)
|
112
102
|
true
|
113
103
|
rescue => error
|
114
104
|
[false, error]
|
115
105
|
end
|
116
|
-
|
117
|
-
def call_after_hooks(error)
|
118
|
-
@job.task.call_hooks(:after_invoke, error)
|
119
|
-
true
|
120
|
-
rescue
|
121
|
-
# TODO: Error logging
|
122
|
-
false
|
123
|
-
end
|
124
106
|
end
|
125
107
|
end
|
126
108
|
end
|
data/lib/abid/engine/process.rb
CHANGED
@@ -1,136 +1,170 @@
|
|
1
|
-
require 'concurrent/ivar'
|
2
1
|
require 'forwardable'
|
3
|
-
require 'monitor'
|
4
2
|
|
5
3
|
module Abid
|
6
|
-
|
4
|
+
class Engine
|
7
5
|
# @!visibility private
|
8
6
|
|
9
|
-
# Process object manages the
|
7
|
+
# Process object manages the job execution status.
|
10
8
|
#
|
11
9
|
# You should retrive a process object via Job#process.
|
12
10
|
# Do not create a process object by Process.new constructor.
|
13
11
|
#
|
14
|
-
# A process object has an internal status of the
|
15
|
-
# the task result (Process#result).
|
12
|
+
# A process object has an internal status of the job execution.
|
16
13
|
#
|
17
14
|
# An initial status is :unscheduled.
|
18
15
|
# When Process#prepare is called, the status gets :pending.
|
19
|
-
# When Process#execute is called and the
|
20
|
-
# the status gets :running. When the
|
21
|
-
# :
|
16
|
+
# When Process#execute is called and the job is posted to a thread pool,
|
17
|
+
# the status gets :running. When the job is finished, the status gets
|
18
|
+
# :successed or :failed.
|
22
19
|
#
|
23
|
-
# process = Job['
|
20
|
+
# process = Job['job_name'].process
|
24
21
|
# process.prepare
|
25
22
|
# process.start
|
26
23
|
# process.wait
|
27
|
-
# process.
|
24
|
+
# process.status #=> :successed or :failed
|
28
25
|
#
|
29
26
|
# Possible status are:
|
30
27
|
#
|
31
28
|
# <dl>
|
32
29
|
# <dt>:unscheduled</dt>
|
33
|
-
# <dd>The
|
30
|
+
# <dd>The job is not invoked yet.</dd>
|
34
31
|
# <dt>:pending</dt>
|
35
|
-
# <dd>The
|
32
|
+
# <dd>The job is waiting for prerequisites complete.</dd>
|
36
33
|
# <dt>:running</dt>
|
37
|
-
# <dd>The
|
38
|
-
# <dt>:complete</dt>
|
39
|
-
# <dd>The task is finished.</dd>
|
40
|
-
# </dl>
|
41
|
-
#
|
42
|
-
# Possible results are:
|
43
|
-
#
|
44
|
-
# <dl>
|
34
|
+
# <dd>The job is running.</dd>
|
45
35
|
# <dt>:successed</dt>
|
46
|
-
# <dd>The
|
36
|
+
# <dd>The job is successed.</dd>
|
47
37
|
# <dt>:failed</dt>
|
48
|
-
# <dd>The
|
38
|
+
# <dd>The job is failed.</dd>
|
49
39
|
# <dt>:cancelled</dt>
|
50
|
-
# <dd>The
|
40
|
+
# <dd>The job is not executed because of some problems.</dd>
|
51
41
|
# <dt>:skipped</dt>
|
52
|
-
# <dd>The
|
42
|
+
# <dd>The job is not executed because already successed.</dd>
|
53
43
|
# </dl>
|
54
44
|
class Process
|
55
45
|
extend Forwardable
|
56
46
|
|
57
|
-
|
47
|
+
def initialize(engine, job)
|
48
|
+
@engine = engine
|
49
|
+
@job = job
|
50
|
+
@status = Status.new(:unscheduled)
|
51
|
+
@error = nil
|
52
|
+
initialize_logger(job)
|
53
|
+
initialize_state_service(job)
|
54
|
+
end
|
55
|
+
attr_reader :error, :engine, :job
|
56
|
+
def_delegators :@status, :on_update, :on_complete, :wait, :complete?
|
57
|
+
|
58
|
+
def initialize_logger(job)
|
59
|
+
@logger = @engine.logger.clone
|
60
|
+
pn = @logger.progname
|
61
|
+
@logger.progname = pn ? "#{pn}: #{job}" : job.to_s
|
62
|
+
end
|
63
|
+
private :initialize_logger
|
64
|
+
attr_reader :logger
|
58
65
|
|
59
|
-
|
66
|
+
def initialize_state_service(job)
|
67
|
+
@state_service = @engine.state_manager.state(
|
68
|
+
job.name, job.params,
|
69
|
+
dryrun: job.dryrun? || job.preview?,
|
70
|
+
volatile: job.volatile?
|
71
|
+
)
|
72
|
+
end
|
73
|
+
private :initialize_state_service
|
74
|
+
attr_reader :state_service
|
60
75
|
|
61
|
-
def
|
62
|
-
@
|
63
|
-
@result_ivar = Concurrent::IVar.new
|
64
|
-
@status = :unscheduled
|
65
|
-
@error = nil
|
66
|
-
@mon = Monitor.new
|
76
|
+
def prerequisites
|
77
|
+
@job.prerequisites.map { |preq| @engine.process_manager[preq] }
|
67
78
|
end
|
68
79
|
|
69
|
-
|
80
|
+
#
|
81
|
+
# State predicates
|
82
|
+
#
|
83
|
+
%w(unscheduled pending running
|
84
|
+
successed failed cancelled skipped).each do |meth|
|
70
85
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
71
86
|
def #{meth}?
|
72
|
-
|
87
|
+
@status.get == :#{meth}
|
73
88
|
end
|
74
89
|
RUBY
|
75
90
|
end
|
76
91
|
|
77
|
-
def
|
78
|
-
@
|
92
|
+
def root?
|
93
|
+
@engine.process_manager.root?(self)
|
94
|
+
end
|
95
|
+
|
96
|
+
def status
|
97
|
+
@status.get
|
79
98
|
end
|
80
99
|
|
81
100
|
def prepare
|
82
|
-
|
101
|
+
@status.compare_and_set(:unscheduled, :pending)
|
83
102
|
end
|
84
103
|
|
85
104
|
def start
|
86
|
-
|
105
|
+
return unless @status.compare_and_set(:pending, :running)
|
106
|
+
@logger.info('start.')
|
87
107
|
end
|
88
108
|
|
89
109
|
def finish(error = nil)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
110
|
+
error.nil? ? successed : failed(error)
|
111
|
+
end
|
112
|
+
|
113
|
+
def successed
|
114
|
+
return unless @status.compare_and_set(:running, :successed, true)
|
115
|
+
@logger.info('successed.')
|
116
|
+
end
|
117
|
+
|
118
|
+
def failed(error)
|
119
|
+
log_error(error)
|
120
|
+
return unless @status.compare_and_set(:running, :failed, true)
|
121
|
+
@logger.error('failed.')
|
94
122
|
end
|
95
123
|
|
96
124
|
def cancel(error = nil)
|
97
|
-
|
98
|
-
|
99
|
-
@
|
100
|
-
true
|
125
|
+
log_error(error) if error
|
126
|
+
return unless @status.compare_and_set(:pending, :cancelled, true)
|
127
|
+
@logger.info('cancelled.')
|
101
128
|
end
|
102
129
|
|
103
130
|
def skip
|
104
|
-
return
|
105
|
-
@
|
106
|
-
true
|
131
|
+
return unless @status.compare_and_set(:pending, :skipped, true)
|
132
|
+
@logger.info('skipped')
|
107
133
|
end
|
108
134
|
|
109
|
-
# Force fail the
|
135
|
+
# Force fail the job.
|
110
136
|
# @return [void]
|
111
137
|
def quit(error)
|
112
|
-
|
113
|
-
@
|
114
|
-
|
138
|
+
log_error(error)
|
139
|
+
@status.try_set(:failed, true)
|
140
|
+
end
|
141
|
+
|
142
|
+
def capture_exception
|
143
|
+
yield
|
144
|
+
rescue StandardError, ScriptError => error
|
145
|
+
quit(error)
|
146
|
+
rescue Exception => exception
|
147
|
+
# kill from independent thread
|
148
|
+
Thread.start { @engine.kill(exception) }
|
115
149
|
end
|
116
150
|
|
117
151
|
private
|
118
152
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
def compare_and_set_status(next_state, *expected_current)
|
128
|
-
@mon.synchronize do
|
129
|
-
return unless expected_current.include? @status
|
130
|
-
@status = next_state
|
131
|
-
@process_manager.update(self)
|
132
|
-
true
|
153
|
+
def log_error(error)
|
154
|
+
@error = error
|
155
|
+
@logger.error(format_error_backtrace(error))
|
156
|
+
end
|
157
|
+
|
158
|
+
def format_error_backtrace(error)
|
159
|
+
if error.backtrace.nil? || error.backtrace.empty?
|
160
|
+
return "#{error.message} (#{error.class})"
|
133
161
|
end
|
162
|
+
|
163
|
+
bt = error.backtrace
|
164
|
+
ret = ''
|
165
|
+
ret << "#{bt.first}: #{error.message} (#{error.class})\n"
|
166
|
+
bt.each { |b| ret << " from #{b}\n" }
|
167
|
+
ret
|
134
168
|
end
|
135
169
|
end
|
136
170
|
end
|