solid_queue 0.4.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -24
- 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/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 +0 -4
- data/lib/solid_queue/supervisor/maintenance.rb +5 -3
- data/lib/solid_queue/supervisor.rb +123 -10
- data/lib/solid_queue/version.rb +1 -1
- metadata +32 -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
@@ -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
|
@@ -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
|
@@ -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
|