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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/abid.gemspec +1 -2
  4. data/exe/abidsc +1 -1
  5. data/lib/abid.rb +3 -30
  6. data/lib/abid/application.rb +83 -13
  7. data/lib/abid/cli/assume.rb +7 -8
  8. data/lib/abid/cli/list.rb +2 -2
  9. data/lib/abid/cli/migrate.rb +1 -1
  10. data/lib/abid/cli/revoke.rb +9 -10
  11. data/lib/abid/config.rb +2 -0
  12. data/lib/abid/dsl/abid_job.rb +58 -0
  13. data/lib/abid/dsl/actions.rb +36 -0
  14. data/lib/abid/dsl/job.rb +58 -0
  15. data/lib/abid/dsl/job_manager.rb +53 -0
  16. data/lib/abid/dsl/mixin.rb +52 -0
  17. data/lib/abid/dsl/params_spec.rb +64 -0
  18. data/lib/abid/dsl/play.rb +35 -0
  19. data/lib/abid/dsl/play_core.rb +354 -0
  20. data/lib/abid/dsl/rake_job.rb +41 -0
  21. data/lib/abid/dsl/syntax.rb +34 -0
  22. data/lib/abid/dsl/task.rb +53 -0
  23. data/lib/abid/engine.rb +36 -6
  24. data/lib/abid/engine/executor.rb +30 -48
  25. data/lib/abid/engine/process.rb +102 -68
  26. data/lib/abid/engine/process_manager.rb +72 -28
  27. data/lib/abid/engine/scheduler.rb +24 -30
  28. data/lib/abid/engine/waiter.rb +20 -30
  29. data/lib/abid/engine/worker_manager.rb +26 -60
  30. data/lib/abid/environment.rb +12 -27
  31. data/lib/abid/error.rb +2 -0
  32. data/lib/abid/params_format.rb +29 -12
  33. data/lib/abid/rake_extensions.rb +11 -3
  34. data/lib/abid/state_manager.rb +40 -0
  35. data/lib/abid/state_manager/state.rb +52 -114
  36. data/lib/abid/state_manager/state_service.rb +88 -0
  37. data/lib/abid/status.rb +63 -0
  38. data/lib/abid/version.rb +1 -1
  39. metadata +19 -32
  40. data/lib/Abidfile.rb +0 -1
  41. data/lib/abid/dsl_definition.rb +0 -29
  42. data/lib/abid/job.rb +0 -67
  43. data/lib/abid/job_manager.rb +0 -22
  44. data/lib/abid/mixin_task.rb +0 -29
  45. data/lib/abid/params_parser.rb +0 -50
  46. data/lib/abid/play.rb +0 -66
  47. data/lib/abid/play_core.rb +0 -53
  48. data/lib/abid/rake_extensions/task.rb +0 -41
  49. data/lib/abid/state_manager/database.rb +0 -40
  50. data/lib/abid/state_manager/state_proxy.rb +0 -65
  51. data/lib/abid/task.rb +0 -123
  52. 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
- module Engine
4
- def self.kill(error)
5
- Abid.global.worker_manager.kill
6
- Abid.global.process_manager.kill(error)
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 self.shutdown
10
- Abid.global.worker_manager.shutdown
38
+ def shutdown
39
+ process_manager.shutdown
40
+ worker_manager.shutdown
11
41
  end
12
42
  end
13
43
  end
@@ -1,21 +1,22 @@
1
1
  module Abid
2
- module Engine
2
+ class Engine
3
3
  # @!visibility private
4
4
 
5
- # Executor operates each task execution.
5
+ # Executor operates each job execution.
6
6
  class Executor
7
- def initialize(job, args)
8
- @job = job
7
+ def initialize(process, args)
8
+ @process = process
9
+ @job = process.job
9
10
  @args = args
10
11
 
11
- @process = job.process
12
- @state = job.state.find
13
- @prerequisites = job.prerequisites.map(&:process)
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 task should be executed.
17
+ # Check if the job should be executed.
17
18
  #
18
- # @return [Boolean] false if the task should not be executed.
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 task.
27
+ # Start processing the job.
27
28
  #
28
- # The task is executed asynchronously.
29
+ # The job is executed asynchronously.
29
30
  #
30
- # @return [Boolean] false if the task is not executed
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 task if it should be.
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.env.options.repair
48
+ return false if @job.repair?
57
49
  return false unless @state.failed?
58
- return false if @job.task.top_level?
50
+ return false if @process.root?
59
51
  @process.cancel(Error.new('task has been failed'))
60
52
  end
61
53
 
62
- # Skip the task if it should be.
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.task.concerned?
57
+ return @process.skip unless @job.concerned?
66
58
 
67
- return false if @job.env.options.repair && !@prerequisites.empty?
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 task if it should be.
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 task if it should be.
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.task.needed?
75
+ return @process.skip unless @job.needed?
84
76
 
85
77
  return false if @prerequisites.empty?
86
- return false unless @job.env.options.repair
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 task if no external process executing the same task, wait the
92
- # task finished otherwise.
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 @job.state.try_start
95
- @job.worker.post { capture_exception { execute } }
86
+ if @process.state_service.try_start
87
+ @worker.post { @process.capture_exception { execute } }
96
88
  else
97
- Waiter.new(@job).wait
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
- call_after_hooks(error)
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.task.call_hooks(:before_execute)
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
@@ -1,136 +1,170 @@
1
- require 'concurrent/ivar'
2
1
  require 'forwardable'
3
- require 'monitor'
4
2
 
5
3
  module Abid
6
- module Engine
4
+ class Engine
7
5
  # @!visibility private
8
6
 
9
- # Process object manages the task execution status.
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 task execution and
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 task is posted to a thread pool,
20
- # the status gets :running. When the task is finished, the status gets
21
- # :complete and the result is assigned to :successed or :failed.
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['task_name'].process
20
+ # process = Job['job_name'].process
24
21
  # process.prepare
25
22
  # process.start
26
23
  # process.wait
27
- # process.result #=> :successed or :failed
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 task is not invoked yet.</dd>
30
+ # <dd>The job is not invoked yet.</dd>
34
31
  # <dt>:pending</dt>
35
- # <dd>The task is waiting for prerequisites complete.</dd>
32
+ # <dd>The job is waiting for prerequisites complete.</dd>
36
33
  # <dt>:running</dt>
37
- # <dd>The task is running.</dd>
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 task is successed.</dd>
36
+ # <dd>The job is successed.</dd>
47
37
  # <dt>:failed</dt>
48
- # <dd>The task is failed.</dd>
38
+ # <dd>The job is failed.</dd>
49
39
  # <dt>:cancelled</dt>
50
- # <dd>The task is not executed because of some problems.</dd>
40
+ # <dd>The job is not executed because of some problems.</dd>
51
41
  # <dt>:skipped</dt>
52
- # <dd>The task is not executed because already successed.</dd>
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
- attr_reader :status, :error
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
- def_delegators :@result_ivar, :add_observer, :wait, :complete?
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 initialize(process_manager)
62
- @process_manager = process_manager
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
- %w(successed failed cancelled skipped).each do |meth|
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
- result == :#{meth}
87
+ @status.get == :#{meth}
73
88
  end
74
89
  RUBY
75
90
  end
76
91
 
77
- def result
78
- @result_ivar.value if @result_ivar.complete?
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
- compare_and_set_status(:pending, :unscheduled)
101
+ @status.compare_and_set(:unscheduled, :pending)
83
102
  end
84
103
 
85
104
  def start
86
- compare_and_set_status(:running, :pending)
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
- return unless compare_and_set_status(:complete, :running)
91
- @error = error if error
92
- @result_ivar.set(error.nil? ? :successed : :failed)
93
- true
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
- return false unless compare_and_set_status(:complete, :pending)
98
- @error = error if error
99
- @result_ivar.set :cancelled
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 false unless compare_and_set_status(:complete, :pending)
105
- @result_ivar.set :skipped
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 task.
135
+ # Force fail the job.
110
136
  # @return [void]
111
137
  def quit(error)
112
- @status = :complete
113
- @error = error
114
- @result_ivar.try_set(:failed)
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
- # Atomic compare and set operation.
120
- # State is set to `next_state` only if
121
- # `current state == expected_current`.
122
- #
123
- # @param [Symbol] next_state
124
- # @param [Symbol] expected_current
125
- #
126
- # @return [Boolean] true if state is changed, false otherwise
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