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.
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