solid_queue 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -10
- data/app/models/solid_queue/blocked_execution.rb +16 -10
- data/app/models/solid_queue/claimed_execution.rb +11 -4
- data/app/models/solid_queue/execution/dispatching.rb +2 -3
- data/app/models/solid_queue/execution.rb +32 -15
- data/app/models/solid_queue/failed_execution.rb +10 -6
- data/app/models/solid_queue/job/executable.rb +1 -1
- data/app/models/solid_queue/job/schedulable.rb +1 -1
- data/app/models/solid_queue/process/prunable.rb +6 -5
- data/app/models/solid_queue/process.rb +13 -6
- data/app/models/solid_queue/recurring_execution.rb +3 -3
- data/app/models/solid_queue/scheduled_execution.rb +3 -1
- data/app/models/solid_queue/semaphore.rb +1 -1
- data/lib/active_job/queue_adapters/solid_queue_adapter.rb +4 -0
- data/lib/generators/solid_queue/install/templates/config.yml +1 -1
- data/lib/puma/plugin/solid_queue.rb +1 -0
- data/lib/solid_queue/app_executor.rb +1 -1
- data/lib/solid_queue/dispatcher/recurring_task.rb +13 -7
- data/lib/solid_queue/dispatcher.rb +4 -4
- data/lib/solid_queue/engine.rb +4 -2
- data/lib/solid_queue/log_subscriber.rb +164 -0
- data/lib/solid_queue/processes/base.rb +16 -0
- data/lib/solid_queue/processes/interruptible.rb +1 -1
- data/lib/solid_queue/processes/poller.rb +7 -5
- data/lib/solid_queue/processes/registrable.rb +5 -23
- data/lib/solid_queue/processes/runnable.rb +4 -3
- data/lib/solid_queue/processes/signals.rb +1 -1
- data/lib/solid_queue/supervisor.rb +25 -24
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue/worker.rb +6 -6
- data/lib/solid_queue.rb +19 -10
- metadata +23 -11
- data/lib/solid_queue/recurring_tasks/manager.rb +0 -31
- data/lib/solid_queue/recurring_tasks/schedule.rb +0 -58
- data/lib/solid_queue/recurring_tasks/task.rb +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 490264bacfa32881edc69e3fa349081bfa1666785db38c7df55824ab6bd538b4
|
4
|
+
data.tar.gz: b12570f172afad018ac4709c7203276c0ba737ff90654cae02cadc3197be0357
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5909c1a726457b029d0de62bd22bf6929d84ebcddf35a56a8a0af10a0be7108ca1eb60ed48294ef77013f03d9e3c0f9b87a34d1e54ec966742abf79f5edd41f
|
7
|
+
data.tar.gz: daec7288a9652399f5dd75c0c6d8a5d5def60f9772a122a8b6212a675bb2d5e6ccee831156afd59b624204c694276f5011df64a122df7d44b578e848798cb5fe
|
data/README.md
CHANGED
@@ -171,6 +171,7 @@ There are several settings that control how Solid Queue works that you can set a
|
|
171
171
|
- `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`.
|
172
172
|
- `clear_finished_jobs_after`: period to keep finished jobs around, in case `preserve_finished_jobs` is true—defaults to 1 day. **Note:** Right now, there's no automatic cleanup of finished jobs. You'd need to do this by periodically invoking `SolidQueue::Job.clear_finished_in_batches`, but this will happen automatically in the near future.
|
173
173
|
- `default_concurrency_control_period`: the value to be used as the default for the `duration` parameter in [concurrency controls](#concurrency-controls). It defaults to 3 minutes.
|
174
|
+
- `enqueue_after_transaction_commit`: whether the job queuing is deferred to after the current Active Record transaction is committed. The default is `false`. [Read more](https://github.com/rails/rails/pull/51426).
|
174
175
|
|
175
176
|
|
176
177
|
## Concurrency controls
|
@@ -233,7 +234,7 @@ failed_execution.retry # This will re-enqueue the job as if it was enqueued for
|
|
233
234
|
failed_execution.discard # This will delete the job from the system
|
234
235
|
```
|
235
236
|
|
236
|
-
However, we recommend taking a look at [mission_control-jobs](https://github.com/
|
237
|
+
However, we recommend taking a look at [mission_control-jobs](https://github.com/rails/mission_control-jobs), a dashboard where, among other things, you can examine and retry/discard failed jobs.
|
237
238
|
|
238
239
|
## Puma plugin
|
239
240
|
We provide a Puma plugin if you want to run the Solid Queue's supervisor together with Puma and have Puma monitor and manage it. You just need to add
|
@@ -246,9 +247,12 @@ to your `puma.rb` configuration.
|
|
246
247
|
## Jobs and transactional integrity
|
247
248
|
:warning: Having your jobs in the same ACID-compliant database as your application data enables a powerful yet sharp tool: taking advantage of transactional integrity to ensure some action in your app is not committed unless your job is also committed. This can be very powerful and useful, but it can also backfire if you base some of your logic on this behaviour, and in the future, you move to another active job backend, or if you simply move Solid Queue to its own database, and suddenly the behaviour changes under you.
|
248
249
|
|
250
|
+
By default, Solid Queue runs in the same DB as your app, and job enqueuing is _not_ deferred until any ongoing transaction is committed, which means that by default, you'll be taking advantage of this transactional integrity.
|
251
|
+
|
249
252
|
If you prefer not to rely on this, or avoid relying on it unintentionally, you should make sure that:
|
250
|
-
-
|
251
|
-
- Or,
|
253
|
+
- You set [`config.active_job.enqueue_after_transaction_commit`](https://edgeguides.rubyonrails.org/configuring.html#config-active-job-enqueue-after-transaction-commit) to `always`, if you're using Rails 7.2+.
|
254
|
+
- Or, your jobs relying on specific records are always enqueued on [`after_commit` callbacks](https://guides.rubyonrails.org/active_record_callbacks.html#after-commit-and-after-rollback) or otherwise from a place where you're certain that whatever data the job will use has been committed to the database before the job is enqueued.
|
255
|
+
- Or, you configure a database for Solid Queue, even if it's the same as your app, ensuring that a different connection on the thread handling requests or running jobs for your app will be used to enqueue jobs. For example:
|
252
256
|
|
253
257
|
```ruby
|
254
258
|
class ApplicationRecord < ActiveRecord::Base
|
@@ -261,13 +265,6 @@ If you prefer not to rely on this, or avoid relying on it unintentionally, you s
|
|
261
265
|
config.solid_queue.connects_to = { database: { writing: :primary, reading: :replica } }
|
262
266
|
```
|
263
267
|
|
264
|
-
## Inspiration
|
265
|
-
|
266
|
-
Solid Queue has been inspired by [resque](https://github.com/resque/resque) and [GoodJob](https://github.com/bensheldon/good_job). We recommend checking out these projects as they're great examples from which we've learnt a lot.
|
267
|
-
|
268
|
-
## License
|
269
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
270
|
-
|
271
268
|
## Recurring tasks
|
272
269
|
Solid Queue supports defining recurring tasks that run at specific times in the future, on a regular basis like cron jobs. These are managed by dispatcher processes and as such, they can be defined in the dispatcher's configuration like this:
|
273
270
|
```yml
|
@@ -312,3 +309,10 @@ You can still configure this in Solid Queue:
|
|
312
309
|
schedule: "*/5 * * * *"
|
313
310
|
```
|
314
311
|
and the job will be enqueued via `perform_later` so it'll run in Resque. However, in this case we won't track any `solid_queue_recurring_execution` record for it and there won't be any guarantees that the job is enqueued only once each time.
|
312
|
+
|
313
|
+
## Inspiration
|
314
|
+
|
315
|
+
Solid Queue has been inspired by [resque](https://github.com/resque/resque) and [GoodJob](https://github.com/bensheldon/good_job). We recommend checking out these projects as they're great examples from which we've learnt a lot.
|
316
|
+
|
317
|
+
## License
|
318
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -10,21 +10,25 @@ module SolidQueue
|
|
10
10
|
scope :expired, -> { where(expires_at: ...Time.current) }
|
11
11
|
|
12
12
|
class << self
|
13
|
-
def unblock(
|
14
|
-
|
15
|
-
|
13
|
+
def unblock(limit)
|
14
|
+
SolidQueue.instrument(:release_many_blocked, limit: limit) do |payload|
|
15
|
+
expired.distinct.limit(limit).pluck(:concurrency_key).then do |concurrency_keys|
|
16
|
+
payload[:size] = release_many releasable(concurrency_keys)
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
19
21
|
def release_many(concurrency_keys)
|
20
22
|
# We want to release exactly one blocked execution for each concurrency key, and we need to do it
|
21
23
|
# one by one, locking each record and acquiring the semaphore individually for each of them:
|
22
|
-
Array(concurrency_keys).
|
24
|
+
Array(concurrency_keys).count { |concurrency_key| release_one(concurrency_key) }
|
23
25
|
end
|
24
26
|
|
25
27
|
def release_one(concurrency_key)
|
26
28
|
transaction do
|
27
|
-
ordered.where(concurrency_key: concurrency_key).limit(1).non_blocking_lock.
|
29
|
+
if execution = ordered.where(concurrency_key: concurrency_key).limit(1).non_blocking_lock.first
|
30
|
+
execution.release
|
31
|
+
end
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
@@ -38,12 +42,14 @@ module SolidQueue
|
|
38
42
|
end
|
39
43
|
|
40
44
|
def release
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
SolidQueue.instrument(:release_blocked, job_id: job.id, concurrency_key: concurrency_key, released: false) do |payload|
|
46
|
+
transaction do
|
47
|
+
if acquire_concurrency_lock
|
48
|
+
promote_to_ready
|
49
|
+
destroy!
|
45
50
|
|
46
|
-
|
51
|
+
payload[:released] = true
|
52
|
+
end
|
47
53
|
end
|
48
54
|
end
|
49
55
|
end
|
@@ -20,7 +20,12 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def release_all
|
23
|
-
|
23
|
+
SolidQueue.instrument(:release_many_claimed) do |payload|
|
24
|
+
includes(:job).tap do |executions|
|
25
|
+
payload[:size] = executions.size
|
26
|
+
executions.each(&:release)
|
27
|
+
end
|
28
|
+
end
|
24
29
|
end
|
25
30
|
|
26
31
|
def discard_all_in_batches(*)
|
@@ -45,9 +50,11 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
45
50
|
end
|
46
51
|
|
47
52
|
def release
|
48
|
-
|
49
|
-
|
50
|
-
|
53
|
+
SolidQueue.instrument(:release_claimed, job_id: job.id, process_id: process_id) do
|
54
|
+
transaction do
|
55
|
+
job.dispatch_bypassing_concurrency_limits
|
56
|
+
destroy!
|
57
|
+
end
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
@@ -9,9 +9,8 @@ module SolidQueue
|
|
9
9
|
def dispatch_jobs(job_ids)
|
10
10
|
jobs = Job.where(id: job_ids)
|
11
11
|
|
12
|
-
Job.dispatch_all(jobs).map(&:id).
|
13
|
-
where(job_id: dispatched_job_ids).
|
14
|
-
SolidQueue.logger.info("[SolidQueue] Dispatched #{dispatched_job_ids.size} jobs")
|
12
|
+
Job.dispatch_all(jobs).map(&:id).then do |dispatched_job_ids|
|
13
|
+
where(id: where(job_id: dispatched_job_ids).pluck(:id)).delete_all
|
15
14
|
end
|
16
15
|
end
|
17
16
|
end
|
@@ -13,6 +13,10 @@ module SolidQueue
|
|
13
13
|
belongs_to :job
|
14
14
|
|
15
15
|
class << self
|
16
|
+
def type
|
17
|
+
model_name.element.sub("_execution", "").to_sym
|
18
|
+
end
|
19
|
+
|
16
20
|
def create_all_from_jobs(jobs)
|
17
21
|
insert_all execution_data_from_jobs(jobs)
|
18
22
|
end
|
@@ -27,25 +31,32 @@ module SolidQueue
|
|
27
31
|
pending = count
|
28
32
|
discarded = 0
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
SolidQueue.instrument(:discard_all, batch_size: batch_size, status: type, batches: 0, size: 0) do |payload|
|
35
|
+
loop do
|
36
|
+
transaction do
|
37
|
+
job_ids = limit(batch_size).order(:job_id).lock.pluck(:job_id)
|
38
|
+
discarded = discard_jobs job_ids
|
33
39
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
40
|
+
where(job_id: job_ids).delete_all
|
41
|
+
pending -= discarded
|
42
|
+
|
43
|
+
payload[:size] += discarded
|
44
|
+
payload[:batches] += 1
|
45
|
+
end
|
38
46
|
|
39
|
-
|
47
|
+
break if pending <= 0 || discarded == 0
|
48
|
+
end
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
43
52
|
def discard_all_from_jobs(jobs)
|
44
|
-
|
45
|
-
|
53
|
+
SolidQueue.instrument(:discard_all, jobs_size: jobs.size, status: type) do |payload|
|
54
|
+
transaction do
|
55
|
+
job_ids = lock_all_from_jobs(jobs)
|
46
56
|
|
47
|
-
|
48
|
-
|
57
|
+
payload[:size] = discard_jobs job_ids
|
58
|
+
where(job_id: job_ids).delete_all
|
59
|
+
end
|
49
60
|
end
|
50
61
|
end
|
51
62
|
|
@@ -59,10 +70,16 @@ module SolidQueue
|
|
59
70
|
end
|
60
71
|
end
|
61
72
|
|
73
|
+
def type
|
74
|
+
self.class.type
|
75
|
+
end
|
76
|
+
|
62
77
|
def discard
|
63
|
-
|
64
|
-
|
65
|
-
|
78
|
+
SolidQueue.instrument(:discard, job_id: job_id, status: type) do
|
79
|
+
with_lock do
|
80
|
+
job.destroy
|
81
|
+
destroy
|
82
|
+
end
|
66
83
|
end
|
67
84
|
end
|
68
85
|
end
|
@@ -11,15 +11,19 @@ module SolidQueue
|
|
11
11
|
attr_accessor :exception
|
12
12
|
|
13
13
|
def self.retry_all(jobs)
|
14
|
-
|
15
|
-
|
14
|
+
SolidQueue.instrument(:retry_all, jobs_size: jobs.size) do |payload|
|
15
|
+
transaction do
|
16
|
+
payload[:size] = dispatch_jobs lock_all_from_jobs(jobs)
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
19
21
|
def retry
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
SolidQueue.instrument(:retry, job_id: job.id) do
|
23
|
+
with_lock do
|
24
|
+
job.prepare_for_execution
|
25
|
+
destroy!
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
@@ -33,5 +37,5 @@ module SolidQueue
|
|
33
37
|
self.error = { exception_class: exception.class.name, message: exception.message, backtrace: exception.backtrace }
|
34
38
|
end
|
35
39
|
end
|
36
|
-
|
40
|
+
end
|
37
41
|
end
|
@@ -4,15 +4,16 @@ module SolidQueue::Process::Prunable
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
scope :prunable, -> { where(
|
7
|
+
scope :prunable, -> { where(last_heartbeat_at: ..SolidQueue.process_alive_threshold.ago) }
|
8
8
|
end
|
9
9
|
|
10
10
|
class_methods do
|
11
11
|
def prune
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
SolidQueue.instrument :prune_processes, size: 0 do |payload|
|
13
|
+
prunable.non_blocking_lock.find_in_batches(batch_size: 50) do |batch|
|
14
|
+
payload[:size] += batch.size
|
15
|
+
|
16
|
+
batch.each { |process| process.deregister(pruned: true) }
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
@@ -12,17 +12,24 @@ class SolidQueue::Process < SolidQueue::Record
|
|
12
12
|
after_destroy -> { claimed_executions.release_all }
|
13
13
|
|
14
14
|
def self.register(**attributes)
|
15
|
-
|
15
|
+
SolidQueue.instrument :register_process, **attributes do
|
16
|
+
create!(attributes.merge(last_heartbeat_at: Time.current))
|
17
|
+
end
|
18
|
+
rescue Exception => error
|
19
|
+
SolidQueue.instrument :register_process, **attributes.merge(error: error)
|
20
|
+
raise
|
16
21
|
end
|
17
22
|
|
18
23
|
def heartbeat
|
19
24
|
touch(:last_heartbeat_at)
|
20
25
|
end
|
21
26
|
|
22
|
-
def deregister
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
def deregister(pruned: false)
|
28
|
+
SolidQueue.instrument :deregister_process, process: self, pruned: pruned, claimed_size: claimed_executions.size do |payload|
|
29
|
+
destroy!
|
30
|
+
rescue Exception => error
|
31
|
+
payload[:error] = error
|
32
|
+
raise
|
33
|
+
end
|
27
34
|
end
|
28
35
|
end
|
@@ -7,12 +7,12 @@ module SolidQueue
|
|
7
7
|
class << self
|
8
8
|
def record(task_key, run_at, &block)
|
9
9
|
transaction do
|
10
|
-
|
11
|
-
create!(job_id:
|
10
|
+
block.call.tap do |active_job|
|
11
|
+
create!(job_id: active_job.provider_job_id, task_key: task_key, run_at: run_at)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
rescue ActiveRecord::RecordNotUnique
|
15
|
-
|
15
|
+
# Task already dispatched
|
16
16
|
end
|
17
17
|
|
18
18
|
def clear_in_batches(batch_size: 500)
|
@@ -16,7 +16,9 @@ module SolidQueue
|
|
16
16
|
job_ids = next_batch(batch_size).non_blocking_lock.pluck(:job_id)
|
17
17
|
if job_ids.empty? then []
|
18
18
|
else
|
19
|
-
|
19
|
+
SolidQueue.instrument(:dispatch_scheduled, batch_size: batch_size) do |payload|
|
20
|
+
payload[:size] = dispatch_jobs(job_ids)
|
21
|
+
end
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -8,6 +8,10 @@ module ActiveJob
|
|
8
8
|
#
|
9
9
|
# Rails.application.config.active_job.queue_adapter = :solid_queue
|
10
10
|
class SolidQueueAdapter
|
11
|
+
def enqueue_after_transaction_commit?
|
12
|
+
SolidQueue.enqueue_after_transaction_commit
|
13
|
+
end
|
14
|
+
|
11
15
|
def enqueue(active_job) # :nodoc:
|
12
16
|
SolidQueue::Job.enqueue(active_job)
|
13
17
|
end
|
@@ -30,10 +30,16 @@ module SolidQueue
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def enqueue(at:)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
SolidQueue.instrument(:enqueue_recurring_task, task: key, at: at) do |payload|
|
34
|
+
if using_solid_queue_adapter?
|
35
|
+
perform_later_and_record(run_at: at)
|
36
|
+
else
|
37
|
+
payload[:other_adapter] = true
|
38
|
+
|
39
|
+
perform_later
|
40
|
+
end.tap do |active_job|
|
41
|
+
payload[:active_job_id] = active_job&.job_id
|
42
|
+
end
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
@@ -42,7 +48,7 @@ module SolidQueue
|
|
42
48
|
end
|
43
49
|
|
44
50
|
def to_s
|
45
|
-
"#{class_name}.perform_later(#{arguments.map(&:inspect).join(",")}) [ #{parsed_schedule.original
|
51
|
+
"#{class_name}.perform_later(#{arguments.map(&:inspect).join(",")}) [ #{parsed_schedule.original} ]"
|
46
52
|
end
|
47
53
|
|
48
54
|
def to_h
|
@@ -59,7 +65,7 @@ module SolidQueue
|
|
59
65
|
end
|
60
66
|
|
61
67
|
def perform_later_and_record(run_at:)
|
62
|
-
RecurringExecution.record(key, run_at) { perform_later
|
68
|
+
RecurringExecution.record(key, run_at) { perform_later }
|
63
69
|
end
|
64
70
|
|
65
71
|
def perform_later
|
@@ -82,4 +88,4 @@ module SolidQueue
|
|
82
88
|
@job_class ||= class_name.safe_constantize
|
83
89
|
end
|
84
90
|
end
|
85
|
-
end
|
91
|
+
end
|
@@ -19,6 +19,10 @@ module SolidQueue
|
|
19
19
|
@recurring_schedule = RecurringSchedule.new(options[:recurring_tasks])
|
20
20
|
end
|
21
21
|
|
22
|
+
def metadata
|
23
|
+
super.merge(batch_size: batch_size, concurrency_maintenance_interval: concurrency_maintenance&.interval, recurring_schedule: recurring_schedule.tasks.presence)
|
24
|
+
end
|
25
|
+
|
22
26
|
private
|
23
27
|
def poll
|
24
28
|
batch = dispatch_next_batch
|
@@ -50,9 +54,5 @@ module SolidQueue
|
|
50
54
|
def set_procline
|
51
55
|
procline "waiting"
|
52
56
|
end
|
53
|
-
|
54
|
-
def metadata
|
55
|
-
super.merge(batch_size: batch_size, concurrency_maintenance_interval: concurrency_maintenance&.interval, recurring_schedule: recurring_schedule.tasks.presence )
|
56
|
-
end
|
57
57
|
end
|
58
58
|
end
|
data/lib/solid_queue/engine.rb
CHANGED
@@ -18,7 +18,7 @@ module SolidQueue
|
|
18
18
|
|
19
19
|
initializer "solid_queue.app_executor", before: :run_prepare_callbacks do |app|
|
20
20
|
config.solid_queue.app_executor ||= app.executor
|
21
|
-
config.solid_queue.on_thread_error ||= ->
|
21
|
+
config.solid_queue.on_thread_error ||= ->(exception) { Rails.error.report(exception, handled: false) }
|
22
22
|
|
23
23
|
SolidQueue.app_executor = config.solid_queue.app_executor
|
24
24
|
SolidQueue.on_thread_error = config.solid_queue.on_thread_error
|
@@ -26,8 +26,10 @@ module SolidQueue
|
|
26
26
|
|
27
27
|
initializer "solid_queue.logger" do |app|
|
28
28
|
ActiveSupport.on_load(:solid_queue) do
|
29
|
-
self.logger
|
29
|
+
self.logger ||= app.logger
|
30
30
|
end
|
31
|
+
|
32
|
+
SolidQueue::LogSubscriber.attach_to :solid_queue
|
31
33
|
end
|
32
34
|
|
33
35
|
initializer "solid_queue.active_job.extensions" do
|