Skip to content

Commit eedf3ec

Browse files
Merge branch 'main' into feature/clear-ar-connections-flag
2 parents 2e1fc3f + b61bbdf commit eedf3ec

31 files changed

+244
-135
lines changed

.github/workflows/main.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
tests:
2121
name: Tests
2222
runs-on: ubuntu-latest
23+
timeout-minutes: 15
2324
strategy:
2425
fail-fast: false
2526
matrix:
@@ -29,10 +30,12 @@ jobs:
2930
- 3.3
3031
- 3.4
3132
database: [ mysql, postgres, sqlite ]
32-
gemfile: [ rails_7_1, rails_7_2, rails_8_0, rails_main ]
33+
gemfile: [ rails_7_1, rails_7_2, rails_8_0, rails_8_1, rails_main ]
3334
exclude:
3435
- ruby-version: "3.1"
3536
gemfile: rails_8_0
37+
- ruby-version: "3.1"
38+
gemfile: rails_8_1
3639
- ruby-version: "3.1"
3740
gemfile: rails_main
3841
services:
@@ -41,7 +44,7 @@ jobs:
4144
env:
4245
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
4346
ports:
44-
- 33060:3306
47+
- 33066:3306
4548
options: --health-cmd "mysql -h localhost -e \"select now()\"" --health-interval 1s --health-timeout 5s --health-retries 30
4649
postgres:
4750
image: postgres:15.1
@@ -52,6 +55,7 @@ jobs:
5255
env:
5356
TARGET_DB: ${{ matrix.database }}
5457
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
58+
RAILS_ENV: test
5559
steps:
5660
- name: Checkout code
5761
uses: actions/checkout@v4
@@ -68,3 +72,12 @@ jobs:
6872
bin/rails db:setup
6973
- name: Run tests
7074
run: bin/rails test
75+
- name: Upload logs on failure
76+
if: ${{ failure() }}
77+
uses: actions/upload-artifact@v4
78+
with:
79+
name: logs-${{ matrix.database }}-${{ matrix.gemfile }}-ruby${{ matrix.ruby-version }}-attempt${{ github.run_attempt }}
80+
path: |
81+
test/dummy/log/test.log
82+
if-no-files-found: ignore
83+
retention-days: 30

Appraisals

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ appraise "rails-8-0" do
1515
gem "railties", "~> 8.0.0"
1616
end
1717

18+
appraise "rails-8-1" do
19+
gem "railties", "~> 8.1.0"
20+
end
21+
1822
appraise "rails-main" do
1923
gem "railties", github: "rails/rails", branch: "main"
2024
end

Gemfile.lock

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
11
PATH
22
remote: .
33
specs:
4-
solid_queue (1.2.1)
4+
solid_queue (1.2.4)
55
activejob (>= 7.1)
66
activerecord (>= 7.1)
77
concurrent-ruby (>= 1.3.1)
8-
fugit (~> 1.11.0)
8+
fugit (~> 1.11)
99
railties (>= 7.1)
1010
thor (>= 1.3.1)
1111

1212
GEM
1313
remote: https://rubygems.org/
1414
specs:
15-
actionpack (7.1.5.1)
16-
actionview (= 7.1.5.1)
17-
activesupport (= 7.1.5.1)
15+
actionpack (7.1.5.2)
16+
actionview (= 7.1.5.2)
17+
activesupport (= 7.1.5.2)
1818
nokogiri (>= 1.8.5)
1919
racc
2020
rack (>= 2.2.4)
2121
rack-session (>= 1.0.1)
2222
rack-test (>= 0.6.3)
2323
rails-dom-testing (~> 2.2)
2424
rails-html-sanitizer (~> 1.6)
25-
actionview (7.1.5.1)
26-
activesupport (= 7.1.5.1)
25+
actionview (7.1.5.2)
26+
activesupport (= 7.1.5.2)
2727
builder (~> 3.1)
2828
erubi (~> 1.11)
2929
rails-dom-testing (~> 2.2)
3030
rails-html-sanitizer (~> 1.6)
31-
activejob (7.1.5.1)
32-
activesupport (= 7.1.5.1)
31+
activejob (7.1.5.2)
32+
activesupport (= 7.1.5.2)
3333
globalid (>= 0.3.6)
34-
activemodel (7.1.5.1)
35-
activesupport (= 7.1.5.1)
36-
activerecord (7.1.5.1)
37-
activemodel (= 7.1.5.1)
38-
activesupport (= 7.1.5.1)
34+
activemodel (7.1.5.2)
35+
activesupport (= 7.1.5.2)
36+
activerecord (7.1.5.2)
37+
activemodel (= 7.1.5.2)
38+
activesupport (= 7.1.5.2)
3939
timeout (>= 0.4.0)
40-
activesupport (7.1.5.1)
40+
activesupport (7.1.5.2)
4141
base64
4242
benchmark (>= 0.3)
4343
bigdecimal
@@ -55,18 +55,18 @@ GEM
5555
rake
5656
thor (>= 0.14.0)
5757
ast (2.4.2)
58-
base64 (0.2.0)
59-
benchmark (0.4.0)
60-
bigdecimal (3.1.9)
58+
base64 (0.3.0)
59+
benchmark (0.4.1)
60+
bigdecimal (3.3.1)
6161
builder (3.3.0)
62-
concurrent-ruby (1.3.4)
63-
connection_pool (2.4.1)
62+
concurrent-ruby (1.3.5)
63+
connection_pool (2.5.4)
6464
crass (1.0.6)
6565
date (3.4.1)
6666
debug (1.9.2)
6767
irb (~> 1.10)
6868
reline (>= 0.3.8)
69-
drb (2.2.1)
69+
drb (2.2.3)
7070
erubi (1.13.1)
7171
et-orbi (1.2.11)
7272
tzinfo
@@ -75,7 +75,7 @@ GEM
7575
raabro (~> 1.4)
7676
globalid (1.2.1)
7777
activesupport (>= 6.1)
78-
i18n (1.14.6)
78+
i18n (1.14.7)
7979
concurrent-ruby (~> 1.0)
8080
io-console (0.8.0)
8181
irb (1.14.3)
@@ -87,19 +87,19 @@ GEM
8787
loofah (2.23.1)
8888
crass (~> 1.0.2)
8989
nokogiri (>= 1.12.0)
90-
minitest (5.25.4)
90+
minitest (5.26.0)
9191
mocha (2.1.0)
9292
ruby2_keywords (>= 0.0.5)
9393
mutex_m (0.3.0)
9494
mysql2 (0.5.6)
9595
nio4r (2.7.4)
96-
nokogiri (1.18.0-aarch64-linux-gnu)
96+
nokogiri (1.18.9-aarch64-linux-gnu)
9797
racc (~> 1.4)
98-
nokogiri (1.18.0-arm64-darwin)
98+
nokogiri (1.18.9-arm64-darwin)
9999
racc (~> 1.4)
100-
nokogiri (1.18.0-x86_64-darwin)
100+
nokogiri (1.18.9-x86_64-darwin)
101101
racc (~> 1.4)
102-
nokogiri (1.18.0-x86_64-linux-gnu)
102+
nokogiri (1.18.9-x86_64-linux-gnu)
103103
racc (~> 1.4)
104104
parallel (1.26.3)
105105
parser (3.3.6.0)
@@ -109,12 +109,13 @@ GEM
109109
psych (5.2.2)
110110
date
111111
stringio
112-
puma (6.4.3)
112+
puma (7.0.2)
113113
nio4r (~> 2.0)
114114
raabro (1.4.0)
115115
racc (1.8.1)
116-
rack (3.1.8)
117-
rack-session (2.0.0)
116+
rack (3.2.3)
117+
rack-session (2.1.1)
118+
base64 (>= 0.1.0)
118119
rack (>= 3.0.0)
119120
rack-test (2.2.0)
120121
rack (>= 1.3)
@@ -127,9 +128,9 @@ GEM
127128
rails-html-sanitizer (1.6.2)
128129
loofah (~> 2.21)
129130
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
130-
railties (7.1.5.1)
131-
actionpack (= 7.1.5.1)
132-
activesupport (= 7.1.5.1)
131+
railties (7.1.5.2)
132+
actionpack (= 7.1.5.2)
133+
activesupport (= 7.1.5.2)
133134
irb
134135
rackup (>= 1.0.0)
135136
rake (>= 12.2)
@@ -203,7 +204,7 @@ DEPENDENCIES
203204
mocha
204205
mysql2
205206
pg
206-
puma
207+
puma (~> 7.0)
207208
rdoc
208209
rubocop-rails-omakase
209210
solid_queue!

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ When receiving a `QUIT` signal, if workers still have jobs in-flight, these will
342342

343343
If processes have no chance of cleaning up before exiting (e.g. if someone pulls a cable somewhere), in-flight jobs might remain claimed by the processes executing them. Processes send heartbeats, and the supervisor checks and prunes processes with expired heartbeats. Jobs that were claimed by processes with an expired heartbeat will be marked as failed with a `SolidQueue::Processes::ProcessPrunedError`. You can configure both the frequency of heartbeats and the threshold to consider a process dead. See the section below for this.
344344

345-
In a similar way, if a worker is terminated in any other way not initiated by the above signals (e.g. a worker is sent a `KILL` signal), jobs in progress will be marked as failed so that they can be inspected, with a `SolidQueue::Processes::Process::ProcessExitError`. Sometimes a job in particular is responsible for this, for example, if it has a memory leak and you have a mechanism to kill processes over a certain memory threshold, so this will help identifying this kind of situation.
345+
In a similar way, if a worker is terminated in any other way not initiated by the above signals (e.g. a worker is sent a `KILL` signal), jobs in progress will be marked as failed so that they can be inspected, with a `SolidQueue::Processes::ProcessExitError`. Sometimes a job in particular is responsible for this, for example, if it has a memory leak and you have a mechanism to kill processes over a certain memory threshold, so this will help identifying this kind of situation.
346346

347347

348348
### Database configuration
@@ -519,11 +519,11 @@ DeliverAnnouncementToContactJob.set(wait: 30.minutes).perform_later(contact)
519519

520520
The 3 jobs will go into the scheduled queue and will wait there until they're due. Then, 10 minutes after, the first two jobs will be enqueued and the second one most likely will be blocked because the first one will be running first. Then, assuming the jobs are fast and finish in a few seconds, when the third job is due, it'll be enqueued normally.
521521

522-
Normally scheduled jobs are enqueued in batches, but with concurrency controls, jobs need to be enqueued one by one. This has an impact on performance, similarly to the impact of concurrency controls in bulk enqueuing. Read below for more details. I'd generally advise against mixing concurrency controls with waiting/scheduling in the future.
522+
Normally scheduled jobs are enqueued in batches, but with concurrency controls, jobs need to be enqueued one by one. This has an impact on performance, similarly to the impact of concurrency controls in bulk enqueuing. Read below for more details. We generally advise against mixing concurrency controls with waiting/scheduling in the future.
523523

524524
### Performance considerations
525525

526-
Concurrency controls introduce significant overhead (blocked executions need to be created and promoted to ready, semaphores need to be created and updated) so you should consider carefully whether you need them. For throttling purposes, where you plan to have `limit` significantly larger than 1, I'd encourage relying on a limited number of workers per queue instead. For example:
526+
Concurrency controls introduce significant overhead (blocked executions need to be created and promoted to ready, semaphores need to be created and updated) so you should consider carefully whether you need them. For throttling purposes, where you plan to have `limit` significantly larger than 1, we encourage relying on a limited number of workers per queue instead. For example:
527527

528528
```ruby
529529
class ThrottledJob < ApplicationJob

Rakefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ require "bundler/setup"
55
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
66
load "rails/tasks/engine.rake"
77

8-
load "rails/tasks/statistics.rake"
8+
if Rails::VERSION::MAJOR < 8
9+
load "rails/tasks/statistics.rake"
10+
end
911

1012
require "bundler/gem_tasks"
1113
require "rake/tasklib"

app/models/solid_queue/claimed_execution.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ def release_all
3737
end
3838

3939
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|
4244
executions.each do |execution|
4345
execution.failed_with(error)
4446
execution.unblock_next_job

app/models/solid_queue/failed_execution.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,11 @@ def exception_backtrace
5858
end
5959

6060
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?
6366
column.limit - exception_class_name.bytesize - exception_message.bytesize - JSON_OVERHEAD
6467
end
6568
end

app/models/solid_queue/job.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class EnqueueError < StandardError; end
1010

1111
class << self
1212
def enqueue_all(active_jobs)
13+
active_jobs.each { |job| job.scheduled_at ||= Time.current }
1314
active_jobs_by_job_id = active_jobs.index_by(&:job_id)
1415

1516
transaction do

app/models/solid_queue/record.rb

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ class Record < ActiveRecord::Base
66

77
connects_to(**SolidQueue.connects_to) if SolidQueue.connects_to
88

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
1422
end
1523
end
1624
end

app/models/solid_queue/recurring_execution.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class AlreadyRecorded < StandardError; end
88

99
class << self
1010
def create_or_insert!(**attributes)
11-
if connection.supports_insert_conflict_target?
11+
if supports_insert_conflict_target?
1212
# PostgreSQL fails and aborts the current transaction when it hits a duplicate key conflict
1313
# during two concurrent INSERTs for the same value of an unique index. We need to explicitly
1414
# indicate unique_by to ignore duplicate rows by this value when inserting

0 commit comments

Comments
 (0)