solid_queue 0.4.1 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -25
  3. data/UPGRADING.md +102 -0
  4. data/app/jobs/solid_queue/recurring_job.rb +9 -0
  5. data/app/models/solid_queue/claimed_execution.rb +21 -8
  6. data/app/models/solid_queue/process/executor.rb +13 -1
  7. data/app/models/solid_queue/process/prunable.rb +8 -1
  8. data/app/models/solid_queue/process.rb +13 -6
  9. data/app/models/solid_queue/recurring_execution.rb +17 -4
  10. data/app/models/solid_queue/recurring_task/arguments.rb +17 -0
  11. data/app/models/solid_queue/recurring_task.rb +122 -0
  12. data/app/models/solid_queue/semaphore.rb +18 -5
  13. data/db/migrate/20240719134516_create_recurring_tasks.rb +20 -0
  14. data/db/migrate/20240811173327_add_name_to_processes.rb +5 -0
  15. data/db/migrate/20240813160053_make_name_not_null.rb +16 -0
  16. data/db/migrate/20240819165045_change_solid_queue_recurring_tasks_static_to_not_null.rb +5 -0
  17. data/lib/generators/solid_queue/install/USAGE +1 -0
  18. data/lib/generators/solid_queue/install/install_generator.rb +21 -7
  19. data/lib/generators/solid_queue/install/templates/jobs +6 -0
  20. data/lib/puma/plugin/solid_queue.rb +10 -32
  21. data/lib/solid_queue/cli.rb +20 -0
  22. data/lib/solid_queue/configuration.rb +40 -29
  23. data/lib/solid_queue/dispatcher/recurring_schedule.rb +21 -12
  24. data/lib/solid_queue/dispatcher.rb +8 -8
  25. data/lib/solid_queue/lifecycle_hooks.rb +43 -0
  26. data/lib/solid_queue/log_subscriber.rb +13 -6
  27. data/lib/solid_queue/processes/base.rb +11 -0
  28. data/lib/solid_queue/processes/poller.rb +8 -4
  29. data/lib/solid_queue/processes/process_exit_error.rb +20 -0
  30. data/lib/solid_queue/processes/process_missing_error.rb +9 -0
  31. data/lib/solid_queue/processes/process_pruned_error.rb +11 -0
  32. data/lib/solid_queue/processes/registrable.rb +1 -0
  33. data/lib/solid_queue/processes/runnable.rb +10 -16
  34. data/lib/solid_queue/supervisor/maintenance.rb +5 -3
  35. data/lib/solid_queue/supervisor.rb +126 -10
  36. data/lib/solid_queue/version.rb +1 -1
  37. data/lib/solid_queue/worker.rb +5 -0
  38. data/lib/solid_queue.rb +10 -0
  39. metadata +33 -7
  40. data/lib/solid_queue/dispatcher/recurring_task.rb +0 -99
  41. data/lib/solid_queue/supervisor/async_supervisor.rb +0 -44
  42. data/lib/solid_queue/supervisor/fork_supervisor.rb +0 -108
@@ -0,0 +1,16 @@
1
+ class MakeNameNotNull < ActiveRecord::Migration[7.1]
2
+ def up
3
+ SolidQueue::Process.where(name: nil).find_each do |process|
4
+ process.name ||= [ process.kind.downcase, SecureRandom.hex(10) ].join("-")
5
+ process.save!
6
+ end
7
+
8
+ change_column :solid_queue_processes, :name, :string, null: false
9
+ add_index :solid_queue_processes, [ :name, :supervisor_id ], unique: true
10
+ end
11
+
12
+ def down
13
+ remove_index :solid_queue_processes, [ :name, :supervisor_id ]
14
+ change_column :solid_queue_processes, :name, :string, null: true
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ class ChangeSolidQueueRecurringTasksStaticToNotNull < ActiveRecord::Migration[7.1]
2
+ def change
3
+ change_column_null :solid_queue_recurring_tasks, :static, false, true
4
+ end
5
+ end
@@ -7,3 +7,4 @@ Example:
7
7
  This will perform the following:
8
8
  Installs solid_queue migrations
9
9
  Replaces Active Job's adapter in environment configuration
10
+ Installs bin/jobs binstub to start the supervisor
@@ -3,19 +3,33 @@
3
3
  class SolidQueue::InstallGenerator < Rails::Generators::Base
4
4
  source_root File.expand_path("templates", __dir__)
5
5
 
6
- class_option :skip_migrations, type: :boolean, default: nil, desc: "Skip migrations"
6
+ class_option :skip_adapter, type: :boolean, default: nil, desc: "Skip setting Solid Queue as the Active Job's adapter"
7
+ class_option :database, type: :string, default: nil, desc: "The database to use for migrations, if different from the primary one."
7
8
 
8
9
  def add_solid_queue
9
- if (env_config = Pathname(destination_root).join("config/environments/production.rb")).exist?
10
- gsub_file env_config, /(# )?config\.active_job\.queue_adapter\s+=.*/, "config.active_job.queue_adapter = :solid_queue"
10
+ unless options[:skip_adapter]
11
+ if (env_config = Pathname(destination_root).join("config/environments/production.rb")).exist?
12
+ say "Setting solid_queue as Active Job's queue adapter"
13
+ gsub_file env_config, /(# )?config\.active_job\.queue_adapter\s+=.*/, "config.active_job.queue_adapter = :solid_queue"
14
+ end
11
15
  end
12
16
 
13
- copy_file "config.yml", "config/solid_queue.yml"
17
+ if File.exist?("config/solid_queue.yml")
18
+ say "Skipping sample configuration as config/solid_queue.yml exists"
19
+ else
20
+ say "Copying sample configuration"
21
+ copy_file "config.yml", "config/solid_queue.yml"
22
+ end
23
+
24
+ say "Copying binstub"
25
+ copy_file "jobs", "bin/jobs"
26
+ chmod "bin/jobs", 0755 & ~File.umask, verbose: false
14
27
  end
15
28
 
16
29
  def create_migrations
17
- unless options[:skip_migrations]
18
- rails_command "railties:install:migrations FROM=solid_queue", inline: true
19
- end
30
+ say "Installing database migrations"
31
+ arguments = [ "FROM=solid_queue" ]
32
+ arguments << "DATABASE=#{options[:database]}" if options[:database].present?
33
+ rails_command "railties:install:migrations #{arguments.join(" ")}", inline: true
20
34
  end
21
35
  end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../config/environment"
4
+ require "solid_queue/cli"
5
+
6
+ SolidQueue::Cli.start(ARGV)
@@ -1,13 +1,5 @@
1
1
  require "puma/plugin"
2
2
 
3
- module Puma
4
- class DSL
5
- def solid_queue_mode(mode = :fork)
6
- @options[:solid_queue_mode] = mode.to_sym
7
- end
8
- end
9
- end
10
-
11
3
  Puma::Plugin.create do
12
4
  attr_reader :puma_pid, :solid_queue_pid, :log_writer, :solid_queue_supervisor
13
5
 
@@ -15,36 +7,22 @@ Puma::Plugin.create do
15
7
  @log_writer = launcher.log_writer
16
8
  @puma_pid = $$
17
9
 
18
- if launcher.options[:solid_queue_mode] == :async
19
- start_async(launcher)
20
- else
21
- start_forked(launcher)
10
+ in_background do
11
+ monitor_solid_queue
22
12
  end
23
- end
24
13
 
25
- private
26
- def start_forked(launcher)
27
- in_background do
28
- monitor_solid_queue
14
+ launcher.events.on_booted do
15
+ @solid_queue_pid = fork do
16
+ Thread.new { monitor_puma }
17
+ SolidQueue::Supervisor.start
29
18
  end
30
-
31
- launcher.events.on_booted do
32
- @solid_queue_pid = fork do
33
- Thread.new { monitor_puma }
34
- SolidQueue::Supervisor.start(mode: :fork)
35
- end
36
- end
37
-
38
- launcher.events.on_stopped { stop_solid_queue }
39
- launcher.events.on_restart { stop_solid_queue }
40
19
  end
41
20
 
42
- def start_async(launcher)
43
- launcher.events.on_booted { @solid_queue_supervisor = SolidQueue::Supervisor.start(mode: :async) }
44
- launcher.events.on_stopped { solid_queue_supervisor.stop }
45
- launcher.events.on_restart { solid_queue_supervisor.stop; solid_queue_supervisor.start }
46
- end
21
+ launcher.events.on_stopped { stop_solid_queue }
22
+ launcher.events.on_restart { stop_solid_queue }
23
+ end
47
24
 
25
+ private
48
26
  def stop_solid_queue
49
27
  Process.waitpid(solid_queue_pid, Process::WNOHANG)
50
28
  log "Stopping Solid Queue..."
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module SolidQueue
6
+ class Cli < Thor
7
+ class_option :config_file, type: :string, aliases: "-c", default: Configuration::DEFAULT_CONFIG_FILE_PATH, desc: "Path to config file"
8
+
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ desc :start, "Starts Solid Queue supervisor to dispatch and perform enqueued jobs. Default command."
14
+ default_command :start
15
+
16
+ def start
17
+ SolidQueue::Supervisor.start(load_configuration_from: options["config_file"])
18
+ end
19
+ end
20
+ end
@@ -2,6 +2,12 @@
2
2
 
3
3
  module SolidQueue
4
4
  class Configuration
5
+ class Process < Struct.new(:kind, :attributes)
6
+ def instantiate
7
+ "SolidQueue::#{kind.to_s.titleize}".safe_constantize.new(**attributes)
8
+ end
9
+ end
10
+
5
11
  WORKER_DEFAULTS = {
6
12
  queues: "*",
7
13
  threads: 3,
@@ -17,65 +23,70 @@ module SolidQueue
17
23
  recurring_tasks: []
18
24
  }
19
25
 
20
- def initialize(mode: :fork, load_from: nil)
21
- @mode = mode.to_s.inquiry
26
+ DEFAULT_CONFIG = {
27
+ workers: [ WORKER_DEFAULTS ],
28
+ dispatchers: [ DISPATCHER_DEFAULTS ]
29
+ }
30
+
31
+ def initialize(load_from: nil)
22
32
  @raw_config = config_from(load_from)
23
33
  end
24
34
 
25
- def processes
35
+ def configured_processes
26
36
  dispatchers + workers
27
37
  end
28
38
 
29
- def workers
30
- workers_options.flat_map do |worker_options|
31
- processes = if mode.fork?
32
- worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
33
- else
34
- WORKER_DEFAULTS[:processes]
35
- end
36
- processes.times.map { Worker.new(**worker_options.with_defaults(WORKER_DEFAULTS)) }
37
- end
38
- end
39
-
40
- def dispatchers
41
- dispatchers_options.map do |dispatcher_options|
42
- recurring_tasks = parse_recurring_tasks dispatcher_options[:recurring_tasks]
43
- Dispatcher.new **dispatcher_options.merge(recurring_tasks: recurring_tasks).with_defaults(DISPATCHER_DEFAULTS)
44
- end
45
- end
46
-
47
39
  def max_number_of_threads
48
40
  # At most "threads" in each worker + 1 thread for the worker + 1 thread for the heartbeat task
49
41
  workers_options.map { |options| options[:threads] }.max + 2
50
42
  end
51
43
 
52
44
  private
53
- attr_reader :raw_config, :mode
45
+ attr_reader :raw_config
54
46
 
55
47
  DEFAULT_CONFIG_FILE_PATH = "config/solid_queue.yml"
56
48
 
49
+ def workers
50
+ workers_options.flat_map do |worker_options|
51
+ processes = worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
52
+ processes.times.map { Process.new(:worker, worker_options.with_defaults(WORKER_DEFAULTS)) }
53
+ end
54
+ end
55
+
56
+ def dispatchers
57
+ dispatchers_options.map do |dispatcher_options|
58
+ recurring_tasks = parse_recurring_tasks dispatcher_options[:recurring_tasks]
59
+ Process.new :dispatcher, dispatcher_options.merge(recurring_tasks: recurring_tasks).with_defaults(DISPATCHER_DEFAULTS)
60
+ end
61
+ end
62
+
57
63
  def config_from(file_or_hash, env: Rails.env)
58
- config = load_config_from(file_or_hash)
59
- config[env.to_sym] ? config[env.to_sym] : config
64
+ load_config_from(file_or_hash).then do |config|
65
+ config = config[env.to_sym] ? config[env.to_sym] : config
66
+ if (config.keys & DEFAULT_CONFIG.keys).any? then config
67
+ else
68
+ DEFAULT_CONFIG
69
+ end
70
+ end
60
71
  end
61
72
 
62
73
  def workers_options
63
- @workers_options ||= options_from_raw_config(:workers, WORKER_DEFAULTS)
74
+ @workers_options ||= options_from_raw_config(:workers)
64
75
  .map { |options| options.dup.symbolize_keys }
65
76
  end
66
77
 
67
78
  def dispatchers_options
68
- @dispatchers_options ||= options_from_raw_config(:dispatchers, DISPATCHER_DEFAULTS)
79
+ @dispatchers_options ||= options_from_raw_config(:dispatchers)
69
80
  .map { |options| options.dup.symbolize_keys }
70
81
  end
71
82
 
72
- def options_from_raw_config(key, defaults)
73
- raw_config.empty? ? [ defaults ] : Array(raw_config[key])
83
+ def options_from_raw_config(key)
84
+ Array(raw_config[key])
74
85
  end
75
86
 
76
87
  def parse_recurring_tasks(tasks)
77
88
  Array(tasks).map do |id, options|
78
- Dispatcher::RecurringTask.from_configuration(id, **options)
89
+ RecurringTask.from_configuration(id, **options)
79
90
  end.select(&:valid?)
80
91
  end
81
92
 
@@ -7,7 +7,7 @@ module SolidQueue
7
7
  attr_reader :configured_tasks, :scheduled_tasks
8
8
 
9
9
  def initialize(tasks)
10
- @configured_tasks = Array(tasks).map { |task| Dispatcher::RecurringTask.wrap(task) }
10
+ @configured_tasks = Array(tasks).map { |task| SolidQueue::RecurringTask.wrap(task) }.select(&:valid?)
11
11
  @scheduled_tasks = Concurrent::Hash.new
12
12
  end
13
13
 
@@ -15,33 +15,42 @@ module SolidQueue
15
15
  configured_tasks.empty?
16
16
  end
17
17
 
18
- def load_tasks
18
+ def schedule_tasks
19
+ wrap_in_app_executor do
20
+ persist_tasks
21
+ reload_tasks
22
+ end
23
+
19
24
  configured_tasks.each do |task|
20
- load_task(task)
25
+ schedule_task(task)
21
26
  end
22
27
  end
23
28
 
24
- def load_task(task)
29
+ def schedule_task(task)
25
30
  scheduled_tasks[task.key] = schedule(task)
26
31
  end
27
32
 
28
- def unload_tasks
33
+ def unschedule_tasks
29
34
  scheduled_tasks.values.each(&:cancel)
30
35
  scheduled_tasks.clear
31
36
  end
32
37
 
33
- def tasks
34
- configured_tasks.each_with_object({}) { |task, hsh| hsh[task.key] = task.to_h }
35
- end
36
-
37
- def inspect
38
- configured_tasks.map(&:to_s).join(" | ")
38
+ def task_keys
39
+ configured_tasks.map(&:key)
39
40
  end
40
41
 
41
42
  private
43
+ def persist_tasks
44
+ SolidQueue::RecurringTask.create_or_update_all configured_tasks
45
+ end
46
+
47
+ def reload_tasks
48
+ @configured_tasks = SolidQueue::RecurringTask.where(key: task_keys)
49
+ end
50
+
42
51
  def schedule(task)
43
52
  scheduled_task = Concurrent::ScheduledTask.new(task.delay_from_now, args: [ self, task, task.next_time ]) do |thread_schedule, thread_task, thread_task_run_at|
44
- thread_schedule.load_task(thread_task)
53
+ thread_schedule.schedule_task(thread_task)
45
54
 
46
55
  wrap_in_app_executor do
47
56
  thread_task.enqueue(at: thread_task_run_at)
@@ -4,8 +4,8 @@ module SolidQueue
4
4
  class Dispatcher < Processes::Poller
5
5
  attr_accessor :batch_size, :concurrency_maintenance, :recurring_schedule
6
6
 
7
- after_boot :start_concurrency_maintenance, :load_recurring_schedule
8
- before_shutdown :stop_concurrency_maintenance, :unload_recurring_schedule
7
+ after_boot :start_concurrency_maintenance, :schedule_recurring_tasks
8
+ before_shutdown :stop_concurrency_maintenance, :unschedule_recurring_tasks
9
9
 
10
10
  def initialize(**options)
11
11
  options = options.dup.with_defaults(SolidQueue::Configuration::DISPATCHER_DEFAULTS)
@@ -19,7 +19,7 @@ module SolidQueue
19
19
  end
20
20
 
21
21
  def metadata
22
- super.merge(batch_size: batch_size, concurrency_maintenance_interval: concurrency_maintenance&.interval, recurring_schedule: recurring_schedule.tasks.presence)
22
+ super.merge(batch_size: batch_size, concurrency_maintenance_interval: concurrency_maintenance&.interval, recurring_schedule: recurring_schedule.task_keys.presence)
23
23
  end
24
24
 
25
25
  private
@@ -38,16 +38,16 @@ module SolidQueue
38
38
  concurrency_maintenance&.start
39
39
  end
40
40
 
41
- def load_recurring_schedule
42
- recurring_schedule.load_tasks
41
+ def schedule_recurring_tasks
42
+ recurring_schedule.schedule_tasks
43
43
  end
44
44
 
45
45
  def stop_concurrency_maintenance
46
46
  concurrency_maintenance&.stop
47
47
  end
48
48
 
49
- def unload_recurring_schedule
50
- recurring_schedule.unload_tasks
49
+ def unschedule_recurring_tasks
50
+ recurring_schedule.unschedule_tasks
51
51
  end
52
52
 
53
53
  def all_work_completed?
@@ -55,7 +55,7 @@ module SolidQueue
55
55
  end
56
56
 
57
57
  def set_procline
58
- procline "waiting"
58
+ procline "dispatching every #{polling_interval.seconds} seconds"
59
59
  end
60
60
  end
61
61
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueue
4
+ module LifecycleHooks
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ mattr_reader :lifecycle_hooks, default: { start: [], stop: [] }
9
+ end
10
+
11
+ class_methods do
12
+ def on_start(&block)
13
+ self.lifecycle_hooks[:start] << block
14
+ end
15
+
16
+ def on_stop(&block)
17
+ self.lifecycle_hooks[:stop] << block
18
+ end
19
+
20
+ def clear_hooks
21
+ self.lifecycle_hooks[:start] = []
22
+ self.lifecycle_hooks[:stop] = []
23
+ end
24
+ end
25
+
26
+ private
27
+ def run_start_hooks
28
+ run_hooks_for :start
29
+ end
30
+
31
+ def run_stop_hooks
32
+ run_hooks_for :stop
33
+ end
34
+
35
+ def run_hooks_for(event)
36
+ self.class.lifecycle_hooks.fetch(event, []).each do |block|
37
+ block.call
38
+ rescue Exception => exception
39
+ handle_thread_error(exception)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -12,11 +12,15 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
12
12
  end
13
13
 
14
14
  def release_many_claimed(event)
15
- debug formatted_event(event, action: "Release claimed jobs", **event.payload.slice(:size))
15
+ info formatted_event(event, action: "Release claimed jobs", **event.payload.slice(:size))
16
+ end
17
+
18
+ def fail_many_claimed(event)
19
+ warn formatted_event(event, action: "Fail claimed jobs", **event.payload.slice(:job_ids, :process_ids))
16
20
  end
17
21
 
18
22
  def release_claimed(event)
19
- debug formatted_event(event, action: "Release claimed job", **event.payload.slice(:job_id, :process_id))
23
+ info formatted_event(event, action: "Release claimed job", **event.payload.slice(:job_id, :process_id))
20
24
  end
21
25
 
22
26
  def retry_all(event)
@@ -63,7 +67,8 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
63
67
  attributes = {
64
68
  pid: process.pid,
65
69
  hostname: process.hostname,
66
- process_id: process.process_id
70
+ process_id: process.process_id,
71
+ name: process.name
67
72
  }.merge(process.metadata)
68
73
 
69
74
  info formatted_event(event, action: "Started #{process.kind}", **attributes)
@@ -75,7 +80,8 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
75
80
  attributes = {
76
81
  pid: process.pid,
77
82
  hostname: process.hostname,
78
- process_id: process.process_id
83
+ process_id: process.process_id,
84
+ name: process.name
79
85
  }.merge(process.metadata)
80
86
 
81
87
  info formatted_event(event, action: "Shutdown #{process.kind}", **attributes)
@@ -83,7 +89,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
83
89
 
84
90
  def register_process(event)
85
91
  process_kind = event.payload[:kind]
86
- attributes = event.payload.slice(:pid, :hostname, :process_id)
92
+ attributes = event.payload.slice(:pid, :hostname, :process_id, :name)
87
93
 
88
94
  if error = event.payload[:error]
89
95
  warn formatted_event(event, action: "Error registering #{process_kind}", **attributes.merge(error: formatted_error(error)))
@@ -99,6 +105,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
99
105
  process_id: process.id,
100
106
  pid: process.pid,
101
107
  hostname: process.hostname,
108
+ name: process.name,
102
109
  last_heartbeat_at: process.last_heartbeat_at.iso8601,
103
110
  claimed_size: event.payload[:claimed_size],
104
111
  pruned: event.payload[:pruned]
@@ -147,7 +154,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
147
154
  termsig: status.termsig
148
155
 
149
156
  if replaced_fork = event.payload[:fork]
150
- info formatted_event(event, action: "Replaced terminated #{replaced_fork.kind}", **attributes.merge(hostname: replaced_fork.hostname))
157
+ info formatted_event(event, action: "Replaced terminated #{replaced_fork.kind}", **attributes.merge(hostname: replaced_fork.hostname, name: replaced_fork.name))
151
158
  else
152
159
  warn formatted_event(event, action: "Tried to replace forked process but it had already died", **attributes)
153
160
  end
@@ -6,6 +6,12 @@ module SolidQueue
6
6
  include Callbacks # Defines callbacks needed by other concerns
7
7
  include AppExecutor, Registrable, Interruptible, Procline
8
8
 
9
+ attr_reader :name
10
+
11
+ def initialize(*)
12
+ @name = generate_name
13
+ end
14
+
9
15
  def kind
10
16
  self.class.name.demodulize
11
17
  end
@@ -21,6 +27,11 @@ module SolidQueue
21
27
  def metadata
22
28
  {}
23
29
  end
30
+
31
+ private
32
+ def generate_name
33
+ [ kind.downcase, SecureRandom.hex(10) ].join("-")
34
+ end
24
35
  end
25
36
  end
26
37
  end
@@ -8,6 +8,8 @@ module SolidQueue::Processes
8
8
 
9
9
  def initialize(polling_interval:, **options)
10
10
  @polling_interval = polling_interval
11
+
12
+ super(**options)
11
13
  end
12
14
 
13
15
  def metadata
@@ -43,10 +45,12 @@ module SolidQueue::Processes
43
45
  end
44
46
 
45
47
  def with_polling_volume
46
- if SolidQueue.silence_polling? && ActiveRecord::Base.logger
47
- ActiveRecord::Base.logger.silence { yield }
48
- else
49
- yield
48
+ SolidQueue.instrument(:polling) do
49
+ if SolidQueue.silence_polling? && ActiveRecord::Base.logger
50
+ ActiveRecord::Base.logger.silence { yield }
51
+ else
52
+ yield
53
+ end
50
54
  end
51
55
  end
52
56
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueue
4
+ module Processes
5
+ class ProcessExitError < RuntimeError
6
+ def initialize(status)
7
+ message = "Process pid=#{status.pid} exited unexpectedly."
8
+ if status.exitstatus.present?
9
+ message += " Exited with status #{status.exitstatus}."
10
+ end
11
+
12
+ if status.signaled?
13
+ message += " Received unhandled signal #{status.termsig}."
14
+ end
15
+
16
+ super(message)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module SolidQueue
2
+ module Processes
3
+ class ProcessMissingError < RuntimeError
4
+ def initialize
5
+ super("The process that was running this job no longer exists")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueue
4
+ module Processes
5
+ class ProcessPrunedError < RuntimeError
6
+ def initialize(last_heartbeat_at)
7
+ super("Process was found dead and pruned (last heartbeat at: #{last_heartbeat_at}")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -21,6 +21,7 @@ module SolidQueue::Processes
21
21
  def register
22
22
  @process = SolidQueue::Process.register \
23
23
  kind: kind,
24
+ name: name,
24
25
  pid: pid,
25
26
  hostname: hostname,
26
27
  supervisor: try(:supervisor),
@@ -7,11 +7,7 @@ module SolidQueue::Processes
7
7
  attr_writer :mode
8
8
 
9
9
  def start
10
- @stopped = false
11
-
12
- SolidQueue.instrument(:start_process, process: self) do
13
- run_callbacks(:boot) { boot }
14
- end
10
+ boot
15
11
 
16
12
  if running_async?
17
13
  @thread = create_thread { run }
@@ -25,14 +21,6 @@ module SolidQueue::Processes
25
21
  @thread&.join
26
22
  end
27
23
 
28
- def name
29
- @name ||= [ kind.downcase, SecureRandom.hex(6) ].join("-")
30
- end
31
-
32
- def alive?
33
- !running_async? || @thread.alive?
34
- end
35
-
36
24
  private
37
25
  DEFAULT_MODE = :async
38
26
 
@@ -41,9 +29,15 @@ module SolidQueue::Processes
41
29
  end
42
30
 
43
31
  def boot
44
- if running_as_fork?
45
- register_signal_handlers
46
- set_procline
32
+ SolidQueue.instrument(:start_process, process: self) do
33
+ run_callbacks(:boot) do
34
+ @stopped = false
35
+
36
+ if running_as_fork?
37
+ register_signal_handlers
38
+ set_procline
39
+ end
40
+ end
47
41
  end
48
42
  end
49
43
 
@@ -3,7 +3,7 @@ module SolidQueue
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- after_boot :release_orphaned_executions
6
+ after_boot :fail_orphaned_executions
7
7
  end
8
8
 
9
9
  private
@@ -27,8 +27,10 @@ module SolidQueue
27
27
  wrap_in_app_executor { SolidQueue::Process.prune }
28
28
  end
29
29
 
30
- def release_orphaned_executions
31
- wrap_in_app_executor { SolidQueue::ClaimedExecution.orphaned.release_all }
30
+ def fail_orphaned_executions
31
+ wrap_in_app_executor do
32
+ ClaimedExecution.orphaned.fail_all_with(Processes::ProcessMissingError.new)
33
+ end
32
34
  end
33
35
  end
34
36
  end