solid_queue 0.4.1 → 0.7.1

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