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