solid_queue 0.1.2 → 0.2.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/MIT-LICENSE +1 -1
- data/README.md +30 -26
- data/app/models/solid_queue/blocked_execution.rb +1 -1
- data/app/models/solid_queue/claimed_execution.rb +13 -1
- data/app/models/solid_queue/execution/dispatching.rb +20 -0
- data/app/models/solid_queue/execution/job_attributes.rb +12 -5
- data/app/models/solid_queue/execution.rb +55 -1
- data/app/models/solid_queue/failed_execution.rb +25 -19
- data/app/models/solid_queue/job/clearable.rb +4 -1
- data/app/models/solid_queue/job/concurrency_controls.rb +17 -1
- data/app/models/solid_queue/job/executable.rb +60 -21
- data/app/models/solid_queue/job/schedulable.rb +48 -0
- data/app/models/solid_queue/job.rb +50 -27
- data/app/models/solid_queue/queue.rb +1 -1
- data/app/models/solid_queue/ready_execution.rb +11 -1
- data/app/models/solid_queue/scheduled_execution.rb +4 -44
- data/app/models/solid_queue/semaphore.rb +75 -46
- data/db/migrate/20240110143450_add_missing_index_to_blocked_executions.rb +5 -0
- data/lib/active_job/concurrency_controls.rb +4 -0
- data/lib/active_job/queue_adapters/solid_queue_adapter.rb +6 -6
- data/lib/active_job/uniqueness.rb +41 -0
- data/lib/generators/solid_queue/install/USAGE +2 -2
- data/lib/generators/solid_queue/install/install_generator.rb +2 -4
- data/lib/generators/solid_queue/install/templates/config.yml +1 -1
- data/lib/puma/plugin/solid_queue.rb +10 -7
- data/lib/solid_queue/dispatcher/scheduled_executions_dispatcher.rb +6 -0
- data/lib/solid_queue/processes/runnable.rb +5 -3
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue/worker.rb +1 -1
- metadata +26 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0eb31439e2768af5f5d3fb9289ac51056570c762ad140bc54aba81ee770d5a12
|
4
|
+
data.tar.gz: 4d3e4c2608b0a3ed2de82c7712c8a524e8745bc4e2ddf6907de8b3723560ab04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f4271aaf7b55b86d81f97bf19bbfa2b76de1ff994a6b71bb097b89ab9585212f28c0fdbd4fbb86b74c18b49cf229e7913eaa724784b3a62f000d60c2ade3263
|
7
|
+
data.tar.gz: a84d91b13db4a3ec96d1afa5fb5f39abc9ea0bf4bc216c14e20f4bf7e1bf2ff414e08f78166377950351b8170cbd734aa69322feb9076a67a3f9939ebacf6573
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,29 +2,11 @@
|
|
2
2
|
|
3
3
|
Solid Queue is a DB-based queuing backend for [Active Job](https://edgeguides.rubyonrails.org/active_job_basics.html), designed with simplicity and performance in mind.
|
4
4
|
|
5
|
-
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job,
|
5
|
+
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`). _Improvements to logging and instrumentation, a better CLI tool, a way to run within an existing process in "async" mode, unique jobs and recurring, cron-like tasks are coming very soon._
|
6
6
|
|
7
7
|
Solid Queue can be used with SQL databases such as MySQL, PostgreSQL or SQLite, and it leverages the `FOR UPDATE SKIP LOCKED` clause, if available, to avoid blocking and waiting on locks when polling jobs. It relies on Active Job for retries, discarding, error handling, serialization, or delays, and it's compatible with Ruby on Rails multi-threading.
|
8
8
|
|
9
|
-
##
|
10
|
-
To set Solid Queue as your Active Job's queue backend, you should add this to your environment config:
|
11
|
-
```ruby
|
12
|
-
# config/environments/production.rb
|
13
|
-
config.active_job.queue_adapter = :solid_queue
|
14
|
-
```
|
15
|
-
|
16
|
-
Alternatively, you can set only specific jobs to use Solid Queue as their backend if you're migrating from another adapter and want to move jobs progressively:
|
17
|
-
|
18
|
-
```ruby
|
19
|
-
# app/jobs/my_job.rb
|
20
|
-
|
21
|
-
class MyJob < ApplicationJob
|
22
|
-
self.queue_adapter = :solid_queue
|
23
|
-
# ...
|
24
|
-
end
|
25
|
-
```
|
26
|
-
|
27
|
-
## Installation
|
9
|
+
## Installation and usage
|
28
10
|
Add this line to your application's Gemfile:
|
29
11
|
|
30
12
|
```ruby
|
@@ -41,23 +23,43 @@ Or install it yourself as:
|
|
41
23
|
$ gem install solid_queue
|
42
24
|
```
|
43
25
|
|
44
|
-
|
45
|
-
|
26
|
+
Now, you need to install the necessary migrations and configure the Active Job's adapter. You can do both at once using the provided generator:
|
27
|
+
|
46
28
|
```bash
|
47
29
|
$ bin/rails generate solid_queue:install
|
48
30
|
```
|
49
31
|
|
50
|
-
|
32
|
+
This will set `solid_queue` as the Active Job's adapter in production, and will copy the required migration over to your app.
|
33
|
+
|
34
|
+
Alternatively, you can add only the migration to your app:
|
51
35
|
```bash
|
52
36
|
$ bin/rails solid_queue:install:migrations
|
53
37
|
```
|
54
38
|
|
55
|
-
|
39
|
+
And set Solid Queue as your Active Job's queue backend manually, in your environment config:
|
40
|
+
```ruby
|
41
|
+
# config/environments/production.rb
|
42
|
+
config.active_job.queue_adapter = :solid_queue
|
43
|
+
```
|
44
|
+
|
45
|
+
Alternatively, you can set only specific jobs to use Solid Queue as their backend if you're migrating from another adapter and want to move jobs progressively:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
# app/jobs/my_job.rb
|
49
|
+
|
50
|
+
class MyJob < ApplicationJob
|
51
|
+
self.queue_adapter = :solid_queue
|
52
|
+
# ...
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Finally, you need to run the migrations:
|
57
|
+
|
56
58
|
```bash
|
57
59
|
$ bin/rails db:migrate
|
58
60
|
```
|
59
61
|
|
60
|
-
|
62
|
+
After this, you'll be ready to enqueue jobs using Solid Queue, but you need to start Solid Queue's supervisor to run them.
|
61
63
|
```bash
|
62
64
|
$ bundle exec rake solid_queue:start
|
63
65
|
```
|
@@ -65,7 +67,7 @@ $ bundle exec rake solid_queue:start
|
|
65
67
|
This will start processing jobs in all queues using the default configuration. See [below](#configuration) to learn more about configuring Solid Queue.
|
66
68
|
|
67
69
|
## Requirements
|
68
|
-
Besides Rails 7, Solid Queue works best with MySQL 8+ or PostgreSQL 9.5+, as they support `FOR UPDATE SKIP LOCKED`. You can use it with older versions, but in that case, you might run into lock waits if you run multiple workers for the same queue.
|
70
|
+
Besides Rails 7.1, Solid Queue works best with MySQL 8+ or PostgreSQL 9.5+, as they support `FOR UPDATE SKIP LOCKED`. You can use it with older versions, but in that case, you might run into lock waits if you run multiple workers for the same queue.
|
69
71
|
|
70
72
|
## Configuration
|
71
73
|
|
@@ -83,6 +85,7 @@ production:
|
|
83
85
|
dispatchers:
|
84
86
|
- polling_interval: 1
|
85
87
|
batch_size: 500
|
88
|
+
concurrency_maintenance_interval: 300
|
86
89
|
workers:
|
87
90
|
- queues: "*"
|
88
91
|
threads: 3
|
@@ -97,6 +100,7 @@ Everything is optional. If no configuration is provided, Solid Queue will run wi
|
|
97
100
|
|
98
101
|
- `polling_interval`: the time interval in seconds that workers and dispatchers will wait before checking for more jobs. This time defaults to `1` second for dispatchers and `0.1` seconds for workers.
|
99
102
|
- `batch_size`: the dispatcher will dispatch jobs in batches of this size. The default is 500.
|
103
|
+
- `concurrency_maintenance_interval`: the time interval in seconds that the dispatcher will wait before checking for blocked jobs that can be unblocked. Read more about [concurrency controls](#concurrency-controls) to learn more about this setting. It defaults to `600` seconds.
|
100
104
|
- `queues`: the list of queues that workers will pick jobs from. You can use `*` to indicate all queues (which is also the default and the behaviour you'll get if you omit this). You can provide a single queue, or a list of queues as an array. Jobs will be polled from those queues in order, so for example, with `[ real_time, background ]`, no jobs will be taken from `background` unless there aren't any more jobs waiting in `real_time`. You can also provide a prefix with a wildcard to match queues starting with a prefix. For example:
|
101
105
|
|
102
106
|
```yml
|
@@ -23,6 +23,14 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
23
23
|
def release_all
|
24
24
|
includes(:job).each(&:release)
|
25
25
|
end
|
26
|
+
|
27
|
+
def discard_all_in_batches(*)
|
28
|
+
raise UndiscardableError, "Can't discard jobs in progress"
|
29
|
+
end
|
30
|
+
|
31
|
+
def discard_all_from_jobs(*)
|
32
|
+
raise UndiscardableError, "Can't discard jobs in progress"
|
33
|
+
end
|
26
34
|
end
|
27
35
|
|
28
36
|
def perform
|
@@ -39,11 +47,15 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
39
47
|
|
40
48
|
def release
|
41
49
|
transaction do
|
42
|
-
job.
|
50
|
+
job.dispatch_bypassing_concurrency_limits
|
43
51
|
destroy!
|
44
52
|
end
|
45
53
|
end
|
46
54
|
|
55
|
+
def discard
|
56
|
+
raise UndiscardableError, "Can't discard a job in progress"
|
57
|
+
end
|
58
|
+
|
47
59
|
private
|
48
60
|
def execute
|
49
61
|
ActiveJob::Base.execute(job.arguments)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Execution
|
5
|
+
module Dispatching
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def dispatch_jobs(job_ids)
|
10
|
+
jobs = Job.where(id: job_ids)
|
11
|
+
|
12
|
+
Job.dispatch_all(jobs).map(&:id).tap do |dispatched_job_ids|
|
13
|
+
where(job_id: dispatched_job_ids).delete_all
|
14
|
+
SolidQueue.logger.info("[SolidQueue] Dispatched #{dispatched_job_ids.size} jobs")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -5,17 +5,24 @@ module SolidQueue
|
|
5
5
|
module JobAttributes
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
|
8
|
+
included do
|
9
|
+
class_attribute :assumable_attributes_from_job, instance_accessor: false, default: %i[ queue_name priority ]
|
10
|
+
end
|
9
11
|
|
10
12
|
class_methods do
|
11
|
-
def
|
12
|
-
|
13
|
+
def assumes_attributes_from_job(*attribute_names)
|
14
|
+
self.assumable_attributes_from_job |= attribute_names
|
15
|
+
before_create -> { assume_attributes_from_job }
|
16
|
+
end
|
17
|
+
|
18
|
+
def attributes_from_job(job)
|
19
|
+
job.attributes.symbolize_keys.slice(*assumable_attributes_from_job)
|
13
20
|
end
|
14
21
|
end
|
15
22
|
|
16
23
|
private
|
17
|
-
def assume_attributes_from_job
|
18
|
-
|
24
|
+
def assume_attributes_from_job
|
25
|
+
self.class.assumable_attributes_from_job.each do |attribute|
|
19
26
|
send("#{attribute}=", job.send(attribute))
|
20
27
|
end
|
21
28
|
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Execution < Record
|
5
|
+
class UndiscardableError < StandardError; end
|
6
|
+
|
5
7
|
include JobAttributes
|
6
8
|
|
7
9
|
self.abstract_class = true
|
@@ -10,6 +12,58 @@ module SolidQueue
|
|
10
12
|
|
11
13
|
belongs_to :job
|
12
14
|
|
13
|
-
|
15
|
+
class << self
|
16
|
+
def create_all_from_jobs(jobs)
|
17
|
+
insert_all execution_data_from_jobs(jobs)
|
18
|
+
end
|
19
|
+
|
20
|
+
def execution_data_from_jobs(jobs)
|
21
|
+
jobs.collect do |job|
|
22
|
+
attributes_from_job(job).merge(job_id: job.id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def discard_all_in_batches(batch_size: 500)
|
27
|
+
pending = count
|
28
|
+
discarded = 0
|
29
|
+
|
30
|
+
loop do
|
31
|
+
transaction do
|
32
|
+
job_ids = limit(batch_size).order(:job_id).lock.pluck(:job_id)
|
33
|
+
|
34
|
+
discard_jobs job_ids
|
35
|
+
discarded = where(job_id: job_ids).delete_all
|
36
|
+
pending -= discarded
|
37
|
+
end
|
38
|
+
|
39
|
+
break if pending <= 0 || discarded == 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def discard_all_from_jobs(jobs)
|
44
|
+
transaction do
|
45
|
+
job_ids = lock_all_from_jobs(jobs)
|
46
|
+
|
47
|
+
discard_jobs job_ids
|
48
|
+
where(job_id: job_ids).delete_all
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def lock_all_from_jobs(jobs)
|
54
|
+
where(job_id: jobs.map(&:id)).order(:job_id).lock.pluck(:job_id)
|
55
|
+
end
|
56
|
+
|
57
|
+
def discard_jobs(job_ids)
|
58
|
+
Job.where(id: job_ids).delete_all
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def discard
|
63
|
+
with_lock do
|
64
|
+
job.destroy
|
65
|
+
destroy
|
66
|
+
end
|
67
|
+
end
|
14
68
|
end
|
15
69
|
end
|
@@ -1,31 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module SolidQueue
|
4
|
+
class FailedExecution < Execution
|
5
|
+
include Dispatching
|
6
|
+
|
5
7
|
serialize :error, coder: JSON
|
6
|
-
else
|
7
|
-
serialize :error, JSON
|
8
|
-
end
|
9
8
|
|
10
|
-
|
9
|
+
before_create :expand_error_details_from_exception
|
10
|
+
|
11
|
+
attr_accessor :exception
|
11
12
|
|
12
|
-
|
13
|
+
def self.retry_all(jobs)
|
14
|
+
transaction do
|
15
|
+
dispatch_jobs lock_all_from_jobs(jobs)
|
16
|
+
end
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
def retry
|
20
|
+
with_lock do
|
21
|
+
job.prepare_for_execution
|
22
|
+
destroy!
|
23
|
+
end
|
18
24
|
end
|
19
|
-
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
%i[ exception_class message backtrace ].each do |attribute|
|
27
|
+
define_method(attribute) { error.with_indifferent_access[attribute] }
|
28
|
+
end
|
24
29
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
private
|
31
|
+
def expand_error_details_from_exception
|
32
|
+
if exception
|
33
|
+
self.error = { exception_class: exception.class.name, message: exception.message, backtrace: exception.backtrace }
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -11,7 +11,10 @@ module SolidQueue
|
|
11
11
|
|
12
12
|
class_methods do
|
13
13
|
def clear_finished_in_batches(batch_size: 500, finished_before: SolidQueue.clear_finished_jobs_after.ago)
|
14
|
-
|
14
|
+
loop do
|
15
|
+
records_deleted = clearable(finished_before: finished_before).limit(batch_size).delete_all
|
16
|
+
break if records_deleted == 0
|
17
|
+
end
|
15
18
|
end
|
16
19
|
end
|
17
20
|
end
|
@@ -6,9 +6,17 @@ module SolidQueue
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
has_one :blocked_execution
|
9
|
+
has_one :blocked_execution
|
10
10
|
|
11
11
|
delegate :concurrency_limit, :concurrency_duration, to: :job_class
|
12
|
+
|
13
|
+
before_destroy :unblock_next_blocked_job, if: -> { concurrency_limited? && ready? }
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def release_all_concurrency_locks(jobs)
|
18
|
+
Semaphore.signal_all(jobs.select(&:concurrency_limited?))
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
22
|
def unblock_next_blocked_job
|
@@ -21,6 +29,10 @@ module SolidQueue
|
|
21
29
|
concurrency_key.present?
|
22
30
|
end
|
23
31
|
|
32
|
+
def blocked?
|
33
|
+
blocked_execution.present?
|
34
|
+
end
|
35
|
+
|
24
36
|
private
|
25
37
|
def acquire_concurrency_lock
|
26
38
|
return true unless concurrency_limited?
|
@@ -45,6 +57,10 @@ module SolidQueue
|
|
45
57
|
def job_class
|
46
58
|
@job_class ||= class_name.safe_constantize
|
47
59
|
end
|
60
|
+
|
61
|
+
def execution
|
62
|
+
super || blocked_execution
|
63
|
+
end
|
48
64
|
end
|
49
65
|
end
|
50
66
|
end
|
@@ -6,20 +6,56 @@ module SolidQueue
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
include Clearable, ConcurrencyControls
|
9
|
+
include Clearable, ConcurrencyControls, Schedulable
|
10
10
|
|
11
|
-
has_one :ready_execution
|
12
|
-
has_one :claimed_execution
|
13
|
-
has_one :failed_execution
|
14
|
-
|
15
|
-
has_one :scheduled_execution, dependent: :destroy
|
11
|
+
has_one :ready_execution
|
12
|
+
has_one :claimed_execution
|
13
|
+
has_one :failed_execution
|
16
14
|
|
17
15
|
after_create :prepare_for_execution
|
18
16
|
|
19
17
|
scope :finished, -> { where.not(finished_at: nil) }
|
18
|
+
scope :failed, -> { includes(:failed_execution).where.not(failed_execution: { id: nil }) }
|
20
19
|
end
|
21
20
|
|
22
|
-
|
21
|
+
class_methods do
|
22
|
+
def prepare_all_for_execution(jobs)
|
23
|
+
due, not_yet_due = jobs.partition(&:due?)
|
24
|
+
dispatch_all(due) + schedule_all(not_yet_due)
|
25
|
+
end
|
26
|
+
|
27
|
+
def dispatch_all(jobs)
|
28
|
+
with_concurrency_limits, without_concurrency_limits = jobs.partition(&:concurrency_limited?)
|
29
|
+
|
30
|
+
dispatch_all_at_once(without_concurrency_limits)
|
31
|
+
dispatch_all_one_by_one(with_concurrency_limits)
|
32
|
+
|
33
|
+
successfully_dispatched(jobs)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def dispatch_all_at_once(jobs)
|
38
|
+
ReadyExecution.create_all_from_jobs jobs
|
39
|
+
end
|
40
|
+
|
41
|
+
def dispatch_all_one_by_one(jobs)
|
42
|
+
jobs.each(&:dispatch)
|
43
|
+
end
|
44
|
+
|
45
|
+
def successfully_dispatched(jobs)
|
46
|
+
dispatched_and_ready(jobs) + dispatched_and_blocked(jobs)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dispatched_and_ready(jobs)
|
50
|
+
where(id: ReadyExecution.where(job_id: jobs.map(&:id)).pluck(:job_id))
|
51
|
+
end
|
52
|
+
|
53
|
+
def dispatched_and_blocked(jobs)
|
54
|
+
where(id: BlockedExecution.where(job_id: jobs.map(&:id)).pluck(:job_id))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
%w[ ready claimed failed ].each do |status|
|
23
59
|
define_method("#{status}?") { public_send("#{status}_execution").present? }
|
24
60
|
end
|
25
61
|
|
@@ -37,6 +73,10 @@ module SolidQueue
|
|
37
73
|
end
|
38
74
|
end
|
39
75
|
|
76
|
+
def dispatch_bypassing_concurrency_limits
|
77
|
+
ready
|
78
|
+
end
|
79
|
+
|
40
80
|
def finished!
|
41
81
|
if preserve_finished_jobs?
|
42
82
|
touch(:finished_at)
|
@@ -49,35 +89,34 @@ module SolidQueue
|
|
49
89
|
finished_at.present?
|
50
90
|
end
|
51
91
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
92
|
+
def status
|
93
|
+
if finished?
|
94
|
+
:finished
|
95
|
+
elsif execution.present?
|
96
|
+
execution.model_name.element.sub("_execution", "").to_sym
|
97
|
+
end
|
58
98
|
end
|
59
99
|
|
60
100
|
def retry
|
61
101
|
failed_execution&.retry
|
62
102
|
end
|
63
103
|
|
104
|
+
def discard
|
105
|
+
execution&.discard
|
106
|
+
end
|
107
|
+
|
64
108
|
def failed_with(exception)
|
65
109
|
FailedExecution.create_or_find_by!(job_id: id, exception: exception)
|
66
110
|
end
|
67
111
|
|
68
112
|
private
|
69
|
-
def due?
|
70
|
-
scheduled_at.nil? || scheduled_at <= Time.current
|
71
|
-
end
|
72
|
-
|
73
|
-
def schedule
|
74
|
-
ScheduledExecution.create_or_find_by!(job_id: id)
|
75
|
-
end
|
76
|
-
|
77
113
|
def ready
|
78
114
|
ReadyExecution.create_or_find_by!(job_id: id)
|
79
115
|
end
|
80
116
|
|
117
|
+
def execution
|
118
|
+
%w[ ready claimed failed ].reduce(nil) { |acc, status| acc || public_send("#{status}_execution") }
|
119
|
+
end
|
81
120
|
|
82
121
|
def preserve_finished_jobs?
|
83
122
|
SolidQueue.preserve_finished_jobs
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Job
|
5
|
+
module Schedulable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
has_one :scheduled_execution
|
10
|
+
|
11
|
+
scope :scheduled, -> { where.not(finished_at: nil) }
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def schedule_all(jobs)
|
16
|
+
schedule_all_at_once(jobs)
|
17
|
+
successfully_scheduled(jobs)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def schedule_all_at_once(jobs)
|
22
|
+
ScheduledExecution.create_all_from_jobs(jobs)
|
23
|
+
end
|
24
|
+
|
25
|
+
def successfully_scheduled(jobs)
|
26
|
+
where(id: ScheduledExecution.where(job_id: jobs.map(&:id)).pluck(:job_id))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def due?
|
31
|
+
scheduled_at.nil? || scheduled_at <= Time.current
|
32
|
+
end
|
33
|
+
|
34
|
+
def scheduled?
|
35
|
+
scheduled_execution.present?
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def schedule
|
40
|
+
ScheduledExecution.create_or_find_by!(job_id: id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def execution
|
44
|
+
super || scheduled_execution
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,38 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module SolidQueue
|
4
|
+
class Job < Record
|
5
|
+
include Executable
|
5
6
|
|
6
|
-
if Gem::Version.new(Rails.version) >= Gem::Version.new("7.1")
|
7
7
|
serialize :arguments, coder: JSON
|
8
|
-
else
|
9
|
-
serialize :arguments, JSON
|
10
|
-
end
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
9
|
+
class << self
|
10
|
+
def enqueue_all(active_jobs)
|
11
|
+
active_jobs_by_job_id = active_jobs.index_by(&:job_id)
|
12
|
+
|
13
|
+
transaction do
|
14
|
+
jobs = create_all_from_active_jobs(active_jobs)
|
15
|
+
prepare_all_for_execution(jobs).tap do |enqueued_jobs|
|
16
|
+
enqueued_jobs.each do |enqueued_job|
|
17
|
+
active_jobs_by_job_id[enqueued_job.active_job_id].provider_job_id = enqueued_job.id
|
18
|
+
active_jobs_by_job_id[enqueued_job.active_job_id].successfully_enqueued = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
26
22
|
|
27
|
-
|
28
|
-
create!(**kwargs.compact.with_defaults(defaults)).tap do
|
29
|
-
SolidQueue.logger.debug "[SolidQueue] Enqueued job #{kwargs}"
|
23
|
+
active_jobs.count(&:successfully_enqueued?)
|
30
24
|
end
|
31
|
-
end
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
def enqueue(active_job, scheduled_at: Time.current)
|
27
|
+
active_job.scheduled_at = scheduled_at
|
28
|
+
|
29
|
+
create_from_active_job(active_job).tap do |enqueued_job|
|
30
|
+
active_job.provider_job_id = enqueued_job.id
|
31
|
+
end
|
36
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
DEFAULT_PRIORITY = 0
|
36
|
+
DEFAULT_QUEUE_NAME = "default"
|
37
|
+
|
38
|
+
def create_from_active_job(active_job)
|
39
|
+
create!(**attributes_from_active_job(active_job))
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_all_from_active_jobs(active_jobs)
|
43
|
+
job_rows = active_jobs.map { |job| attributes_from_active_job(job) }
|
44
|
+
insert_all(job_rows)
|
45
|
+
where(active_job_id: active_jobs.map(&:job_id))
|
46
|
+
end
|
47
|
+
|
48
|
+
def attributes_from_active_job(active_job)
|
49
|
+
{
|
50
|
+
queue_name: active_job.queue_name || DEFAULT_QUEUE_NAME,
|
51
|
+
active_job_id: active_job.job_id,
|
52
|
+
priority: active_job.priority || DEFAULT_PRIORITY,
|
53
|
+
scheduled_at: active_job.scheduled_at,
|
54
|
+
class_name: active_job.class.name,
|
55
|
+
arguments: active_job.serialize,
|
56
|
+
concurrency_key: active_job.concurrency_key
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
37
60
|
end
|
38
61
|
end
|
@@ -4,7 +4,7 @@ module SolidQueue
|
|
4
4
|
class ReadyExecution < Execution
|
5
5
|
scope :queued_as, ->(queue_name) { where(queue_name: queue_name) }
|
6
6
|
|
7
|
-
|
7
|
+
assumes_attributes_from_job
|
8
8
|
|
9
9
|
class << self
|
10
10
|
def claim(queue_list, limit, process_id)
|
@@ -15,6 +15,10 @@ module SolidQueue
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def aggregated_count_across(queue_list)
|
19
|
+
QueueSelector.new(queue_list, self).scoped_relations.map(&:count).sum
|
20
|
+
end
|
21
|
+
|
18
22
|
private
|
19
23
|
def select_and_lock(queue_relation, process_id, limit)
|
20
24
|
return [] if limit <= 0
|
@@ -36,6 +40,12 @@ module SolidQueue
|
|
36
40
|
where(job_id: claimed.pluck(:job_id)).delete_all
|
37
41
|
end
|
38
42
|
end
|
43
|
+
|
44
|
+
|
45
|
+
def discard_jobs(job_ids)
|
46
|
+
Job.release_all_concurrency_locks Job.where(id: job_ids)
|
47
|
+
super
|
48
|
+
end
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class ScheduledExecution < Execution
|
5
|
+
include Dispatching
|
6
|
+
|
5
7
|
scope :due, -> { where(scheduled_at: ..Time.current) }
|
6
8
|
scope :ordered, -> { order(scheduled_at: :asc, priority: :asc) }
|
7
9
|
scope :next_batch, ->(batch_size) { due.ordered.limit(batch_size) }
|
8
10
|
|
9
|
-
|
11
|
+
assumes_attributes_from_job :scheduled_at
|
10
12
|
|
11
13
|
class << self
|
12
14
|
def dispatch_next_batch(batch_size)
|
@@ -14,52 +16,10 @@ module SolidQueue
|
|
14
16
|
job_ids = next_batch(batch_size).non_blocking_lock.pluck(:job_id)
|
15
17
|
if job_ids.empty? then []
|
16
18
|
else
|
17
|
-
|
19
|
+
dispatch_jobs(job_ids)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
21
|
-
|
22
|
-
private
|
23
|
-
def dispatch_batch(job_ids)
|
24
|
-
jobs = Job.where(id: job_ids)
|
25
|
-
with_concurrency_limits, without_concurrency_limits = jobs.partition(&:concurrency_limited?)
|
26
|
-
|
27
|
-
dispatch_at_once(without_concurrency_limits)
|
28
|
-
dispatch_one_by_one(with_concurrency_limits)
|
29
|
-
|
30
|
-
successfully_dispatched(job_ids).tap do |dispatched_job_ids|
|
31
|
-
where(job_id: dispatched_job_ids).delete_all
|
32
|
-
SolidQueue.logger.info("[SolidQueue] Dispatched scheduled batch with #{dispatched_job_ids.size} jobs")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def dispatch_at_once(jobs)
|
37
|
-
ReadyExecution.insert_all ready_rows_from_batch(jobs)
|
38
|
-
end
|
39
|
-
|
40
|
-
def dispatch_one_by_one(jobs)
|
41
|
-
jobs.each(&:dispatch)
|
42
|
-
end
|
43
|
-
|
44
|
-
def ready_rows_from_batch(jobs)
|
45
|
-
prepared_at = Time.current
|
46
|
-
|
47
|
-
jobs.map do |job|
|
48
|
-
{ job_id: job.id, queue_name: job.queue_name, priority: job.priority, created_at: prepared_at }
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def successfully_dispatched(job_ids)
|
53
|
-
dispatched_and_ready(job_ids) + dispatched_and_blocked(job_ids)
|
54
|
-
end
|
55
|
-
|
56
|
-
def dispatched_and_ready(job_ids)
|
57
|
-
ReadyExecution.where(job_id: job_ids).pluck(:job_id)
|
58
|
-
end
|
59
|
-
|
60
|
-
def dispatched_and_blocked(job_ids)
|
61
|
-
BlockedExecution.where(job_id: job_ids).pluck(:job_id)
|
62
|
-
end
|
63
23
|
end
|
64
24
|
end
|
65
25
|
end
|
@@ -1,65 +1,94 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module SolidQueue
|
4
|
+
class Semaphore < Record
|
5
|
+
scope :available, -> { where("value > 0") }
|
6
|
+
scope :expired, -> { where(expires_at: ...Time.current) }
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def signal(job)
|
13
|
-
Proxy.new(job, self).signal
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class Proxy
|
18
|
-
def initialize(job, proxied_class)
|
19
|
-
@job = job
|
20
|
-
@proxied_class = proxied_class
|
21
|
-
end
|
8
|
+
class << self
|
9
|
+
def wait(job)
|
10
|
+
Proxy.new(job).wait
|
11
|
+
end
|
22
12
|
|
23
|
-
|
24
|
-
|
25
|
-
semaphore.value > 0 && attempt_decrement
|
26
|
-
else
|
27
|
-
attempt_creation
|
13
|
+
def signal(job)
|
14
|
+
Proxy.new(job).signal
|
28
15
|
end
|
29
|
-
end
|
30
16
|
|
31
|
-
|
32
|
-
|
17
|
+
def signal_all(jobs)
|
18
|
+
Proxy.signal_all(jobs)
|
19
|
+
end
|
33
20
|
end
|
34
21
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def attempt_creation
|
39
|
-
proxied_class.create!(key: key, value: limit - 1, expires_at: expires_at)
|
40
|
-
true
|
41
|
-
rescue ActiveRecord::RecordNotUnique
|
42
|
-
attempt_decrement
|
22
|
+
class Proxy
|
23
|
+
def self.signal_all(jobs)
|
24
|
+
Semaphore.where(key: jobs.map(&:concurrency_key)).update_all("value = value + 1")
|
43
25
|
end
|
44
26
|
|
45
|
-
def
|
46
|
-
|
27
|
+
def initialize(job)
|
28
|
+
@job = job
|
29
|
+
@retries = 0
|
47
30
|
end
|
48
31
|
|
49
|
-
def
|
50
|
-
|
32
|
+
def wait
|
33
|
+
if semaphore = Semaphore.find_by(key: key)
|
34
|
+
semaphore.value > 0 && attempt_decrement
|
35
|
+
else
|
36
|
+
attempt_creation
|
37
|
+
end
|
51
38
|
end
|
52
39
|
|
53
|
-
def
|
54
|
-
|
40
|
+
def signal
|
41
|
+
attempt_increment
|
55
42
|
end
|
56
43
|
|
57
|
-
|
58
|
-
job
|
59
|
-
end
|
44
|
+
private
|
45
|
+
attr_accessor :job, :retries
|
60
46
|
|
61
|
-
|
62
|
-
|
63
|
-
|
47
|
+
def attempt_creation
|
48
|
+
Semaphore.create!(key: key, value: limit - 1, expires_at: expires_at)
|
49
|
+
true
|
50
|
+
rescue ActiveRecord::RecordNotUnique
|
51
|
+
attempt_decrement
|
52
|
+
end
|
53
|
+
|
54
|
+
def attempt_decrement
|
55
|
+
Semaphore.available.where(key: key).update_all([ "value = value - 1, expires_at = ?", expires_at ]) > 0
|
56
|
+
rescue ActiveRecord::Deadlocked
|
57
|
+
if retriable? then attempt_retry
|
58
|
+
else
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def attempt_increment
|
64
|
+
Semaphore.where(key: key, value: ...limit).update_all([ "value = value + 1, expires_at = ?", expires_at ]) > 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def attempt_retry
|
68
|
+
self.retries += 1
|
69
|
+
|
70
|
+
if semaphore = Semaphore.find_by(key: key)
|
71
|
+
semaphore.value > 0 && attempt_decrement
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
MAX_RETRIES = 1
|
76
|
+
|
77
|
+
def retriable?
|
78
|
+
retries < MAX_RETRIES
|
79
|
+
end
|
80
|
+
|
81
|
+
def key
|
82
|
+
job.concurrency_key
|
83
|
+
end
|
84
|
+
|
85
|
+
def expires_at
|
86
|
+
job.concurrency_duration.from_now
|
87
|
+
end
|
88
|
+
|
89
|
+
def limit
|
90
|
+
job.concurrency_limit
|
91
|
+
end
|
92
|
+
end
|
64
93
|
end
|
65
94
|
end
|
@@ -9,15 +9,15 @@ module ActiveJob
|
|
9
9
|
# Rails.application.config.active_job.queue_adapter = :solid_queue
|
10
10
|
class SolidQueueAdapter
|
11
11
|
def enqueue(active_job) # :nodoc:
|
12
|
-
SolidQueue::Job.
|
13
|
-
active_job.provider_job_id = job.id
|
14
|
-
end
|
12
|
+
SolidQueue::Job.enqueue(active_job)
|
15
13
|
end
|
16
14
|
|
17
15
|
def enqueue_at(active_job, timestamp) # :nodoc:
|
18
|
-
SolidQueue::Job.
|
19
|
-
|
20
|
-
|
16
|
+
SolidQueue::Job.enqueue(active_job, scheduled_at: Time.at(timestamp))
|
17
|
+
end
|
18
|
+
|
19
|
+
def enqueue_all(active_jobs) # :nodoc:
|
20
|
+
SolidQueue::Job.enqueue_all(active_jobs)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
DEFAULT_UNIQUENESS_GROUP = ->(*) { self.class.name }
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :uniqueness_key, instance_accessor: false
|
11
|
+
class_attribute :uniqueness_group, default: DEFAULT_UNIQUENESS_GROUP, instance_accessor: false
|
12
|
+
|
13
|
+
class_attribute :uniqueness_duration
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def enqueued_uniquely_by(key:, group: DEFAULT_UNIQUENESS_GROUP, duration: SolidQueue.default_uniqueness_period)
|
18
|
+
self.uniqueness_key = key
|
19
|
+
self.uniqueness_group = group
|
20
|
+
self.uniqueness_duration = duration
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def uniqueness_key
|
25
|
+
if self.class.uniqueness_key
|
26
|
+
param = compute_concurrency_parameter(self.class.concurrency_key)
|
27
|
+
|
28
|
+
case param
|
29
|
+
when ActiveRecord::Base
|
30
|
+
[ concurrency_group, param.class.name, param.id ]
|
31
|
+
else
|
32
|
+
[ concurrency_group, param ]
|
33
|
+
end.compact.join("/")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def enqueued_uniquely?
|
38
|
+
uniqueness_key.present?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -4,6 +4,6 @@ Description:
|
|
4
4
|
Example:
|
5
5
|
bin/rails generate solid_queue:install
|
6
6
|
|
7
|
-
This will
|
7
|
+
This will perform the following:
|
8
8
|
Installs solid_queue migrations
|
9
|
-
Replaces Active Job's adapter in
|
9
|
+
Replaces Active Job's adapter in environment configuration
|
@@ -6,10 +6,8 @@ class SolidQueue::InstallGenerator < Rails::Generators::Base
|
|
6
6
|
class_option :skip_migrations, type: :boolean, default: nil, desc: "Skip migrations"
|
7
7
|
|
8
8
|
def add_solid_queue
|
9
|
-
|
10
|
-
|
11
|
-
gsub_file env_config, /(# )?config\.active_job\.queue_adapter\s+=.*/, "config.active_job.queue_adapter = :solid_queue"
|
12
|
-
end
|
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"
|
13
11
|
end
|
14
12
|
|
15
13
|
copy_file "config.yml", "config/solid_queue.yml"
|
@@ -6,16 +6,19 @@ Puma::Plugin.create do
|
|
6
6
|
def start(launcher)
|
7
7
|
@log_writer = launcher.log_writer
|
8
8
|
@puma_pid = $$
|
9
|
-
@solid_queue_pid = fork do
|
10
|
-
Thread.new { monitor_puma }
|
11
|
-
SolidQueue::Supervisor.start(mode: :all)
|
12
|
-
end
|
13
9
|
|
14
|
-
launcher.events.
|
10
|
+
launcher.events.on_booted do
|
11
|
+
@solid_queue_pid = fork do
|
12
|
+
Thread.new { monitor_puma }
|
13
|
+
SolidQueue::Supervisor.start(mode: :all)
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
in_background do
|
17
|
+
monitor_solid_queue
|
18
|
+
end
|
18
19
|
end
|
20
|
+
|
21
|
+
launcher.events.on_stopped { stop_solid_queue }
|
19
22
|
end
|
20
23
|
|
21
24
|
private
|
@@ -4,6 +4,8 @@ module SolidQueue::Processes
|
|
4
4
|
module Runnable
|
5
5
|
include Supervised
|
6
6
|
|
7
|
+
attr_writer :mode
|
8
|
+
|
7
9
|
def start
|
8
10
|
@stopping = false
|
9
11
|
|
@@ -19,8 +21,6 @@ module SolidQueue::Processes
|
|
19
21
|
end
|
20
22
|
|
21
23
|
private
|
22
|
-
attr_writer :mode
|
23
|
-
|
24
24
|
DEFAULT_MODE = :async
|
25
25
|
|
26
26
|
def mode
|
@@ -44,7 +44,9 @@ module SolidQueue::Processes
|
|
44
44
|
loop do
|
45
45
|
break if shutting_down?
|
46
46
|
|
47
|
-
|
47
|
+
wrap_in_app_executor do
|
48
|
+
run
|
49
|
+
end
|
48
50
|
end
|
49
51
|
ensure
|
50
52
|
run_callbacks(:shutdown) { shutdown }
|
data/lib/solid_queue/version.rb
CHANGED
data/lib/solid_queue/worker.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rosa Gutierrez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 7.
|
19
|
+
version: '7.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 7.
|
26
|
+
version: '7.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: debug
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: puma
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: Database-backed Active Job backend.
|
56
70
|
email:
|
57
71
|
- rosa@37signals.com
|
@@ -65,12 +79,14 @@ files:
|
|
65
79
|
- app/models/solid_queue/blocked_execution.rb
|
66
80
|
- app/models/solid_queue/claimed_execution.rb
|
67
81
|
- app/models/solid_queue/execution.rb
|
82
|
+
- app/models/solid_queue/execution/dispatching.rb
|
68
83
|
- app/models/solid_queue/execution/job_attributes.rb
|
69
84
|
- app/models/solid_queue/failed_execution.rb
|
70
85
|
- app/models/solid_queue/job.rb
|
71
86
|
- app/models/solid_queue/job/clearable.rb
|
72
87
|
- app/models/solid_queue/job/concurrency_controls.rb
|
73
88
|
- app/models/solid_queue/job/executable.rb
|
89
|
+
- app/models/solid_queue/job/schedulable.rb
|
74
90
|
- app/models/solid_queue/pause.rb
|
75
91
|
- app/models/solid_queue/process.rb
|
76
92
|
- app/models/solid_queue/process/prunable.rb
|
@@ -82,8 +98,10 @@ files:
|
|
82
98
|
- app/models/solid_queue/semaphore.rb
|
83
99
|
- config/routes.rb
|
84
100
|
- db/migrate/20231211200639_create_solid_queue_tables.rb
|
101
|
+
- db/migrate/20240110143450_add_missing_index_to_blocked_executions.rb
|
85
102
|
- lib/active_job/concurrency_controls.rb
|
86
103
|
- lib/active_job/queue_adapters/solid_queue_adapter.rb
|
104
|
+
- lib/active_job/uniqueness.rb
|
87
105
|
- lib/generators/solid_queue/install/USAGE
|
88
106
|
- lib/generators/solid_queue/install/install_generator.rb
|
89
107
|
- lib/generators/solid_queue/install/templates/config.yml
|
@@ -92,6 +110,7 @@ files:
|
|
92
110
|
- lib/solid_queue/app_executor.rb
|
93
111
|
- lib/solid_queue/configuration.rb
|
94
112
|
- lib/solid_queue/dispatcher.rb
|
113
|
+
- lib/solid_queue/dispatcher/scheduled_executions_dispatcher.rb
|
95
114
|
- lib/solid_queue/engine.rb
|
96
115
|
- lib/solid_queue/pool.rb
|
97
116
|
- lib/solid_queue/processes/base.rb
|
@@ -128,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
147
|
- !ruby/object:Gem::Version
|
129
148
|
version: '0'
|
130
149
|
requirements: []
|
131
|
-
rubygems_version: 3.4.
|
150
|
+
rubygems_version: 3.4.10
|
132
151
|
signing_key:
|
133
152
|
specification_version: 4
|
134
153
|
summary: Database-backed Active Job backend.
|