solid_queue 0.1.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|