solid_job_monitor 1.0.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +106 -0
- data/Rakefile +8 -0
- data/app/assets/config/mission_control_jobs_manifest.js +4 -0
- data/app/assets/stylesheets/mission_control/jobs/application.css +16 -0
- data/app/assets/stylesheets/mission_control/jobs/bulma.min.css +21013 -0
- data/app/assets/stylesheets/mission_control/jobs/forms.css +8 -0
- data/app/assets/stylesheets/mission_control/jobs/jobs.css +68 -0
- data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +24 -0
- data/app/controllers/concerns/mission_control/jobs/application_scoped.rb +38 -0
- data/app/controllers/concerns/mission_control/jobs/basic_authentication.rb +33 -0
- data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +17 -0
- data/app/controllers/concerns/mission_control/jobs/job_filters.rb +41 -0
- data/app/controllers/concerns/mission_control/jobs/job_scoped.rb +16 -0
- data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +36 -0
- data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +12 -0
- data/app/controllers/mission_control/jobs/application_controller.rb +32 -0
- data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +20 -0
- data/app/controllers/mission_control/jobs/bulk_retries_controller.rb +10 -0
- data/app/controllers/mission_control/jobs/discards_controller.rb +18 -0
- data/app/controllers/mission_control/jobs/dispatches_controller.rb +18 -0
- data/app/controllers/mission_control/jobs/jobs_controller.rb +54 -0
- data/app/controllers/mission_control/jobs/progresses_controller.rb +27 -0
- data/app/controllers/mission_control/jobs/queues/pauses_controller.rb +15 -0
- data/app/controllers/mission_control/jobs/queues_controller.rb +16 -0
- data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +38 -0
- data/app/controllers/mission_control/jobs/retries_controller.rb +13 -0
- data/app/controllers/mission_control/jobs/stops_controller.rb +22 -0
- data/app/controllers/mission_control/jobs/workers_controller.rb +23 -0
- data/app/helpers/mission_control/jobs/application_helper.rb +9 -0
- data/app/helpers/mission_control/jobs/dates_helper.rb +19 -0
- data/app/helpers/mission_control/jobs/interface_helper.rb +23 -0
- data/app/helpers/mission_control/jobs/jobs_helper.rb +94 -0
- data/app/helpers/mission_control/jobs/navigation_helper.rb +60 -0
- data/app/javascript/mission_control/jobs/application.js +4 -0
- data/app/javascript/mission_control/jobs/controllers/application.js +9 -0
- data/app/javascript/mission_control/jobs/controllers/form_controller.js +21 -0
- data/app/javascript/mission_control/jobs/controllers/index.js +11 -0
- data/app/javascript/mission_control/jobs/controllers/progress_controller.js +55 -0
- data/app/javascript/mission_control/jobs/helpers/debounce_helpers.js +9 -0
- data/app/javascript/mission_control/jobs/helpers/index.js +1 -0
- data/app/models/mission_control/jobs/application_record.rb +7 -0
- data/app/models/mission_control/jobs/current.rb +3 -0
- data/app/models/mission_control/jobs/job_progress.rb +7 -0
- data/app/models/mission_control/jobs/page.rb +48 -0
- data/app/models/mission_control/jobs/recurring_task.rb +25 -0
- data/app/models/mission_control/jobs/worker.rb +17 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
- data/app/views/layouts/mission_control/jobs/_flash.html.erb +9 -0
- data/app/views/layouts/mission_control/jobs/_navigation.html.erb +9 -0
- data/app/views/layouts/mission_control/jobs/application.html.erb +27 -0
- data/app/views/layouts/mission_control/jobs/application_selection/_applications.html.erb +13 -0
- data/app/views/layouts/mission_control/jobs/application_selection/_servers.html.erb +15 -0
- data/app/views/mission_control/jobs/jobs/_error_information.html.erb +22 -0
- data/app/views/mission_control/jobs/jobs/_filters.html.erb +55 -0
- data/app/views/mission_control/jobs/jobs/_general_information.html.erb +85 -0
- data/app/views/mission_control/jobs/jobs/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/_progress_bar.html.erb +16 -0
- data/app/views/mission_control/jobs/jobs/_raw_data.html.erb +4 -0
- data/app/views/mission_control/jobs/jobs/_title.html.erb +22 -0
- data/app/views/mission_control/jobs/jobs/_toolbar.html.erb +18 -0
- data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
- data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +8 -0
- data/app/views/mission_control/jobs/jobs/failed/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb +14 -0
- data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +7 -0
- data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +2 -0
- data/app/views/mission_control/jobs/jobs/in_progress/_actions.html.erb +6 -0
- data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/index.html.erb +19 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +13 -0
- data/app/views/mission_control/jobs/jobs/show.html.erb +6 -0
- data/app/views/mission_control/jobs/queues/_actions.html.erb +7 -0
- data/app/views/mission_control/jobs/queues/_job.html.erb +15 -0
- data/app/views/mission_control/jobs/queues/_queue.html.erb +16 -0
- data/app/views/mission_control/jobs/queues/_queue_title.html.erb +17 -0
- data/app/views/mission_control/jobs/queues/index.html.erb +16 -0
- data/app/views/mission_control/jobs/queues/show.html.erb +25 -0
- data/app/views/mission_control/jobs/recurring_tasks/_actions.html.erb +5 -0
- data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +37 -0
- data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +22 -0
- data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +10 -0
- data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +18 -0
- data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
- data/app/views/mission_control/jobs/shared/_job.html.erb +27 -0
- data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
- data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +7 -0
- data/app/views/mission_control/jobs/workers/_configuration.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/_raw_data.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/_title.html.erb +11 -0
- data/app/views/mission_control/jobs/workers/_worker.html.erb +21 -0
- data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
- data/app/views/mission_control/jobs/workers/index.html.erb +6 -0
- data/app/views/mission_control/jobs/workers/show.html.erb +14 -0
- data/config/importmap.rb +6 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20260828105144_create_mission_control_job_progresses.rb +16 -0
- data/lib/active_job/errors/invalid_operation.rb +5 -0
- data/lib/active_job/errors/job_not_found_error.rb +14 -0
- data/lib/active_job/errors/query_error.rb +5 -0
- data/lib/active_job/executing.rb +40 -0
- data/lib/active_job/execution_error.rb +8 -0
- data/lib/active_job/failed.rb +7 -0
- data/lib/active_job/job_proxy.rb +38 -0
- data/lib/active_job/jobs_relation.rb +318 -0
- data/lib/active_job/progress_reporting.rb +21 -0
- data/lib/active_job/querying.rb +44 -0
- data/lib/active_job/queue.rb +62 -0
- data/lib/active_job/queue_adapters/async_ext.rb +49 -0
- data/lib/active_job/queue_adapters/resque_ext.rb +317 -0
- data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +52 -0
- data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
- data/lib/active_job/queue_adapters/solid_queue_ext.rb +313 -0
- data/lib/active_job/queues.rb +33 -0
- data/lib/mission_control/jobs/adapter.rb +156 -0
- data/lib/mission_control/jobs/application.rb +20 -0
- data/lib/mission_control/jobs/applications.rb +8 -0
- data/lib/mission_control/jobs/arguments_filter.rb +24 -0
- data/lib/mission_control/jobs/authentication.rb +67 -0
- data/lib/mission_control/jobs/console/connect_to.rb +15 -0
- data/lib/mission_control/jobs/console/context.rb +11 -0
- data/lib/mission_control/jobs/console/jobs_help.rb +23 -0
- data/lib/mission_control/jobs/engine.rb +118 -0
- data/lib/mission_control/jobs/errors/incompatible_adapter.rb +2 -0
- data/lib/mission_control/jobs/errors/resource_not_found.rb +2 -0
- data/lib/mission_control/jobs/i18n_config.rb +17 -0
- data/lib/mission_control/jobs/identified_by_name.rb +18 -0
- data/lib/mission_control/jobs/identified_elements.rb +23 -0
- data/lib/mission_control/jobs/job_stopped_error.rb +5 -0
- data/lib/mission_control/jobs/progress/null_store.rb +25 -0
- data/lib/mission_control/jobs/progress/reporter.rb +63 -0
- data/lib/mission_control/jobs/progress/store.rb +72 -0
- data/lib/mission_control/jobs/progress.rb +17 -0
- data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
- data/lib/mission_control/jobs/server/serializable.rb +24 -0
- data/lib/mission_control/jobs/server/workers.rb +13 -0
- data/lib/mission_control/jobs/server.rb +27 -0
- data/lib/mission_control/jobs/tasks.rb +8 -0
- data/lib/mission_control/jobs/version.rb +5 -0
- data/lib/mission_control/jobs/workers_relation.rb +78 -0
- data/lib/mission_control/jobs.rb +43 -0
- data/lib/resque/thread_safe_redis.rb +34 -0
- data/lib/solid_job_monitor.rb +6 -0
- metadata +527 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module ActiveJob
|
|
2
|
+
module Errors
|
|
3
|
+
class JobNotFoundError < StandardError
|
|
4
|
+
attr_reader :job_relation
|
|
5
|
+
|
|
6
|
+
def initialize(job_or_job_id, job_relation)
|
|
7
|
+
@job_relation = job_relation
|
|
8
|
+
|
|
9
|
+
job_id = job_or_job_id.is_a?(ActiveJob::Base) ? job_or_job_id.job_id : job_or_job_id
|
|
10
|
+
super "Job with id '#{job_id}' not found"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# TODO: These (or a version of them) should be moved to +ActiveJob::Core+
|
|
2
|
+
# and related concerns when upstreamed.
|
|
3
|
+
module ActiveJob::Executing
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
attr_accessor :raw_data, :position, :finished_at, :blocked_by, :blocked_until, :worker_id, :started_at, :status
|
|
8
|
+
attr_reader :serialized_arguments
|
|
9
|
+
|
|
10
|
+
thread_cattr_accessor :current_queue_adapter
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class_methods do
|
|
14
|
+
def queue_adapter
|
|
15
|
+
ActiveJob::Base.current_queue_adapter || super
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def retry
|
|
20
|
+
ActiveJob.jobs.failed.retry_job(self)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def discard
|
|
24
|
+
jobs_relation_for_discarding.discard_job(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dispatch
|
|
28
|
+
ActiveJob.jobs.dispatch_job(self)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def jobs_relation_for_discarding
|
|
33
|
+
case status
|
|
34
|
+
when :failed then ActiveJob.jobs.failed
|
|
35
|
+
when :pending then ActiveJob.jobs.pending.where(queue_name: queue_name)
|
|
36
|
+
else
|
|
37
|
+
ActiveJob.jobs
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Information about a given error when executing a job.
|
|
2
|
+
#
|
|
3
|
+
# It's attached to failed jobs at +ActiveJob::Base#last_execution_error+.
|
|
4
|
+
ActiveJob::ExecutionError = Struct.new(:error_class, :message, :backtrace, keyword_init: true) do
|
|
5
|
+
def to_s
|
|
6
|
+
"ERROR #{error_class}: #{message}\n#{backtrace&.collect { |line| "\t#{line}" }&.join("\n")}"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# A proxy for managing jobs without having to load the corresponding
|
|
2
|
+
# job class.
|
|
3
|
+
#
|
|
4
|
+
# This is useful for managing jobs without having the job classes
|
|
5
|
+
# present in the code base.
|
|
6
|
+
class ActiveJob::JobProxy < ActiveJob::Base
|
|
7
|
+
class UnsupportedError < StandardError; end
|
|
8
|
+
|
|
9
|
+
attr_reader :job_class_name
|
|
10
|
+
# Raw data with the sensitive user data filtered out.
|
|
11
|
+
attr_accessor :filtered_raw_data
|
|
12
|
+
|
|
13
|
+
def initialize(job_data)
|
|
14
|
+
super
|
|
15
|
+
@job_class_name = job_data["job_class"]
|
|
16
|
+
deserialize(job_data)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def serialize
|
|
20
|
+
super.tap do |json|
|
|
21
|
+
json["job_class"] = @job_class_name
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def perform_now
|
|
26
|
+
raise UnsupportedError, "A JobProxy doesn't support immediate execution, only enqueuing."
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def duration
|
|
30
|
+
finished_at - (scheduled_at || enqueued_at)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
ActiveJob::JobsRelation::STATUSES.each do |status|
|
|
34
|
+
define_method "#{status}?" do
|
|
35
|
+
self.status == status
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# A relation of jobs that can be filtered and acted on.
|
|
2
|
+
#
|
|
3
|
+
# Relations of jobs are normally fetched via +ActiveJob.jobs+
|
|
4
|
+
# or through a given queue (+ActiveJob::Queue#jobs+).
|
|
5
|
+
#
|
|
6
|
+
# This class offers a fluid interface to query a subset of jobs. For
|
|
7
|
+
# example:
|
|
8
|
+
#
|
|
9
|
+
# queue = ActiveJob.queues[:default]
|
|
10
|
+
# queue.jobs.limit(10).where(job_class_name: "DummyJob").last
|
|
11
|
+
#
|
|
12
|
+
# Relations are enumerable, so you can use +Enumerable+ methods on them.
|
|
13
|
+
# Notice however that using these methods will imply loading all the relation
|
|
14
|
+
# in memory, which could introduce performance concerns.
|
|
15
|
+
#
|
|
16
|
+
# Internally, +ActiveJob+ will always use paginated queries to the underlying
|
|
17
|
+
# queue adapter. The page size can be controlled via +config.active_job.default_page_size+
|
|
18
|
+
# (1000 by default).
|
|
19
|
+
#
|
|
20
|
+
# There are additional performance concerns depending on the configured
|
|
21
|
+
# adapter. Please check +ActiveJob::Relation#where+, +ActiveJob::Relation#count+.
|
|
22
|
+
class ActiveJob::JobsRelation
|
|
23
|
+
include Enumerable
|
|
24
|
+
|
|
25
|
+
STATUSES = %i[ pending failed in_progress blocked scheduled finished ]
|
|
26
|
+
FILTERS = %i[ queue_name job_class_name ]
|
|
27
|
+
|
|
28
|
+
PROPERTIES = %i[ queue_name status offset_value limit_value job_class_name worker_id recurring_task_id finished_at ]
|
|
29
|
+
attr_reader(*PROPERTIES, :default_page_size)
|
|
30
|
+
|
|
31
|
+
delegate :last, :[], :reverse, to: :to_a
|
|
32
|
+
delegate :logger, to: MissionControl::Jobs
|
|
33
|
+
|
|
34
|
+
ALL_JOBS_LIMIT = 100_000_000 # When no limit value it defaults to "all jobs"
|
|
35
|
+
|
|
36
|
+
def initialize(queue_adapter: ActiveJob::Base.queue_adapter, default_page_size: ActiveJob::Base.default_page_size)
|
|
37
|
+
@queue_adapter = queue_adapter
|
|
38
|
+
@default_page_size = default_page_size
|
|
39
|
+
|
|
40
|
+
set_defaults
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns a +ActiveJob::JobsRelation+ with the configured filtering options.
|
|
44
|
+
#
|
|
45
|
+
# === Options
|
|
46
|
+
#
|
|
47
|
+
# * <tt>:job_class_name</tt> - To only include the jobs of a given class.
|
|
48
|
+
# Depending on the configured queue adapter, this will perform the
|
|
49
|
+
# filtering in memory, which could introduce performance concerns
|
|
50
|
+
# for large sets of jobs.
|
|
51
|
+
# * <tt>:queue_name</tt> - To only include the jobs in the provided queue.
|
|
52
|
+
# * <tt>:worker_id</tt> - To only include the jobs processed by the provided worker.
|
|
53
|
+
# * <tt>:recurring_task_id</tt> - To only include the jobs corresponding to runs of a recurring task.
|
|
54
|
+
# * <tt>:finished_at</tt> - (Range) To only include the jobs finished between the provided range
|
|
55
|
+
def where(job_class_name: nil, queue_name: nil, worker_id: nil, recurring_task_id: nil, finished_at: nil)
|
|
56
|
+
# Remove nil arguments to avoid overriding parameters when concatenating +where+ clauses
|
|
57
|
+
arguments = { job_class_name: job_class_name,
|
|
58
|
+
queue_name: queue_name&.to_s,
|
|
59
|
+
worker_id: worker_id,
|
|
60
|
+
recurring_task_id: recurring_task_id,
|
|
61
|
+
finished_at: finished_at
|
|
62
|
+
}.compact
|
|
63
|
+
|
|
64
|
+
clone_with(**arguments)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def with_status(status)
|
|
68
|
+
if status.to_sym.in? STATUSES
|
|
69
|
+
clone_with status: status.to_sym
|
|
70
|
+
else
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
STATUSES.each do |status|
|
|
76
|
+
define_method status do
|
|
77
|
+
with_status(status)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
define_method "#{status}?" do
|
|
81
|
+
self.status == status
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Sets an offset for the jobs-fetching query. The first position is 0.
|
|
86
|
+
def offset(offset)
|
|
87
|
+
clone_with offset_value: offset
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Sets the max number of jobs to fetch in the query.
|
|
91
|
+
def limit(limit)
|
|
92
|
+
clone_with limit_value: limit
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns the number of jobs in the relation.
|
|
96
|
+
#
|
|
97
|
+
# When filtering jobs, if the adapter doesn't support the filter(s)
|
|
98
|
+
# directly, this will load all the jobs in memory to filter them.
|
|
99
|
+
def count
|
|
100
|
+
if loaded? || filtering_needed?
|
|
101
|
+
to_a.length
|
|
102
|
+
else
|
|
103
|
+
query_count
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
alias length count
|
|
108
|
+
alias size count
|
|
109
|
+
|
|
110
|
+
def empty?
|
|
111
|
+
count == 0
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def to_s
|
|
115
|
+
properties_with_values = PROPERTIES.collect do |name|
|
|
116
|
+
value = public_send(name)
|
|
117
|
+
"#{name}: #{value}" unless value.nil?
|
|
118
|
+
end.compact.join(", ")
|
|
119
|
+
"<Jobs with [#{properties_with_values}]> (loaded: #{loaded?})"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
alias inspect to_s
|
|
123
|
+
|
|
124
|
+
def each(&block)
|
|
125
|
+
loaded_jobs&.each(&block) || load_jobs(&block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Retry all the jobs in the queue.
|
|
129
|
+
#
|
|
130
|
+
# This operation is only valid for sets of failed jobs. It will
|
|
131
|
+
# raise an error +ActiveJob::Errors::InvalidOperation+ otherwise.
|
|
132
|
+
def retry_all
|
|
133
|
+
ensure_failed_status
|
|
134
|
+
queue_adapter.retry_all_jobs(self)
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Retry the provided job.
|
|
139
|
+
#
|
|
140
|
+
# This operation is only valid for sets of failed jobs. It will
|
|
141
|
+
# raise an error +ActiveJob::Errors::InvalidOperation+ otherwise.
|
|
142
|
+
def retry_job(job)
|
|
143
|
+
ensure_failed_status
|
|
144
|
+
queue_adapter.retry_job(job, self)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Discard all the jobs in the relation.
|
|
148
|
+
def discard_all
|
|
149
|
+
queue_adapter.discard_all_jobs(self)
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Discard the provided job.
|
|
154
|
+
def discard_job(job)
|
|
155
|
+
queue_adapter.discard_job(job, self)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Dispatch the provided job.
|
|
159
|
+
#
|
|
160
|
+
# This operation is only valid for blocked or scheduled jobs. It will
|
|
161
|
+
# raise an error +ActiveJob::Errors::InvalidOperation+ otherwise.
|
|
162
|
+
def dispatch_job(job)
|
|
163
|
+
raise ActiveJob::Errors::InvalidOperation, "This operation can only be performed on blocked or scheduled jobs, but this job is #{job.status}" unless job.blocked? || job.scheduled?
|
|
164
|
+
|
|
165
|
+
queue_adapter.dispatch_job(job, self)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Find a job by id.
|
|
169
|
+
#
|
|
170
|
+
# Returns nil when not found.
|
|
171
|
+
def find_by_id(job_id)
|
|
172
|
+
queue_adapter.find_job(job_id, self)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Find a job by id.
|
|
176
|
+
#
|
|
177
|
+
# Raises +ActiveJob::Errors::JobNotFoundError+ when not found.
|
|
178
|
+
def find_by_id!(job_id)
|
|
179
|
+
queue_adapter.find_job(job_id, self) or raise ActiveJob::Errors::JobNotFoundError.new(job_id, self)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Returns an array of jobs class names in the first +from_first+ jobs.
|
|
183
|
+
def job_class_names(from_first: 500)
|
|
184
|
+
first(from_first).collect(&:job_class_name).uniq
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def reload
|
|
188
|
+
@count = nil
|
|
189
|
+
@loaded_jobs = nil
|
|
190
|
+
@filters = nil
|
|
191
|
+
|
|
192
|
+
self
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def in_batches(of: default_page_size, order: :asc, &block)
|
|
196
|
+
validate_looping_in_batches_is_possible
|
|
197
|
+
|
|
198
|
+
case order
|
|
199
|
+
when :asc
|
|
200
|
+
in_ascending_batches(of: of, &block)
|
|
201
|
+
when :desc
|
|
202
|
+
in_descending_batches(of: of, &block)
|
|
203
|
+
else
|
|
204
|
+
raise "Unsupported order: #{order}. Valid values: :asc, :desc."
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def paginated?
|
|
209
|
+
offset_value > 0 || limit_value_provided?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def limit_value_provided?
|
|
213
|
+
limit_value.present? && limit_value != ActiveJob::JobsRelation::ALL_JOBS_LIMIT
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def filtering_needed?
|
|
217
|
+
filters.any?
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
attr_reader :queue_adapter, :loaded_jobs
|
|
222
|
+
attr_writer(*PROPERTIES)
|
|
223
|
+
|
|
224
|
+
def set_defaults
|
|
225
|
+
self.offset_value = 0
|
|
226
|
+
self.limit_value = ALL_JOBS_LIMIT
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def clone_with(**properties)
|
|
230
|
+
dup.reload.tap do |relation|
|
|
231
|
+
properties.each do |key, value|
|
|
232
|
+
relation.send("#{key}=", value)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def query_count
|
|
238
|
+
@count ||= queue_adapter.jobs_count(self)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def load_jobs
|
|
242
|
+
@loaded_jobs = []
|
|
243
|
+
perform_each do |job|
|
|
244
|
+
@loaded_jobs << job
|
|
245
|
+
yield job
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def perform_each
|
|
250
|
+
current_offset = offset_value
|
|
251
|
+
pending_count = limit_value || Float::INFINITY
|
|
252
|
+
|
|
253
|
+
begin
|
|
254
|
+
limit = [ pending_count, default_page_size ].min
|
|
255
|
+
page = offset(current_offset).limit(limit)
|
|
256
|
+
jobs = queue_adapter.fetch_jobs(page)
|
|
257
|
+
finished = jobs.empty?
|
|
258
|
+
jobs = filter(jobs) if filtering_needed?
|
|
259
|
+
Array(jobs).each { |job| yield job }
|
|
260
|
+
current_offset += limit
|
|
261
|
+
pending_count -= jobs.length
|
|
262
|
+
end until finished || pending_count.zero?
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def loaded?
|
|
266
|
+
!@loaded_jobs.nil?
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Filtering for not natively supported filters is performed in memory
|
|
270
|
+
def filter(jobs)
|
|
271
|
+
jobs.filter { |job| satisfy_filter?(job) }
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def satisfy_filter?(job)
|
|
275
|
+
filters.all? { |property| public_send(property) == job.public_send(property) }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def filters
|
|
279
|
+
@filters ||= FILTERS.select { |property| public_send(property).present? && !queue_adapter.supports_job_filter?(self, property) }
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def ensure_failed_status
|
|
283
|
+
raise ActiveJob::Errors::InvalidOperation, "This operation can only be performed on failed jobs, but these jobs are #{status}" unless failed?
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def validate_looping_in_batches_is_possible
|
|
287
|
+
raise ActiveJob::Errors::InvalidOperation, "Looping in batches is not compatible with providing offset or limit" if paginated?
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def in_ascending_batches(of:)
|
|
291
|
+
current_offset = 0
|
|
292
|
+
max = count
|
|
293
|
+
begin
|
|
294
|
+
page = offset(current_offset).limit(of)
|
|
295
|
+
current_offset += of
|
|
296
|
+
logger.info page
|
|
297
|
+
yield page
|
|
298
|
+
wait_batch_delay
|
|
299
|
+
end until current_offset >= max
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def in_descending_batches(of:)
|
|
303
|
+
current_offset = count - of
|
|
304
|
+
|
|
305
|
+
begin
|
|
306
|
+
limit = current_offset < 0 ? of + current_offset : of
|
|
307
|
+
page = offset([ current_offset, 0 ].max).limit(limit)
|
|
308
|
+
current_offset -= of
|
|
309
|
+
logger.info page
|
|
310
|
+
yield page
|
|
311
|
+
wait_batch_delay
|
|
312
|
+
end until current_offset + of <= 0
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def wait_batch_delay
|
|
316
|
+
sleep MissionControl::Jobs.delay_between_bulk_operation_batches if MissionControl::Jobs.delay_between_bulk_operation_batches.to_i > 0
|
|
317
|
+
end
|
|
318
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Progress reporting mixed into Active Job. Unused methods are inert for jobs
|
|
2
|
+
# that never call +progress+.
|
|
3
|
+
module ActiveJob::ProgressReporting
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
discard_on MissionControl::Jobs::JobStoppedError
|
|
8
|
+
|
|
9
|
+
after_perform { |job| job.send(:complete_mission_control_progress) }
|
|
10
|
+
after_discard { |job, _error| job.send(:complete_mission_control_progress) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def progress
|
|
14
|
+
@mission_control_progress_reporter ||= MissionControl::Jobs::Progress::Reporter.new(job_id)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
def complete_mission_control_progress
|
|
19
|
+
@mission_control_progress_reporter&.complete
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module ActiveJob::Querying
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
# ActiveJob will use pagination internally when fetching relations of jobs. This
|
|
6
|
+
# parameter sets the max amount of jobs to fetch in each data store query.
|
|
7
|
+
class_attribute :default_page_size, default: 1000
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class_methods do
|
|
11
|
+
# Returns the list of queues.
|
|
12
|
+
#
|
|
13
|
+
# See +ActiveJob::Queues+
|
|
14
|
+
def queues
|
|
15
|
+
ActiveJob::Queues.new(fetch_queues)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def jobs
|
|
19
|
+
ActiveJob::JobsRelation.new(queue_adapter: queue_adapter, default_page_size: default_page_size)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def fetch_queues
|
|
24
|
+
queue_adapter.queues.collect do |queue|
|
|
25
|
+
ActiveJob::Queue.new(queue[:name], size: queue[:size], active: queue[:active], queue_adapter: queue_adapter)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def queue
|
|
31
|
+
self.class.queues[queue_name]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Top-level query methods added to `ActiveJob`
|
|
35
|
+
module Root
|
|
36
|
+
def queues
|
|
37
|
+
ActiveJob::Base.queues
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def jobs
|
|
41
|
+
ActiveJob::Base.jobs
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# A queue of jobs
|
|
2
|
+
class ActiveJob::Queue
|
|
3
|
+
attr_reader :name
|
|
4
|
+
|
|
5
|
+
def initialize(name, size: nil, active: nil, queue_adapter: ActiveJob::Base.queue_adapter)
|
|
6
|
+
@name = name
|
|
7
|
+
@queue_adapter = queue_adapter
|
|
8
|
+
|
|
9
|
+
@size = size
|
|
10
|
+
@active = active
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def size
|
|
14
|
+
@size ||= queue_adapter.queue_size(name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
alias length size
|
|
18
|
+
|
|
19
|
+
def clear
|
|
20
|
+
queue_adapter.clear_queue(name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def empty?
|
|
24
|
+
size == 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def pause
|
|
28
|
+
queue_adapter.pause_queue(name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resume
|
|
32
|
+
queue_adapter.resume_queue(name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def paused?
|
|
36
|
+
!active?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def active?
|
|
40
|
+
return @active unless @active.nil?
|
|
41
|
+
@active = !queue_adapter.queue_paused?(name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Return an +ActiveJob::JobsRelation+ with the pending jobs in the queue.
|
|
45
|
+
def jobs
|
|
46
|
+
ActiveJob::JobsRelation.new(queue_adapter: queue_adapter).pending.where(queue_name: name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reload
|
|
50
|
+
@active = @size = nil
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def id
|
|
55
|
+
name.parameterize
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
alias to_param id
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
attr_reader :queue_adapter
|
|
62
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module ActiveJob::QueueAdapters::AsyncExt
|
|
2
|
+
include MissionControl::Jobs::Adapter
|
|
3
|
+
|
|
4
|
+
# List of filters supported natively. Non-supported filters are done in memory.
|
|
5
|
+
def supported_job_filters(jobs_relation)
|
|
6
|
+
[]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def supports_queue_pausing?
|
|
10
|
+
false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def queues
|
|
14
|
+
[]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def queue_size(*)
|
|
18
|
+
0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def clear_queue(*)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def jobs_count(*)
|
|
25
|
+
0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def fetch_jobs(*)
|
|
29
|
+
[]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def retry_all_jobs(*)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def retry_job(job, *)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def discard_all_jobs(*)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def discard_job(*)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def dispatch_job(*)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def find_job(*)
|
|
48
|
+
end
|
|
49
|
+
end
|