solid_queue 1.2.1 → 1.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9066bd5266e43075385bfd3365de2512400960f5cfa7f780dba69e4a3259c07a
4
- data.tar.gz: 0a7103f485e445563814874e3113b6ac6dca84c8333bad418c449ebaf3fac1c9
3
+ metadata.gz: ee016dde69aa11302fc20335e657d8f65f31049ad1fad9bd56c363c8e8a6ee4d
4
+ data.tar.gz: fd54a73d694f7dba45bd8a86daf426cceb4f351c87492d75130517b23929d884
5
5
  SHA512:
6
- metadata.gz: 952b71b5cd59ebd79eb51c44f7ed509bf3d4959c010dc0441cff37c0a1bd2ccea97054007bd3a197b287a158342e8791318c841f0d1d9b3dd347986da68bb53a
7
- data.tar.gz: 1309ce242499f430667d9677b7ff8807e1b43af33873d9b00575c4c9e13be24824d9520ca24db432901da2616c4ba5a530db83fd29c09c47b820fff3586a75b7
6
+ metadata.gz: 4a30c8abce9f22bd28901387bbb7c0a1da41671926033f48c2f46339f3b447ae5677763f52f0b1b46801a9f3bf206834d1eae712462989bbe6a6d8daa2fd43e5
7
+ data.tar.gz: 286fafe7fc7202053989993bbfc51a207b530a538f2898685482c328cfe6bf28fc3b514d4ea15c2c5fcb57cadbf010c43823fba12dd92dfc54b0b105a45f340c
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Solid Queue
2
2
 
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.
3
+ Solid Queue is a database-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, recurring jobs, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`).
5
+ In addition to regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, recurring jobs, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`).
6
6
 
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's multi-threading.
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, and delays, and it's compatible with Ruby on Rails's multi-threading.
8
8
 
9
- ## Table of contents
9
+ ## Table of Contents
10
10
 
11
11
  - [Installation](#installation)
12
12
  - [Usage in development and other non-production environments](#usage-in-development-and-other-non-production-environments)
@@ -15,10 +15,10 @@ Solid Queue can be used with SQL databases such as MySQL, PostgreSQL or SQLite,
15
15
  - [Incremental adoption](#incremental-adoption)
16
16
  - [High performance requirements](#high-performance-requirements)
17
17
  - [Configuration](#configuration)
18
- - [Workers, dispatchers and scheduler](#workers-dispatchers-and-scheduler)
18
+ - [Workers, dispatchers, and scheduler](#workers-dispatchers-and-scheduler)
19
19
  - [Queue order and priorities](#queue-order-and-priorities)
20
20
  - [Queues specification and performance](#queues-specification-and-performance)
21
- - [Threads, processes and signals](#threads-processes-and-signals)
21
+ - [Threads, processes, and signals](#threads-processes-and-signals)
22
22
  - [Database configuration](#database-configuration)
23
23
  - [Other configuration settings](#other-configuration-settings)
24
24
  - [Lifecycle hooks](#lifecycle-hooks)
@@ -36,7 +36,7 @@ Solid Queue can be used with SQL databases such as MySQL, PostgreSQL or SQLite,
36
36
 
37
37
  ## Installation
38
38
 
39
- Solid Queue is configured by default in new Rails 8 applications. But if you're running an earlier version, you can add it manually following these steps:
39
+ Solid Queue is configured by default in new Rails 8 applications. If you're running an earlier version, you can add it manually following these steps:
40
40
 
41
41
  1. `bundle add solid_queue`
42
42
  2. `bin/rails solid_queue:install`
@@ -45,7 +45,7 @@ Solid Queue is configured by default in new Rails 8 applications. But if you're
45
45
 
46
46
  This will configure Solid Queue as the production Active Job backend, create the configuration files `config/queue.yml` and `config/recurring.yml`, and create the `db/queue_schema.rb`. It'll also create a `bin/jobs` executable wrapper that you can use to start Solid Queue.
47
47
 
48
- Once you've done that, you will then have to add the configuration for the queue database in `config/database.yml`. If you're using SQLite, it'll look like this:
48
+ Once you've done that, you will have to add the configuration for the queue database in `config/database.yml`. If you're using SQLite, it'll look like this:
49
49
 
50
50
  ```yaml
51
51
  production:
@@ -79,7 +79,7 @@ Now you're ready to start processing jobs by running `bin/jobs` on the server th
79
79
 
80
80
  For small projects, you can run Solid Queue on the same machine as your webserver. When you're ready to scale, Solid Queue supports horizontal scaling out-of-the-box. You can run Solid Queue on a separate server from your webserver, or even run `bin/jobs` on multiple machines at the same time. Depending on the configuration, you can designate some machines to run only dispatchers or only workers. See the [configuration](#configuration) section for more details on this.
81
81
 
82
- **Note**: future changes to the schema will come in the form of regular migrations.
82
+ **Note**: Future changes to the schema will come in the form of regular migrations.
83
83
 
84
84
  ### Usage in development and other non-production environments
85
85
 
@@ -150,7 +150,7 @@ development:
150
150
 
151
151
  ### Single database configuration
152
152
 
153
- Running Solid Queue in a separate database is recommended, but it's also possible to use one single database for both the app and the queue. Just follow these steps:
153
+ Running Solid Queue in a separate database is recommended, but it's also possible to use one single database for both the app and the queue. Follow these steps:
154
154
 
155
155
  1. Copy the contents of `db/queue_schema.rb` into a normal migration and delete `db/queue_schema.rb`
156
156
  2. Remove `config.solid_queue.connects_to` from `production.rb`
@@ -158,7 +158,7 @@ Running Solid Queue in a separate database is recommended, but it's also possibl
158
158
 
159
159
  You won't have multiple databases, so `database.yml` doesn't need to have primary and queue database.
160
160
 
161
- ### Dashboard ui setup
161
+ ### Dashboard UI Setup
162
162
 
163
163
  For viewing information about your jobs via a UI, 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.
164
164
 
@@ -181,7 +181,7 @@ Solid Queue was designed for the highest throughput when used with MySQL 8+ or P
181
181
 
182
182
  ## Configuration
183
183
 
184
- ### Workers, dispatchers and scheduler
184
+ ### Workers, dispatchers, and scheduler
185
185
 
186
186
  We have several types of actors in Solid Queue:
187
187
 
@@ -330,7 +330,7 @@ queues: back*
330
330
  ```
331
331
 
332
332
 
333
- ### Threads, processes and signals
333
+ ### Threads, processes, and signals
334
334
 
335
335
  Workers in Solid Queue use a thread pool to run work in multiple threads, configurable via the `threads` parameter above. Besides this, parallelism can be achieved via multiple processes on one machine (configurable via different workers or the `processes` parameter above) or by horizontal scaling.
336
336
 
@@ -366,7 +366,7 @@ There are several settings that control how Solid Queue works that you can set a
366
366
 
367
367
  **This is not used for errors raised within a job execution**. Errors happening in jobs are handled by Active Job's `retry_on` or `discard_on`, and ultimately will result in [failed jobs](#failed-jobs-and-retries). This is for errors happening within Solid Queue itself.
368
368
 
369
- - `use_skip_locked`: whether to use `FOR UPDATE SKIP LOCKED` when performing locking reads. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential.
369
+ - `use_skip_locked`: whether to use `FOR UPDATE SKIP LOCKED` when performing locking reads. This will be automatically detected in the future, and for now, you only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential.
370
370
  - `process_heartbeat_interval`: the heartbeat interval that all processes will follow—defaults to 60 seconds.
371
371
  - `process_alive_threshold`: how long to wait until a process is considered dead after its last heartbeat—defaults to 5 minutes.
372
372
  - `shutdown_timeout`: time the supervisor will wait since it sent the `TERM` signal to its supervised processes before sending a `QUIT` version to them requesting immediate termination—defaults to 5 seconds.
@@ -616,7 +616,7 @@ end
616
616
 
617
617
  Using this option, you can also use Solid Queue in the same database as your app but not rely on transactional integrity.
618
618
 
619
- If you don't set this option but still want to make sure you're not inadvertently on transactional integrity, you can make sure that:
619
+ If you don't set this option but still want to make sure you're not inadvertently relying on transactional integrity, you can make sure that:
620
620
  - Your jobs relying on specific data 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.
621
621
  - Or, you configure a different 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:
622
622
 
@@ -37,8 +37,10 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
37
37
  end
38
38
 
39
39
  def fail_all_with(error)
40
- SolidQueue.instrument(:fail_many_claimed) do |payload|
41
- includes(:job).tap do |executions|
40
+ includes(:job).tap do |executions|
41
+ return if executions.empty?
42
+
43
+ SolidQueue.instrument(:fail_many_claimed) do |payload|
42
44
  executions.each do |execution|
43
45
  execution.failed_with(error)
44
46
  execution.unblock_next_job
@@ -58,8 +58,11 @@ module SolidQueue
58
58
  end
59
59
 
60
60
  def determine_backtrace_size_limit
61
- column = self.class.connection.schema_cache.columns_hash(self.class.table_name)["error"]
62
- if column.limit.present?
61
+ column = self.class.connection_pool.with_connection do |connection|
62
+ connection.schema_cache.columns_hash(self.class.table_name)["error"]
63
+ end
64
+
65
+ if column && column.limit.present?
63
66
  column.limit - exception_class_name.bytesize - exception_message.bytesize - JSON_OVERHEAD
64
67
  end
65
68
  end
@@ -10,6 +10,7 @@ module SolidQueue
10
10
 
11
11
  class << self
12
12
  def enqueue_all(active_jobs)
13
+ active_jobs.each { |job| job.scheduled_at ||= Time.current }
13
14
  active_jobs_by_job_id = active_jobs.index_by(&:job_id)
14
15
 
15
16
  transaction do
@@ -6,11 +6,19 @@ module SolidQueue
6
6
 
7
7
  connects_to(**SolidQueue.connects_to) if SolidQueue.connects_to
8
8
 
9
- def self.non_blocking_lock
10
- if SolidQueue.use_skip_locked
11
- lock(Arel.sql("FOR UPDATE SKIP LOCKED"))
12
- else
13
- lock
9
+ class << self
10
+ def non_blocking_lock
11
+ if SolidQueue.use_skip_locked
12
+ lock(Arel.sql("FOR UPDATE SKIP LOCKED"))
13
+ else
14
+ lock
15
+ end
16
+ end
17
+
18
+ def supports_insert_conflict_target?
19
+ connection_pool.with_connection do |connection|
20
+ connection.supports_insert_conflict_target?
21
+ end
14
22
  end
15
23
  end
16
24
  end
@@ -8,7 +8,7 @@ module SolidQueue
8
8
 
9
9
  class << self
10
10
  def create_or_insert!(**attributes)
11
- if connection.supports_insert_conflict_target?
11
+ if supports_insert_conflict_target?
12
12
  # PostgreSQL fails and aborts the current transaction when it hits a duplicate key conflict
13
13
  # during two concurrent INSERTs for the same value of an unique index. We need to explicitly
14
14
  # indicate unique_by to ignore duplicate rows by this value when inserting
@@ -36,7 +36,7 @@ module SolidQueue
36
36
  end
37
37
 
38
38
  def create_or_update_all(tasks)
39
- if connection.supports_insert_conflict_target?
39
+ if supports_insert_conflict_target?
40
40
  # PostgreSQL fails and aborts the current transaction when it hits a duplicate key conflict
41
41
  # during two concurrent INSERTs for the same value of an unique index. We need to explicitly
42
42
  # indicate unique_by to ignore duplicate rows by this value when inserting
@@ -48,7 +48,7 @@ module SolidQueue
48
48
  end
49
49
 
50
50
  def delay_from_now
51
- [ (next_time - Time.current).to_f, 0 ].max
51
+ [ (next_time - Time.current).to_f, 0.1 ].max
52
52
  end
53
53
 
54
54
  def next_time
@@ -20,7 +20,7 @@ module SolidQueue
20
20
 
21
21
  # Requires a unique index on key
22
22
  def create_unique_by(attributes)
23
- if connection.supports_insert_conflict_target?
23
+ if supports_insert_conflict_target?
24
24
  insert({ **attributes }, unique_by: :key).any?
25
25
  else
26
26
  create!(**attributes)
@@ -11,15 +11,27 @@ Puma::Plugin.create do
11
11
  monitor_solid_queue
12
12
  end
13
13
 
14
- launcher.events.on_booted do
15
- @solid_queue_pid = fork do
16
- Thread.new { monitor_puma }
17
- SolidQueue::Supervisor.start
14
+ if Gem::Version.new(Puma::Const::VERSION) < Gem::Version.new("7")
15
+ launcher.events.on_booted do
16
+ @solid_queue_pid = fork do
17
+ Thread.new { monitor_puma }
18
+ SolidQueue::Supervisor.start
19
+ end
20
+ end
21
+
22
+ launcher.events.on_stopped { stop_solid_queue }
23
+ launcher.events.on_restart { stop_solid_queue }
24
+ else
25
+ launcher.events.after_booted do
26
+ @solid_queue_pid = fork do
27
+ Thread.new { monitor_puma }
28
+ SolidQueue::Supervisor.start
29
+ end
18
30
  end
19
- end
20
31
 
21
- launcher.events.on_stopped { stop_solid_queue }
22
- launcher.events.on_restart { stop_solid_queue }
32
+ launcher.events.after_stopped { stop_solid_queue }
33
+ launcher.events.before_restart { stop_solid_queue }
34
+ end
23
35
  end
24
36
 
25
37
  private
@@ -12,7 +12,7 @@ module SolidQueue
12
12
  desc: "Path to recurring schedule definition (default: #{Configuration::DEFAULT_RECURRING_SCHEDULE_FILE_PATH}).",
13
13
  banner: "SOLID_QUEUE_RECURRING_SCHEDULE"
14
14
 
15
- class_option :skip_recurring, type: :boolean, default: false,
15
+ class_option :skip_recurring, type: :boolean,
16
16
  desc: "Whether to skip recurring tasks scheduling",
17
17
  banner: "SOLID_QUEUE_SKIP_RECURRING"
18
18
 
@@ -29,18 +29,22 @@ module SolidQueue
29
29
  end
30
30
 
31
31
  def start
32
- boot
33
- run_start_hooks
32
+ wrap_in_app_executor do
33
+ boot
34
+ run_start_hooks
34
35
 
35
- start_processes
36
- launch_maintenance_task
36
+ start_processes
37
+ launch_maintenance_task
37
38
 
38
- supervise
39
+ supervise
40
+ end
39
41
  end
40
42
 
41
43
  def stop
42
- super
43
- run_stop_hooks
44
+ wrap_in_app_executor do
45
+ super
46
+ run_stop_hooks
47
+ end
44
48
  end
45
49
 
46
50
  private
@@ -1,3 +1,3 @@
1
1
  module SolidQueue
2
- VERSION = "1.2.1"
2
+ VERSION = "1.2.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rosa Gutierrez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-23 00:00:00.000000000 Z
11
+ date: 2025-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 1.11.0
75
+ version: '1.11'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 1.11.0
82
+ version: '1.11'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: thor
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -140,16 +140,16 @@ dependencies:
140
140
  name: puma
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ">="
143
+ - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '0'
145
+ version: '7.0'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - ">="
150
+ - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '0'
152
+ version: '7.0'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: mysql2
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -257,7 +257,6 @@ extra_rdoc_files: []
257
257
  files:
258
258
  - MIT-LICENSE
259
259
  - README.md
260
- - Rakefile
261
260
  - UPGRADING.md
262
261
  - app/jobs/solid_queue/recurring_job.rb
263
262
  - app/models/solid_queue/blocked_execution.rb
data/Rakefile DELETED
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/setup"
4
-
5
- APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
- load "rails/tasks/engine.rake"
7
-
8
- load "rails/tasks/statistics.rake"
9
-
10
- require "bundler/gem_tasks"
11
- require "rake/tasklib"
12
-
13
- class TestHelpers < Rake::TaskLib
14
- def initialize(databases)
15
- @databases = databases
16
- define
17
- end
18
-
19
- def define
20
- desc "Run tests for all databases (mysql, postgres, sqlite)"
21
- task :test do
22
- @databases.each { |database| run_test_for_database(database) }
23
- end
24
-
25
- namespace :test do
26
- @databases.each do |database|
27
- desc "Run tests for #{database} database"
28
- task database do
29
- run_test_for_database(database)
30
- end
31
- end
32
- end
33
- end
34
-
35
- private
36
-
37
- def run_test_for_database(database)
38
- sh("TARGET_DB=#{database} bin/setup")
39
- sh("TARGET_DB=#{database} bin/rails test")
40
- end
41
- end
42
-
43
- TestHelpers.new(%w[ mysql postgres sqlite ])