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

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