abid 0.3.0.pre.alpha.3 → 0.3.0.pre.alpha.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|