dipa 0.1.0.pre.1 → 0.1.0.pre.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +77 -49
  3. data/Rakefile +50 -0
  4. data/app/models/concerns/dipa/models/agent_state_attributes.rb +24 -0
  5. data/app/models/concerns/dipa/models/coordinator_state_attributes.rb +24 -0
  6. data/app/models/concerns/{models/dipa → dipa/models}/dumpable.rb +3 -3
  7. data/app/models/concerns/{models/dipa → dipa/models}/loadable.rb +4 -4
  8. data/app/models/concerns/dipa/models/setting_constants.rb +23 -0
  9. data/app/models/concerns/dipa/models/state_attribute_handling.rb +77 -0
  10. data/app/models/dipa/agent.rb +14 -13
  11. data/app/models/dipa/coordinator.rb +32 -19
  12. data/app/models/dipa/coordinator_options_record.rb +52 -0
  13. data/app/services/concerns/dipa/services/process_failure_handling.rb +80 -0
  14. data/app/services/dipa/agent_services/base_service.rb +21 -0
  15. data/app/services/dipa/agent_services/coordinator_state_service.rb +58 -4
  16. data/app/services/dipa/agent_services/post_processing_service.rb +5 -3
  17. data/app/services/dipa/agent_services/processing_service.rb +84 -3
  18. data/app/services/dipa/agent_services/start_processing_service.rb +6 -4
  19. data/app/services/dipa/coordinator_services/base_service.rb +21 -0
  20. data/app/services/dipa/coordinator_services/create_agents_service.rb +7 -5
  21. data/app/services/dipa/coordinator_services/destroy_service.rb +13 -0
  22. data/app/services/dipa/coordinator_services/maybe_cleanup_service.rb +20 -0
  23. data/app/services/dipa/coordinator_services/start_processing_service.rb +6 -4
  24. data/db/migrate/20220102132652_create_dipa_coordinators.rb +3 -5
  25. data/db/migrate/20220106183616_create_dipa_agents.rb +3 -3
  26. data/db/migrate/20230624053724_create_dipa_coordinator_options_record.rb +21 -0
  27. data/lib/dipa/engine.rb +15 -8
  28. data/lib/dipa/errors.rb +20 -3
  29. data/lib/dipa/processor/base.rb +20 -106
  30. data/lib/dipa/processor/concerns/coordinator.rb +86 -0
  31. data/lib/dipa/processor/concerns/options.rb +80 -0
  32. data/lib/dipa/processor/concerns/source.rb +17 -0
  33. data/lib/dipa/processor/concerns/state.rb +27 -0
  34. data/lib/dipa/processor/concerns/wait.rb +62 -0
  35. data/lib/dipa/processor/map.rb +1 -2
  36. data/lib/dipa/version.rb +3 -1
  37. data/lib/dipa.rb +18 -0
  38. metadata +54 -25
  39. data/app/models/concerns/models/dipa/state_attribute_handling.rb +0 -38
  40. data/app/models/modules/models/dipa/status_constants.rb +0 -24
  41. data/lib/tasks/auto_annotate_models.rake +0 -62
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dipa
4
+ module Services
5
+ module ProcessFailureHandling
6
+ extend ActiveSupport::Concern
7
+
8
+ # Order of the checks is important here
9
+ STATES_TO_CHECK_FOR = %i[
10
+ aborted_through_coordinator
11
+ aborted_through_coordinator_timed_out
12
+ aborted_through_coordinator_processing_timed_out
13
+ timed_out
14
+ processing_timed_out
15
+ ].freeze
16
+
17
+ private
18
+
19
+ def _stop_processing?(agent:)
20
+ STATES_TO_CHECK_FOR.any? do |state|
21
+ __send__("_#{state}_state_desired?".to_sym, agent: agent)
22
+ end
23
+ end
24
+
25
+ def _stop_processing!(agent:, thread:)
26
+ agent_state = _determine_agent_state(agent: agent)
27
+
28
+ error_class_name = _determine_error_class_name(state: agent_state)
29
+
30
+ _end_it_all!(agent: agent, thread: thread, state: agent_state, error_class_name: error_class_name)
31
+ end
32
+
33
+ def _determine_error_class_name(state:)
34
+ "Dipa::Agent#{state.to_s.camelize}Error"
35
+ end
36
+
37
+ def _determine_agent_state(agent:)
38
+ _determine_agent_current_state(agent: agent) || _determine_agent_desired_state(agent: agent)
39
+ end
40
+
41
+ def _determine_agent_current_state(agent:)
42
+ return unless agent.reload.failed_state?
43
+
44
+ agent.state.to_sym
45
+ end
46
+
47
+ def _determine_agent_desired_state(agent:)
48
+ STATES_TO_CHECK_FOR.each do |state|
49
+ return state if __send__("_#{state}_state_desired?", agent: agent)
50
+ end
51
+ end
52
+
53
+ def _aborted_through_coordinator_state_desired?(agent:)
54
+ agent.reload.coordinator.failed_state?
55
+ end
56
+
57
+ def _aborted_through_coordinator_timed_out_state_desired?(agent:)
58
+ agent.reload.coordinator.timeout_reached?
59
+ end
60
+
61
+ def _aborted_through_coordinator_processing_timed_out_state_desired?(agent:)
62
+ agent.reload.coordinator.processing_timeout_reached?
63
+ end
64
+
65
+ def _timed_out_state_desired?(agent:)
66
+ agent.reload.timeout_reached?
67
+ end
68
+
69
+ def _processing_timed_out_state_desired?(agent:)
70
+ agent.reload.processing_timeout_reached?
71
+ end
72
+
73
+ def _end_it_all!(agent:, thread:, state:, error_class_name:)
74
+ agent.__send__("#{state}!") unless agent.reload.__send__("#{state}?")
75
+
76
+ thread.raise error_class_name.constantize
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dipa
4
+ module AgentServices
5
+ class BaseService < ApplicationService
6
+ def call(agent:)
7
+ @_agent = agent
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :_agent
13
+
14
+ def initialize
15
+ super
16
+
17
+ @_agent = nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,12 +2,66 @@
2
2
 
3
3
  module Dipa
4
4
  module AgentServices
5
- class CoordinatorStateService < ApplicationService
5
+ class CoordinatorStateService < Dipa::AgentServices::BaseService
6
+ AGENT_STATES_TO_CHECK_FOR = %i[
7
+ timed_out
8
+ processing_timed_out
9
+ processing_failed
10
+ ].freeze
11
+
12
+ COORDINATOR_STATES_TO_CHECK_FOR = %i[
13
+ aborted_through_coordinator_timed_out
14
+ aborted_through_coordinator_processing_timed_out
15
+ ].freeze
16
+
6
17
  def call(agent:)
7
- return if agent.coordinator.processed?
8
- return unless agent.coordinator.all_agents_created_and_processed?
18
+ super
19
+
20
+ return if _agent.reload.coordinator.finished_state?
21
+
22
+ _handle_agent_states
23
+ end
24
+
25
+ private
26
+
27
+ def _handle_agent_states
28
+ if _agent.reload.failed_state?
29
+ _handle_agent_failed_states
30
+ else
31
+ _handle_agent_success_states
32
+ end
33
+ end
34
+
35
+ def _determine_coordinator_failed_state
36
+ _determine_aborted_through_coordinator_state || _determine_aborted_through_agent_state || :aborted_through_agent
37
+ end
38
+
39
+ def _determine_aborted_through_agent_state
40
+ state = _agent.reload.state
41
+
42
+ return unless AGENT_STATES_TO_CHECK_FOR.include?(state.to_sym)
43
+
44
+ "aborted_through_agent_#{state}".to_sym
45
+ end
46
+
47
+ def _determine_aborted_through_coordinator_state
48
+ state = _agent.reload.state
49
+
50
+ return unless COORDINATOR_STATES_TO_CHECK_FOR.include?(state.to_sym)
51
+
52
+ state.delete_prefix('aborted_through_coordinator_').to_sym
53
+ end
54
+
55
+ def _handle_agent_failed_states
56
+ state = _determine_coordinator_failed_state
57
+
58
+ _agent.coordinator.__send__("#{state}!".to_sym)
59
+ end
60
+
61
+ def _handle_agent_success_states
62
+ return unless _agent.reload.coordinator.all_agents_created_and_finished?
9
63
 
10
- agent.coordinator.finished!
64
+ _agent.coordinator.finished!
11
65
  end
12
66
  end
13
67
  end
@@ -2,13 +2,15 @@
2
2
 
3
3
  module Dipa
4
4
  module AgentServices
5
- class PostProcessingService < ApplicationService
5
+ class PostProcessingService < Dipa::AgentServices::BaseService
6
6
  def call(agent:)
7
- Dipa::ServiceJob.set(queue_as: agent.coordinator.agent_queue)
7
+ super
8
+
9
+ Dipa::ServiceJob.set(queue: _agent.coordinator.agent_queue)
8
10
  .perform_later(
9
11
  service_class_name:
10
12
  'Dipa::AgentServices::CoordinatorStateService',
11
- kwargs: { agent: agent }
13
+ kwargs: { agent: _agent }
12
14
  )
13
15
  end
14
16
  end
@@ -2,11 +2,92 @@
2
2
 
3
3
  module Dipa
4
4
  module AgentServices
5
- class ProcessingService < ApplicationService
5
+ class ProcessingService < Dipa::AgentServices::BaseService
6
+ include Dipa::Services::ProcessFailureHandling
7
+
8
+ TIMER_TASK_SLEEP_START_DURATION = 2
9
+ TIMER_TASK_MAX_SLEEP_DURATION = 120
10
+ TIMER_TASK_SLEEP_DURATION_INCREASE_FACTOR = 1.1
11
+ TIMER_TASK_ARGS = {
12
+ execution_interval: TIMER_TASK_SLEEP_START_DURATION
13
+ }.freeze
14
+
6
15
  def call(agent:)
7
- agent.process!
16
+ super
17
+
18
+ _process!
19
+ ensure
20
+ _post_processing
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :_processor_thread
26
+
27
+ def initialize
28
+ super
29
+
30
+ @_processor_thread = Thread.current
31
+ end
32
+
33
+ def _process!
34
+ _start_monitoring_task
35
+
36
+ processor_result = _processor_result
37
+
38
+ _agent.dump_to_file(data: processor_result, attacher: :result_dump)
39
+ _agent.finished!
40
+ rescue StandardError => e
41
+ _agent.processing_failed! unless e.is_a?(Dipa::AgentError)
42
+ end
43
+
44
+ def _processor_result
45
+ _agent.coordinator.processor_class_name.constantize
46
+ .__send__(
47
+ _agent.coordinator.processor_method_name,
48
+ _agent.source
49
+ )
50
+ end
51
+
52
+ def _monitoring_task
53
+ @_monitoring_task ||= Concurrent::TimerTask.new(TIMER_TASK_ARGS, &_monitoring_task_iteration_block)
54
+ end
55
+
56
+ def _monitoring_task_iteration_block
57
+ proc { |task| _monitoring_task_iteration(task: task) }
58
+ end
59
+
60
+ def _monitoring_task_iteration(task:)
61
+ execution_context = Rails.application.executor.run!
62
+
63
+ _handle_execution_interval(task: task)
64
+
65
+ return unless _stop_processing?(agent: _agent)
66
+
67
+ _stop_processing!(agent: _agent, thread: _processor_thread)
68
+
69
+ task.shutdown
70
+ ensure
71
+ execution_context&.complete!
72
+ end
73
+
74
+ def _handle_execution_interval(task:)
75
+ return if _max_execution_interval_reached?(task: task)
76
+
77
+ task.execution_interval *= TIMER_TASK_SLEEP_DURATION_INCREASE_FACTOR
78
+ end
79
+
80
+ def _max_execution_interval_reached?(task:)
81
+ task.execution_interval > TIMER_TASK_MAX_SLEEP_DURATION
82
+ end
83
+
84
+ def _start_monitoring_task
85
+ _monitoring_task.execute
86
+ end
8
87
 
9
- Dipa::AgentServices::PostProcessingService.call(agent: agent)
88
+ def _post_processing
89
+ _monitoring_task.shutdown if _monitoring_task.running?
90
+ Dipa::AgentServices::PostProcessingService.call(agent: _agent)
10
91
  end
11
92
  end
12
93
  end
@@ -2,15 +2,17 @@
2
2
 
3
3
  module Dipa
4
4
  module AgentServices
5
- class StartProcessingService < ApplicationService
5
+ class StartProcessingService < Dipa::AgentServices::BaseService
6
6
  def call(agent:)
7
- agent.started!
7
+ super
8
8
 
9
- Dipa::ServiceJob.set(queue_as: agent.coordinator.agent_queue)
9
+ _agent.processing!
10
+
11
+ Dipa::ServiceJob.set(queue: _agent.coordinator.agent_queue)
10
12
  .perform_later(
11
13
  service_class_name:
12
14
  'Dipa::AgentServices::ProcessingService',
13
- kwargs: { agent: agent }
15
+ kwargs: { agent: _agent }
14
16
  )
15
17
  end
16
18
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dipa
4
+ module CoordinatorServices
5
+ class BaseService < ApplicationService
6
+ def call(coordinator:)
7
+ @_coordinator = coordinator
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :_coordinator
13
+
14
+ def initialize
15
+ super
16
+
17
+ @_coordinator = nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,17 +2,19 @@
2
2
 
3
3
  module Dipa
4
4
  module CoordinatorServices
5
- class CreateAgentsService < ApplicationService
5
+ class CreateAgentsService < Dipa::CoordinatorServices::BaseService
6
6
  def call(coordinator:)
7
- coordinator.source.each_with_index do |item, i|
8
- _create_agent(coordinator: coordinator, item: item, index: i)
7
+ super
8
+
9
+ _coordinator.source.each_with_index do |item, i|
10
+ _create_agent(item: item, index: i)
9
11
  end
10
12
  end
11
13
 
12
14
  private
13
15
 
14
- def _create_agent(coordinator:, item:, index:)
15
- agent = coordinator.agents.create!(index: index)
16
+ def _create_agent(item:, index:)
17
+ agent = _coordinator.agents.create!(index: index)
16
18
 
17
19
  agent.dump_to_file(data: item, attacher: :source_dump)
18
20
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dipa
4
+ module CoordinatorServices
5
+ class DestroyService < Dipa::CoordinatorServices::BaseService
6
+ def call(coordinator:)
7
+ super
8
+
9
+ _coordinator&.destroy!
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dipa
4
+ module CoordinatorServices
5
+ class MaybeCleanupService < Dipa::CoordinatorServices::BaseService
6
+ def call(coordinator:)
7
+ super
8
+
9
+ return if _coordinator.keep_data?
10
+
11
+ Dipa::ServiceJob.set(queue: _coordinator.coordinator_queue)
12
+ .perform_later(
13
+ service_class_name:
14
+ 'Dipa::CoordinatorServices::DestroyService',
15
+ kwargs: { coordinator: _coordinator }
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,15 +2,17 @@
2
2
 
3
3
  module Dipa
4
4
  module CoordinatorServices
5
- class StartProcessingService < ApplicationService
5
+ class StartProcessingService < Dipa::CoordinatorServices::BaseService
6
6
  def call(coordinator:)
7
- coordinator.started!
7
+ super
8
8
 
9
- ServiceJob.set(queue_as: coordinator.coordinator_queue)
9
+ _coordinator.processing!
10
+
11
+ ServiceJob.set(queue: _coordinator.coordinator_queue)
10
12
  .perform_later(
11
13
  service_class_name:
12
14
  'Dipa::CoordinatorServices::CreateAgentsService',
13
- kwargs: { coordinator: coordinator }
15
+ kwargs: { coordinator: _coordinator }
14
16
  )
15
17
  end
16
18
  end
@@ -3,10 +3,8 @@
3
3
  class CreateDipaCoordinators < ActiveRecord::Migration[6.0]
4
4
  def change # rubocop:disable Metrics/MethodLength
5
5
  create_table :dipa_coordinators do |t|
6
- t.boolean :keep_data, default: false, null: false
7
- t.boolean :want_result, default: true, null: false
8
- t.datetime :finished_at
9
- t.datetime :started_at
6
+ t.datetime :finished_at, precision: Dipa.datetime_precision
7
+ t.datetime :started_at, precision: Dipa.datetime_precision
10
8
  t.integer :size, null: false
11
9
  t.string :agent_queue, null: false
12
10
  t.string :coordinator_queue, null: false
@@ -14,7 +12,7 @@ class CreateDipaCoordinators < ActiveRecord::Migration[6.0]
14
12
  t.string :processor_method_name, null: false
15
13
  t.string :state, null: false
16
14
 
17
- t.timestamps
15
+ t.timestamps precision: Dipa.datetime_precision
18
16
  end
19
17
  end
20
18
  end
@@ -3,12 +3,12 @@
3
3
  class CreateDipaAgents < ActiveRecord::Migration[6.0]
4
4
  def change # rubocop:disable Metrics/MethodLength
5
5
  create_table :dipa_agents do |t|
6
- t.datetime :finished_at
7
- t.datetime :started_at
6
+ t.datetime :finished_at, precision: Dipa.datetime_precision
7
+ t.datetime :started_at, precision: Dipa.datetime_precision
8
8
  t.integer :index, null: false
9
9
  t.string :state, null: false
10
10
 
11
- t.timestamps
11
+ t.timestamps precision: Dipa.datetime_precision
12
12
  end
13
13
 
14
14
  add_belongs_to(
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDipaCoordinatorOptionsRecord < ActiveRecord::Migration[6.0]
4
+ def change # rubocop:disable Metrics/MethodLength
5
+ create_table :dipa_coordinator_options_records do |t|
6
+ t.boolean :keep_data, default: false, null: false
7
+ t.boolean :want_result, default: true, null: false
8
+ t.integer :agent_timeout, null: false
9
+ t.integer :agent_processing_timeout, null: false
10
+ t.integer :coordinator_timeout, null: false
11
+ t.integer :coordinator_processing_timeout, null: false
12
+
13
+ t.timestamps precision: Dipa.datetime_precision
14
+ end
15
+
16
+ add_belongs_to(
17
+ :dipa_coordinator_options_records, :dipa_coordinator,
18
+ foreign_key: true, index: true, null: false
19
+ )
20
+ end
21
+ end
data/lib/dipa/engine.rb CHANGED
@@ -22,14 +22,10 @@ module Dipa
22
22
  initializer 'dipa.queue_names' do
23
23
  config.after_initialize do |app|
24
24
  Dipa.agent_queue = (
25
- app.config.dipa.agent_queue ||
26
- app.config.active_job.default_queue_name ||
27
- :default
25
+ app.config.dipa.agent_queue || app.config.active_job.default_queue_name || :default
28
26
  ).to_sym
29
27
  Dipa.coordinator_queue = (
30
- app.config.dipa.coordinator_queue ||
31
- app.config.active_job.default_queue_name ||
32
- :default
28
+ app.config.dipa.coordinator_queue || app.config.active_job.default_queue_name || :default
33
29
  ).to_sym
34
30
  end
35
31
  end
@@ -39,11 +35,22 @@ module Dipa
39
35
  Dipa.agent_timeout = (
40
36
  app.config.dipa.agent_timeout || Dipa::DEFAULT_AGENT_TIMEOUT
41
37
  ).to_i
38
+ Dipa.agent_processing_timeout = (
39
+ app.config.dipa.agent_processing_timeout || Dipa::DEFAULT_AGENT_PROCESSING_TIMEOUT
40
+ ).to_i
42
41
  Dipa.coordinator_timeout = (
43
- app.config.dipa.coordinator_timeout ||
44
- Dipa::DEFAULT_COORDINATOR_TIMEOUT
42
+ app.config.dipa.coordinator_timeout || Dipa::DEFAULT_COORDINATOR_TIMEOUT
43
+ ).to_i
44
+ Dipa.coordinator_processing_timeout = (
45
+ app.config.dipa.coordinator_processing_timeout || Dipa::DEFAULT_COORDINATOR_PROCESSING_TIMEOUT
45
46
  ).to_i
46
47
  end
47
48
  end
49
+
50
+ initializer 'dipa.datetime_precision' do
51
+ config.after_initialize do
52
+ Dipa.datetime_precision = Dipa::DATETIME_PRECISION
53
+ end
54
+ end
48
55
  end
49
56
  end
data/lib/dipa/errors.rb CHANGED
@@ -3,10 +3,27 @@
3
3
  module Dipa
4
4
  class Error < StandardError; end
5
5
 
6
- class AbortedError < Error; end
7
- class ProcessingFailedError < Error; end
8
- class TimeoutError < Error; end
6
+ class AlreadyProcessingError < Error; end
9
7
  class UnknownProcessingStateError < Error; end
8
+
9
+ class AgentError < Error; end
10
+ class AgentAbortedThroughCoordinatorError < AgentError; end
11
+ class AgentAbortedThroughCoordinatorTimedOutError < AgentAbortedThroughCoordinatorError; end
12
+ class AgentAbortedThroughCoordinatorProcessingTimedOutError < AgentAbortedThroughCoordinatorError; end
13
+ class AgentProcessingTimedOutError < AgentError; end
14
+ class AgentTimedOutError < AgentError; end
15
+
16
+ class CoordinatorError < Error; end
17
+ class CoordinatorAbortedError < CoordinatorError; end
18
+ class CoordinatorAbortedThroughAgentError < CoordinatorError; end
19
+ class CoordinatorAbortedThroughAgentTimedOutError < CoordinatorAbortedThroughAgentError; end
20
+ class CoordinatorAbortedThroughAgentProcessingTimedOutError < CoordinatorAbortedThroughAgentError; end
21
+ class CoordinatorAbortedThroughAgentProcessingFailedError < CoordinatorAbortedThroughAgentError; end
22
+ class CoordinatorAbortedThroughAgentProcessingFailureError < CoordinatorAbortedThroughAgentError; end
23
+ class CoordinatorProcessingFailedError < CoordinatorError; end
24
+ class CoordinatorProcessingTimedOutError < CoordinatorError; end
25
+ class CoordinatorTimedOutError < CoordinatorError; end
26
+
10
27
  class UnknownProcessorClassError < Error; end
11
28
  class UnknownProcessorMethodError < Error; end
12
29
  end