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.
- checksums.yaml +4 -4
- data/README.md +45 -25
- data/UPGRADING.md +102 -0
- data/app/jobs/solid_queue/recurring_job.rb +9 -0
- data/app/models/solid_queue/claimed_execution.rb +21 -8
- data/app/models/solid_queue/process/executor.rb +13 -1
- data/app/models/solid_queue/process/prunable.rb +8 -1
- data/app/models/solid_queue/process.rb +13 -6
- data/app/models/solid_queue/recurring_execution.rb +17 -4
- data/app/models/solid_queue/recurring_task/arguments.rb +17 -0
- data/app/models/solid_queue/recurring_task.rb +122 -0
- data/app/models/solid_queue/semaphore.rb +18 -5
- data/db/migrate/20240719134516_create_recurring_tasks.rb +20 -0
- data/db/migrate/20240811173327_add_name_to_processes.rb +5 -0
- data/db/migrate/20240813160053_make_name_not_null.rb +16 -0
- data/db/migrate/20240819165045_change_solid_queue_recurring_tasks_static_to_not_null.rb +5 -0
- data/lib/generators/solid_queue/install/USAGE +1 -0
- data/lib/generators/solid_queue/install/install_generator.rb +21 -7
- data/lib/generators/solid_queue/install/templates/jobs +6 -0
- data/lib/puma/plugin/solid_queue.rb +10 -32
- data/lib/solid_queue/cli.rb +20 -0
- data/lib/solid_queue/configuration.rb +40 -29
- data/lib/solid_queue/dispatcher/recurring_schedule.rb +21 -12
- data/lib/solid_queue/dispatcher.rb +8 -8
- data/lib/solid_queue/lifecycle_hooks.rb +43 -0
- data/lib/solid_queue/log_subscriber.rb +13 -6
- data/lib/solid_queue/processes/base.rb +11 -0
- data/lib/solid_queue/processes/poller.rb +8 -4
- data/lib/solid_queue/processes/process_exit_error.rb +20 -0
- data/lib/solid_queue/processes/process_missing_error.rb +9 -0
- data/lib/solid_queue/processes/process_pruned_error.rb +11 -0
- data/lib/solid_queue/processes/registrable.rb +1 -0
- data/lib/solid_queue/processes/runnable.rb +10 -16
- data/lib/solid_queue/supervisor/maintenance.rb +5 -3
- data/lib/solid_queue/supervisor.rb +126 -10
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue/worker.rb +5 -0
- data/lib/solid_queue.rb +10 -0
- metadata +33 -7
- data/lib/solid_queue/dispatcher/recurring_task.rb +0 -99
- data/lib/solid_queue/supervisor/async_supervisor.rb +0 -44
- 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
|
@@ -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 :
|
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
|
-
|
10
|
-
|
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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
@@ -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
|
-
|
19
|
-
|
20
|
-
else
|
21
|
-
start_forked(launcher)
|
10
|
+
in_background do
|
11
|
+
monitor_solid_queue
|
22
12
|
end
|
23
|
-
end
|
24
13
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
21
|
-
|
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
|
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
|
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
|
-
|
59
|
-
|
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
|
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
|
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
|
73
|
-
|
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
|
-
|
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|
|
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
|
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
|
-
|
25
|
+
schedule_task(task)
|
21
26
|
end
|
22
27
|
end
|
23
28
|
|
24
|
-
def
|
29
|
+
def schedule_task(task)
|
25
30
|
scheduled_tasks[task.key] = schedule(task)
|
26
31
|
end
|
27
32
|
|
28
|
-
def
|
33
|
+
def unschedule_tasks
|
29
34
|
scheduled_tasks.values.each(&:cancel)
|
30
35
|
scheduled_tasks.clear
|
31
36
|
end
|
32
37
|
|
33
|
-
def
|
34
|
-
configured_tasks.
|
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.
|
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, :
|
8
|
-
before_shutdown :stop_concurrency_maintenance, :
|
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.
|
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
|
42
|
-
recurring_schedule.
|
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
|
50
|
-
recurring_schedule.
|
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 "
|
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
|
-
|
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
|
-
|
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
|
-
|
47
|
-
ActiveRecord::Base.logger
|
48
|
-
|
49
|
-
|
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,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
|
@@ -7,11 +7,7 @@ module SolidQueue::Processes
|
|
7
7
|
attr_writer :mode
|
8
8
|
|
9
9
|
def start
|
10
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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 :
|
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
|
31
|
-
wrap_in_app_executor
|
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
|