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
@@ -1,55 +1,99 @@
1
- require 'monitor'
1
+ require 'concurrent/atomic/atomic_reference'
2
2
 
3
3
  module Abid
4
- module Engine
4
+ class Engine
5
+ # ProcessManager is a processes repositry and tracks each process progress.
5
6
  class ProcessManager
6
- attr_reader :active_processes
7
-
8
- # @param env [Environment] abid environment
9
- def initialize(env)
10
- @env = env
11
- @active_processes = {}.compare_by_identity
7
+ def initialize(engine)
8
+ @engine = engine
9
+ @all = {}.compare_by_identity
10
+ @actives = {}.compare_by_identity
11
+ @top_levels = {}.compare_by_identity
12
+ @summary = Hash.new { |h, k| h[k] = 0 }
13
+ @errors = []
12
14
  @mon = Monitor.new
15
+ @status = Concurrent::AtomicReference.new(:running)
13
16
  end
17
+ attr_reader :summary, :errors
18
+
19
+ def [](job)
20
+ return @all[job] if @all.include?(job)
14
21
 
15
- # @return [Process] new process
16
- def create
17
- Process.new(self)
22
+ @mon.synchronize do
23
+ @all[job] ||= Process.new(@engine, job).tap do |process|
24
+ process.on_update { update(process) }
25
+ end
26
+ end
18
27
  end
19
28
 
20
- # Update active process set.
21
- # @param process [Process]
29
+ # @param job [DSL::Job]
30
+ # @param args [Array<Object>]
31
+ # @return [Process]
32
+ def invoke(job, args)
33
+ raise Error, 'ProcessManager is not running now' unless running?
34
+
35
+ process = self[job]
36
+ @top_levels[process] = process
37
+ Scheduler.invoke(process, *args)
38
+ process.tap(&:wait)
39
+ end
40
+
41
+ # Update active processes list
22
42
  def update(process)
23
- case process.status
24
- when :pending, :running
25
- add_active(process)
26
- when :complete
27
- delete_active(process)
28
- end
43
+ update_actives(process)
44
+ update_summary(process)
45
+ end
46
+
47
+ def shutdown
48
+ return unless @status.compare_and_set(:running, :shuttingdown)
49
+ actives.each(&:wait)
50
+ @status.set(:shutdown)
29
51
  end
30
52
 
31
53
  # Kill all active processes
32
54
  # @param error [Exception] error reason
33
55
  def kill(error)
34
- each_active { |p| p.quit(error) }
56
+ return if shutdown?
57
+ @status.set(:shutdown)
58
+ @errors << error
59
+ actives.each { |process| process.quit(error) }
60
+ end
61
+
62
+ def actives
63
+ @actives.values
35
64
  end
36
65
 
37
66
  def active?(process)
38
- @mon.synchronize { @active_processes.include? process }
67
+ @actives.include?(process)
39
68
  end
40
69
 
41
- private
70
+ def root?(process)
71
+ @top_levels.include?(process)
72
+ end
42
73
 
43
- def add_active(process)
44
- @mon.synchronize { @active_processes[process] ||= process }
74
+ %w(running shuttingdown shutdown).each do |meth|
75
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
76
+ def #{meth}?
77
+ @status.get == :#{meth}
78
+ end
79
+ RUBY
45
80
  end
46
81
 
47
- def delete_active(process)
48
- @mon.synchronize { @active_processes.delete(process) }
82
+ private
83
+
84
+ def update_actives(process)
85
+ if process.complete?
86
+ @actives.delete(process)
87
+ else
88
+ @actives[process] = process
89
+ end
49
90
  end
50
91
 
51
- def each_active(&block)
52
- @mon.synchronize { @active_processes.values.each(&block) }
92
+ def update_summary(process)
93
+ return unless process.complete?
94
+ return if process.job.dryrun?
95
+ @mon.synchronize { @summary[process.status] += 1 }
96
+ @errors << process.error if process.error
53
97
  end
54
98
  end
55
99
  end
@@ -1,16 +1,16 @@
1
1
  require 'concurrent/atomic/atomic_fixnum'
2
2
 
3
3
  module Abid
4
- module Engine
4
+ class Engine
5
5
  # Scheduler operates whole job flow execution.
6
6
  class Scheduler
7
7
  # @return [void]
8
- def self.invoke(job, *args, invocation_chain: nil)
9
- task_args = Rake::TaskArguments.new(job.task.arg_names, args)
8
+ def self.invoke(process, *args, invocation_chain: nil)
9
+ task_args = Rake::TaskArguments.new(process.job.arg_names, args)
10
10
  invocation_chain ||= Rake::InvocationChain::EMPTY
11
11
 
12
- detect_circular_dependency(job, invocation_chain)
13
- new(job, task_args, invocation_chain).invoke
12
+ detect_circular_dependency(process, invocation_chain)
13
+ new(process, task_args, invocation_chain).invoke
14
14
  end
15
15
 
16
16
  # @!visibility private
@@ -29,30 +29,30 @@ module Abid
29
29
  end
30
30
  end
31
31
 
32
- def self.detect_circular_dependency(job, chain)
33
- # raise error if job.task is a member of the chain
34
- new_chain = Rake::InvocationChain.append(job.task, chain)
32
+ def self.detect_circular_dependency(process, chain)
33
+ # raise error if process.job is a member of the chain
34
+ new_chain = Rake::InvocationChain.append(process.job, chain)
35
35
 
36
- job.prerequisites.each do |preq_job|
37
- detect_circular_dependency(preq_job, new_chain)
36
+ process.prerequisites.each do |preq_process|
37
+ detect_circular_dependency(preq_process, new_chain)
38
38
  end
39
39
  end
40
40
 
41
- def initialize(job, args, invocation_chain)
42
- @job = job
41
+ def initialize(process, args, invocation_chain)
42
+ @process = process
43
43
  @args = args
44
- @chain = invocation_chain.conj(@job.task)
45
- @executor = Executor.new(job, args)
44
+ @chain = invocation_chain.conj(@process.job)
45
+ @executor = Executor.new(process, args)
46
46
  end
47
47
 
48
48
  def invoke
49
49
  return unless @executor.prepare
50
50
 
51
- trace_invoke
51
+ @process.job.trace_invoke
52
52
  attach_chain
53
53
  invoke_prerequisites
54
54
  after_prerequisites do
55
- @executor.capture_exception do
55
+ @process.capture_exception do
56
56
  @executor.start
57
57
  end
58
58
  end
@@ -60,15 +60,9 @@ module Abid
60
60
 
61
61
  private
62
62
 
63
- def trace_invoke
64
- return unless @job.env.options.trace
65
- @job.env.application.trace \
66
- "** Invoke #{@job.task.name} #{@job.task.format_trace_flags}"
67
- end
68
-
69
63
  def attach_chain
70
- @job.process.add_observer do
71
- error = @job.process.error
64
+ @process.on_complete do
65
+ error = @process.error
72
66
  next if error.nil?
73
67
  next if @chain.nil?
74
68
 
@@ -79,16 +73,16 @@ module Abid
79
73
  end
80
74
 
81
75
  def invoke_prerequisites
82
- @job.prerequisites.each do |preq_job|
83
- preq_args = @args.new_scope(@job.task.arg_names)
84
- Scheduler.new(preq_job, preq_args, @chain).invoke
76
+ @process.prerequisites.each do |preq|
77
+ preq_args = @args.new_scope(@process.job.arg_names)
78
+ Scheduler.new(preq, preq_args, @chain).invoke
85
79
  end
86
80
  end
87
81
 
88
82
  def after_prerequisites(&block)
89
- counter = DependencyCounter.new(@job.prerequisites.size, &block)
90
- @job.prerequisites.each do |preq_job|
91
- preq_job.process.add_observer counter
83
+ counter = DependencyCounter.new(@process.prerequisites.size, &block)
84
+ @process.prerequisites.each do |preq|
85
+ preq.on_complete { counter.update }
92
86
  end
93
87
  end
94
88
  end
@@ -1,51 +1,50 @@
1
+ require 'concurrent/utility/monotonic_time'
2
+
1
3
  module Abid
2
- module Engine
3
- # Waits for a job to be finished which is running in external application,
4
- # and completes the job in its own application.
4
+ class Engine
5
+ # Waits for a process to be finished which is running in external
6
+ # application, and completes the process in its own application.
5
7
  #
6
- # Waiter.new(job).wait
8
+ # Waiter.new(process).wait
7
9
  #
8
- # The `job` result gets :successed or :failed when external application
9
- # finished the job execution.
10
+ # The `process` result gets :successed or :failed when external application
11
+ # finished the process execution.
10
12
  class Waiter
11
13
  DEFAULT_WAIT_INTERVAL = 10
12
14
  DEFAULT_WAIT_TIMEOUT = 3600
13
15
 
14
- def initialize(job)
15
- @job = job
16
- @process = job.process
16
+ def initialize(process)
17
+ @process = process
18
+ @job = process.job
17
19
  @wait_limit = Concurrent.monotonic_time + wait_timeout
18
20
  end
19
21
 
20
22
  def wait
21
- unless @job.env.options.wait_external_task
22
- @process.finish(AlreadyRunningError.new('job already running'))
23
+ unless @job.options.wait_external_task
24
+ @process.finish(AlreadyRunningError.new('process already running'))
23
25
  return
24
26
  end
25
27
 
28
+ @process.logger.info('waiting')
26
29
  wait_iter
27
30
  end
28
31
 
29
32
  private
30
33
 
31
34
  def wait_interval
32
- @job.env.options.wait_external_task_interval ||
33
- DEFAULT_WAIT_INTERVAL
35
+ @job.options.wait_external_task_interval || DEFAULT_WAIT_INTERVAL
34
36
  end
35
37
 
36
38
  def wait_timeout
37
- @job.env.options.wait_external_task_timeout ||
38
- DEFAULT_WAIT_TIMEOUT
39
+ @job.options.wait_external_task_timeout || DEFAULT_WAIT_TIMEOUT
39
40
  end
40
41
 
41
42
  def wait_iter
42
- @job.env.worker_manager[:timer_set].post(wait_interval) do
43
- capture_exception do
44
- state = @job.state.find
43
+ @process.engine.worker_manager[:timer_set].post(wait_interval) do
44
+ @process.capture_exception do
45
+ state = @process.state_service.find
45
46
 
46
- check_finished(state) ||
47
- check_timeout ||
48
- wait_iter
47
+ check_finished(state) || check_timeout || wait_iter
49
48
  end
50
49
  end
51
50
  end
@@ -69,15 +68,6 @@ module Abid
69
68
  @process.finish RuntimeError.new('timeout')
70
69
  true
71
70
  end
72
-
73
- def capture_exception
74
- yield
75
- rescue StandardError, ScriptError => error
76
- @process.quit(error)
77
- rescue Exception => exception
78
- # TODO: exit immediately when fatal error occurs.
79
- @process.quit(exception)
80
- end
81
71
  end
82
72
  end
83
73
  end
@@ -1,10 +1,11 @@
1
- require 'forwardable'
2
- require 'monitor'
3
-
4
- require 'concurrent/delay'
1
+ require 'concurrent/configuration'
2
+ require 'concurrent/executor/cached_thread_pool'
3
+ require 'concurrent/executor/fixed_thread_pool'
4
+ require 'concurrent/executor/safe_task_executor'
5
+ require 'concurrent/executor/timer_set'
5
6
 
6
7
  module Abid
7
- module Engine
8
+ class Engine
8
9
  # WorkerManager manges thread pools definition, creation and termination.
9
10
  #
10
11
  # worker_manager = Abid.global.worker_manager
@@ -14,32 +15,21 @@ module Abid
14
15
  # worker_manager[:main].post { :do_something }
15
16
  #
16
17
  # worker_manager.shutdown
17
- #
18
18
  class WorkerManager
19
19
  def initialize(env)
20
20
  @env = env
21
21
  @workers = {}
22
- @alive = true
23
- @mon = Monitor.new
24
22
 
25
23
  initialize_builtin_workers
26
24
  end
27
25
 
28
26
  # Define new worker.
29
- #
30
- # An actual worker is created when needed.
31
27
  def define(name, num_threads)
32
- @mon.synchronize do
33
- check_alive!
34
-
35
- if @workers.include?(name)
36
- raise Error, "worker #{name} is already defined"
37
- end
38
-
39
- @workers[name] = Concurrent::Delay.new do
40
- create_worker(num_threads: num_threads)
41
- end
28
+ if @workers.include?(name)
29
+ raise Error, "worker #{name} is already defined"
42
30
  end
31
+
32
+ @workers[name] = create_worker(num_threads: num_threads)
43
33
  end
44
34
 
45
35
  # Find or create worker
@@ -47,46 +37,35 @@ module Abid
47
37
  # @param name [String, Symbol] worker name
48
38
  # @return [Concurrent::ExecutorService]
49
39
  def [](name)
50
- @mon.synchronize do
51
- check_alive!
52
-
53
- unless @workers.include?(name)
54
- raise Error, "worker #{name} is not defined"
55
- end
56
-
57
- @workers[name].value!
40
+ unless @workers.include?(name)
41
+ raise Error, "worker #{name} is not defined"
58
42
  end
43
+
44
+ @workers[name]
59
45
  end
60
46
 
61
47
  def shutdown(timeout = nil)
62
- @mon.synchronize do
63
- check_alive!
64
- each_active(&:shutdown)
65
- each_active { |worker| worker.wait_for_termination(timeout) }
66
-
67
- result = each_active.all?(&:shutdown?)
68
- @alive = false if result
69
- result
70
- end
48
+ each_worker(&:shutdown)
49
+ each_worker { |worker| worker.wait_for_termination(timeout) }
50
+ each_worker.all?(&:shutdown?)
71
51
  end
72
52
 
73
53
  def kill
74
- @mon.synchronize do
75
- check_alive!
76
- @alive = false
77
- each_active(&:kill)
78
- end
54
+ each_worker(&:kill)
79
55
  true
80
56
  end
81
57
 
58
+ def each_worker(&block)
59
+ @workers.values.each(&block)
60
+ end
61
+
82
62
  private
83
63
 
84
64
  def initialize_builtin_workers
85
- @workers[:default] = Concurrent::Delay.new { create_default_worker }
86
- @workers[:waiter] = Concurrent::Delay.new { Concurrent.new_io_executor }
87
- @workers[:timer_set] = Concurrent::Delay.new do
88
- Concurrent::TimerSet.new(executor: self[:waiter])
89
- end
65
+ @workers[:default] = create_worker(num_threads: default_num_threads)
66
+ @workers[:waiter] = Concurrent.new_io_executor
67
+ @workers[:timer_set] =
68
+ Concurrent::TimerSet.new(executor: @workers[:waiter])
90
69
  end
91
70
 
92
71
  def create_worker(definition)
@@ -97,10 +76,6 @@ module Abid
97
76
  end
98
77
  end
99
78
 
100
- def create_default_worker
101
- create_worker(num_threads: default_num_threads)
102
- end
103
-
104
79
  def default_num_threads
105
80
  if @env.options.always_multitask
106
81
  @env.options.thread_pool_size ||
@@ -109,15 +84,6 @@ module Abid
109
84
  1
110
85
  end
111
86
  end
112
-
113
- def check_alive!
114
- raise Error, 'already terminated' unless @alive
115
- end
116
-
117
- # Iterate on active workers
118
- def each_active(&block)
119
- @workers.values.select(&:fulfilled?).map(&:value).each(&block)
120
- end
121
87
  end
122
88
  end
123
89
  end