sidekiq-fairplay 0.0.1 → 0.0.3
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/README.md +7 -4
- data/lib/sidekiq/fairplay/version.rb +1 -1
- metadata +6 -20
- data/.github/workflows/ci.yml +0 -76
- data/.gitignore +0 -37
- data/.standard.yml +0 -9
- data/Gemfile +0 -3
- data/Rakefile +0 -7
- data/bin/console +0 -7
- data/bin/setup +0 -6
- data/gemfiles/sidekiq_7.gemfile +0 -5
- data/gemfiles/sidekiq_8.gemfile +0 -5
- data/sidekiq-fairplay.gemspec +0 -37
- data/spec/sidekiq/fairplay_spec.rb +0 -301
- data/spec/spec_helper.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73751d48396c6e9c645bcea222fa2d2ff431cd3b975030be8eeefdcfe19f2e98
|
4
|
+
data.tar.gz: f7b0d50c472dd079d294c93537451b95d47a7a654298be25a064a9e7d013e708
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2dcf5b7184846e368ceae0bd76b0805dc469458411018e3c85b8be1eaa619e4921e7f16bf004986b01451886cf43ff0309cda2b3327205d0b9b79bc082b356a
|
7
|
+
data.tar.gz: da24fd2f95cf95d8b7997cf627a82d5a9c456373894ec58eb91c633af0aadf337d54187512eabcbac527c658df8f2cdd25df5b329cc7f2d1eba45edc59ca3683
|
data/README.md
CHANGED
@@ -62,17 +62,20 @@ Configure the client middleware on both client and server:
|
|
62
62
|
```ruby
|
63
63
|
Sidekiq.configure_client do |config|
|
64
64
|
config.client_middleware do |chain|
|
65
|
-
chain.
|
65
|
+
chain.prepend Sidekiq::Fairplay::Middleware
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
69
|
Sidekiq.configure_server do |config|
|
70
70
|
config.client_middleware do |chain|
|
71
|
-
chain.
|
71
|
+
chain.prepend Sidekiq::Fairplay::Middleware
|
72
72
|
end
|
73
73
|
end
|
74
74
|
```
|
75
75
|
|
76
|
+
> [!TIP]
|
77
|
+
> It's best to insert the middleware at the start of the chain using the [`#prepend` method](https://github.com/sidekiq/sidekiq/blob/d6395641571eba33050d34526bf93bed92504d4d/lib/sidekiq/middleware/chain.rb#L125), as shown above. This is important because `Sidekiq::Fairplay::Middleware` runs twice: first when you attempt to enqueue the job and it gets intercepted, and again when the planner actually enqueues it. If other middlewares are placed before it, this double execution can cause subtle issues. For example, if you use `unique_for`, you must ensure that `Sidekiq::Fairplay::Middleware` comes before `Sidekiq::Enterprise::Unique::Client`; otherwise, such jobs may lock themselves out of execution.
|
78
|
+
|
76
79
|
## API
|
77
80
|
|
78
81
|
In the following example you can see all of the available configuration parameters and their meaning:
|
@@ -171,8 +174,8 @@ We use two simple Redis-backed distributed locks:
|
|
171
174
|
- Ensures only one planner per job class runs at a time.
|
172
175
|
- Not strictly necessary (the first lock already prevents most issues), but adds safety.
|
173
176
|
|
174
|
-
|
175
|
-
It's not the end of the world, but it means you probably should **optimize your `tenant_weights` logic** and/or increase the `enqueue_interval`.
|
177
|
+
> [!WARNING]
|
178
|
+
> If a planner takes longer than its `planner_lock_ttl`, multiple planners may run concurrently. It's not the end of the world, but it means you probably should **optimize your `tenant_weights` logic** and/or increase the `enqueue_interval`.
|
176
179
|
|
177
180
|
## Troubleshooting
|
178
181
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-fairplay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Baygeldin
|
@@ -153,58 +153,44 @@ dependencies:
|
|
153
153
|
name: activesupport
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
155
155
|
requirements:
|
156
|
-
- - "
|
156
|
+
- - ">="
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '7.0'
|
159
159
|
type: :runtime
|
160
160
|
prerelease: false
|
161
161
|
version_requirements: !ruby/object:Gem::Requirement
|
162
162
|
requirements:
|
163
|
-
- - "
|
163
|
+
- - ">="
|
164
164
|
- !ruby/object:Gem::Version
|
165
165
|
version: '7.0'
|
166
166
|
- !ruby/object:Gem::Dependency
|
167
167
|
name: sidekiq
|
168
168
|
requirement: !ruby/object:Gem::Requirement
|
169
169
|
requirements:
|
170
|
-
- - "
|
170
|
+
- - ">="
|
171
171
|
- !ruby/object:Gem::Version
|
172
172
|
version: '7.0'
|
173
173
|
type: :runtime
|
174
174
|
prerelease: false
|
175
175
|
version_requirements: !ruby/object:Gem::Requirement
|
176
176
|
requirements:
|
177
|
-
- - "
|
177
|
+
- - ">="
|
178
178
|
- !ruby/object:Gem::Version
|
179
179
|
version: '7.0'
|
180
180
|
email:
|
181
181
|
- a.baygeldin@gmail.com
|
182
|
-
executables:
|
183
|
-
- console
|
184
|
-
- setup
|
182
|
+
executables: []
|
185
183
|
extensions: []
|
186
184
|
extra_rdoc_files: []
|
187
185
|
files:
|
188
|
-
- ".github/workflows/ci.yml"
|
189
|
-
- ".gitignore"
|
190
|
-
- ".standard.yml"
|
191
|
-
- Gemfile
|
192
186
|
- LICENSE
|
193
187
|
- README.md
|
194
|
-
- Rakefile
|
195
|
-
- bin/console
|
196
|
-
- bin/setup
|
197
|
-
- gemfiles/sidekiq_7.gemfile
|
198
|
-
- gemfiles/sidekiq_8.gemfile
|
199
188
|
- lib/sidekiq/fairplay.rb
|
200
189
|
- lib/sidekiq/fairplay/config.rb
|
201
190
|
- lib/sidekiq/fairplay/middleware.rb
|
202
191
|
- lib/sidekiq/fairplay/planner.rb
|
203
192
|
- lib/sidekiq/fairplay/redis.rb
|
204
193
|
- lib/sidekiq/fairplay/version.rb
|
205
|
-
- sidekiq-fairplay.gemspec
|
206
|
-
- spec/sidekiq/fairplay_spec.rb
|
207
|
-
- spec/spec_helper.rb
|
208
194
|
homepage: http://github.com/baygeldin/sidekiq-fairplay
|
209
195
|
licenses:
|
210
196
|
- MIT
|
data/.github/workflows/ci.yml
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
name: CI
|
2
|
-
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches: [ main, master ]
|
6
|
-
pull_request:
|
7
|
-
branches: [ main, master ]
|
8
|
-
|
9
|
-
jobs:
|
10
|
-
test:
|
11
|
-
name: Test (Ruby ${{ matrix.ruby }}, Sidekiq ${{ matrix.sidekiq }})
|
12
|
-
runs-on: ubuntu-latest
|
13
|
-
timeout-minutes: 15
|
14
|
-
strategy:
|
15
|
-
fail-fast: false
|
16
|
-
matrix:
|
17
|
-
ruby: ['3.4']
|
18
|
-
sidekiq: ['7', '8']
|
19
|
-
|
20
|
-
services:
|
21
|
-
redis:
|
22
|
-
image: redis:7-alpine
|
23
|
-
ports:
|
24
|
-
- 6379:6379
|
25
|
-
options: >-
|
26
|
-
--health-cmd "redis-cli ping || exit 1"
|
27
|
-
--health-interval 10s
|
28
|
-
--health-timeout 5s
|
29
|
-
--health-retries 5
|
30
|
-
|
31
|
-
steps:
|
32
|
-
- name: Checkout
|
33
|
-
uses: actions/checkout@v4
|
34
|
-
|
35
|
-
- name: Set up Ruby
|
36
|
-
uses: ruby/setup-ruby@v1
|
37
|
-
with:
|
38
|
-
ruby-version: ${{ matrix.ruby }}
|
39
|
-
bundler-cache: true
|
40
|
-
|
41
|
-
- name: Select Gemfile for Sidekiq ${{ matrix.sidekiq }}
|
42
|
-
run: |
|
43
|
-
export BUNDLE_GEMFILE="gemfiles/sidekiq_${{ matrix.sidekiq }}.gemfile"
|
44
|
-
echo "BUNDLE_GEMFILE=$BUNDLE_GEMFILE" >> $GITHUB_ENV
|
45
|
-
|
46
|
-
- name: Bundle install
|
47
|
-
run: bundle install --jobs 4 --retry 3
|
48
|
-
|
49
|
-
- name: Run specs
|
50
|
-
env:
|
51
|
-
REDIS_URL: redis://localhost:6379/1
|
52
|
-
run: |
|
53
|
-
bundle exec rspec --format progress
|
54
|
-
|
55
|
-
- uses: qltysh/qlty-action/coverage@v2
|
56
|
-
if: matrix.sidekiq == '8' && matrix.ruby == '3.4'
|
57
|
-
with:
|
58
|
-
token: ${{secrets.QLTY_COVERAGE_TOKEN}}
|
59
|
-
files: coverage/.resultset.json
|
60
|
-
|
61
|
-
lint:
|
62
|
-
name: Lint (standardrb)
|
63
|
-
runs-on: ubuntu-latest
|
64
|
-
continue-on-error: true
|
65
|
-
steps:
|
66
|
-
- name: Checkout
|
67
|
-
uses: actions/checkout@v4
|
68
|
-
|
69
|
-
- name: Set up Ruby
|
70
|
-
uses: ruby/setup-ruby@v1
|
71
|
-
with:
|
72
|
-
ruby-version: '3.4'
|
73
|
-
bundler-cache: true
|
74
|
-
|
75
|
-
- name: Run StandardRB
|
76
|
-
run: bundle exec standardrb
|
data/.gitignore
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
*.gem
|
2
|
-
*.rbc
|
3
|
-
/.config
|
4
|
-
/coverage/
|
5
|
-
/InstalledFiles
|
6
|
-
/pkg/
|
7
|
-
/spec/reports/
|
8
|
-
/spec/examples.txt
|
9
|
-
/test/tmp/
|
10
|
-
/test/version_tmp/
|
11
|
-
/tmp/
|
12
|
-
spec/examples.txt
|
13
|
-
.byebug_history
|
14
|
-
|
15
|
-
## Documentation cache and generated files
|
16
|
-
/.yardoc/
|
17
|
-
/_yardoc/
|
18
|
-
/doc/
|
19
|
-
/rdoc/
|
20
|
-
|
21
|
-
## Environment normalization
|
22
|
-
/.bundle/
|
23
|
-
/vendor/bundle
|
24
|
-
/lib/bundler/man/
|
25
|
-
|
26
|
-
# System files
|
27
|
-
.DS_Store
|
28
|
-
|
29
|
-
# Editors
|
30
|
-
.vscode
|
31
|
-
.ruby-lsp
|
32
|
-
|
33
|
-
# Unnecessary for Ruby gems
|
34
|
-
Gemfile.lock
|
35
|
-
.ruby-version
|
36
|
-
.ruby-gemset
|
37
|
-
.tool-versions
|
data/.standard.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/bin/console
DELETED
data/bin/setup
DELETED
data/gemfiles/sidekiq_7.gemfile
DELETED
data/gemfiles/sidekiq_8.gemfile
DELETED
data/sidekiq-fairplay.gemspec
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
lib = File.expand_path("lib", __dir__)
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require "sidekiq/fairplay/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = "sidekiq-fairplay"
|
7
|
-
spec.version = Sidekiq::Fairplay::VERSION
|
8
|
-
spec.authors = ["Alexander Baygeldin"]
|
9
|
-
spec.email = ["a.baygeldin@gmail.com"]
|
10
|
-
spec.summary = <<~SUMMARY
|
11
|
-
Make Sidekiq play fair — dynamic job prioritization for multi-tenant apps.
|
12
|
-
SUMMARY
|
13
|
-
spec.homepage = "http://github.com/baygeldin/sidekiq-fairplay"
|
14
|
-
spec.license = "MIT"
|
15
|
-
|
16
|
-
spec.files = `git ls-files -z`.split("\x0")
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.require_paths = ["lib"]
|
19
|
-
|
20
|
-
spec.required_ruby_version = ">= 3.4.0"
|
21
|
-
|
22
|
-
spec.add_development_dependency "bundler", "~> 2.0"
|
23
|
-
spec.add_development_dependency "pry", "~> 0.15"
|
24
|
-
spec.add_development_dependency "rake", "~> 13.0"
|
25
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
-
spec.add_development_dependency "rspec-sidekiq", "~> 5.0"
|
27
|
-
spec.add_development_dependency "standard", "~> 1.0"
|
28
|
-
spec.add_development_dependency "standard-performance", "~> 1.0"
|
29
|
-
spec.add_development_dependency "standard-rspec", "~> 0.3"
|
30
|
-
spec.add_development_dependency "simplecov", "~> 0.22"
|
31
|
-
spec.add_development_dependency "timecop", "~> 0.9"
|
32
|
-
|
33
|
-
spec.add_dependency "activesupport", "~> 7.0"
|
34
|
-
spec.add_runtime_dependency "sidekiq", "~> 7.0"
|
35
|
-
|
36
|
-
spec.metadata["rubygems_mfa_required"] = "true"
|
37
|
-
end
|
@@ -1,301 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
class RegularJob
|
4
|
-
include Sidekiq::Job
|
5
|
-
|
6
|
-
def perform(foo)
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class FairplayJob
|
11
|
-
include Sidekiq::Job
|
12
|
-
include Sidekiq::Fairplay::Job
|
13
|
-
|
14
|
-
def perform(tenant_key, foo)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
RSpec.describe Sidekiq::Fairplay do
|
19
|
-
before do
|
20
|
-
FairplayJob.sidekiq_fairplay_options \
|
21
|
-
enqueue_interval:,
|
22
|
-
enqueue_jobs:,
|
23
|
-
planner_queue:,
|
24
|
-
planner_lock_ttl:,
|
25
|
-
latency_threshold:,
|
26
|
-
tenant_key:,
|
27
|
-
tenant_weights:
|
28
|
-
end
|
29
|
-
|
30
|
-
let(:enqueue_interval) { 1 }
|
31
|
-
let(:enqueue_jobs) { 10 }
|
32
|
-
let(:planner_queue) { "default" }
|
33
|
-
let(:planner_lock_ttl) { 60 }
|
34
|
-
let(:latency_threshold) { 60 }
|
35
|
-
let(:tenant_key) { ->(tenant_key, *_args) { tenant_key } }
|
36
|
-
let(:tenant_weights) { ->(tenant_keys) { tenant_keys.to_h { |tid| [tid, 1] } } }
|
37
|
-
|
38
|
-
describe "fairness (probabilistic)" do
|
39
|
-
# Seed Ruby's PRNG for deterministic results
|
40
|
-
around do |example|
|
41
|
-
prev = srand(1234)
|
42
|
-
example.run
|
43
|
-
ensure
|
44
|
-
srand(prev)
|
45
|
-
end
|
46
|
-
|
47
|
-
let(:enqueue_jobs) { 1000 }
|
48
|
-
let(:tenant_weights) do
|
49
|
-
->(tenant_ids) do
|
50
|
-
mapping = {"t1" => 1, "t2" => 3, "t3" => 6}
|
51
|
-
tenant_ids.to_h { |tid| [tid, mapping.fetch(tid, 1)] }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
it "enqueues approximately proportional to weights" do
|
56
|
-
enqueue_jobs.times do |i|
|
57
|
-
FairplayJob.perform_async("t1", "a#{i}")
|
58
|
-
FairplayJob.perform_async("t2", "b#{i}")
|
59
|
-
FairplayJob.perform_async("t3", "c#{i}")
|
60
|
-
end
|
61
|
-
|
62
|
-
Sidekiq::Fairplay::Planner.new.perform("FairplayJob")
|
63
|
-
|
64
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job.exactly(enqueue_jobs)
|
65
|
-
|
66
|
-
jobs_per_tenant = FairplayJob.jobs.each_with_object(Hash.new(0)) do |job, memo|
|
67
|
-
memo[job["args"].first] += 1
|
68
|
-
end
|
69
|
-
|
70
|
-
expected_jobs_per_tenant = {"t1" => 100, "t2" => 300, "t3" => 600}
|
71
|
-
tolerance = 0.25 # 25% tolerance to avoid flakiness across Ruby versions
|
72
|
-
|
73
|
-
expected_jobs_per_tenant.each do |tid, exp|
|
74
|
-
low = (exp * (1 - tolerance)).floor
|
75
|
-
high = (exp * (1 + tolerance)).ceil
|
76
|
-
|
77
|
-
expect(jobs_per_tenant[tid]).to be_between(low, high).inclusive
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
describe "basic functionality" do
|
83
|
-
it "intercepts fairplay jobs and enqueues them later" do
|
84
|
-
FairplayJob.perform_async("t1", "a")
|
85
|
-
FairplayJob.perform_async("t2", "b")
|
86
|
-
FairplayJob.perform_async("t3", "c")
|
87
|
-
|
88
|
-
expect(FairplayJob).not_to have_enqueued_sidekiq_job
|
89
|
-
expect(Sidekiq::Fairplay::Planner)
|
90
|
-
.to have_enqueued_sidekiq_job("FairplayJob")
|
91
|
-
.exactly(1)
|
92
|
-
.immediately
|
93
|
-
|
94
|
-
Sidekiq::Fairplay::Planner.perform_one
|
95
|
-
|
96
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job.exactly(3)
|
97
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t1", "a")
|
98
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t2", "b")
|
99
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t3", "c")
|
100
|
-
end
|
101
|
-
|
102
|
-
context "with custom planner queue" do
|
103
|
-
let(:planner_queue) { "whatever" }
|
104
|
-
|
105
|
-
it "enqueues the planner job on the configured queue" do
|
106
|
-
FairplayJob.perform_async("t1", "a")
|
107
|
-
|
108
|
-
Sidekiq::Fairplay::Planner.perform_one
|
109
|
-
|
110
|
-
expect(Sidekiq::Fairplay::Planner)
|
111
|
-
.to have_enqueued_sidekiq_job("FairplayJob")
|
112
|
-
.on(planner_queue)
|
113
|
-
.in(enqueue_interval.to_i)
|
114
|
-
|
115
|
-
expect(FairplayJob)
|
116
|
-
.to have_enqueued_sidekiq_job("t1", "a")
|
117
|
-
.on("default") # default queue for FairplayJob
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
context "when latency threshold exceeded" do
|
122
|
-
let(:queue) { instance_double(Sidekiq::Queue) }
|
123
|
-
|
124
|
-
before do
|
125
|
-
allow(Sidekiq::Queue).to receive(:new).and_return(queue)
|
126
|
-
allow(queue).to receive(:latency).and_return(latency_threshold.to_i + 1)
|
127
|
-
end
|
128
|
-
|
129
|
-
it "reschedules the planner without enqueuing jobs" do
|
130
|
-
FairplayJob.perform_async("t1", "a")
|
131
|
-
|
132
|
-
Sidekiq::Fairplay::Planner.perform_one
|
133
|
-
|
134
|
-
expect(FairplayJob).not_to have_enqueued_sidekiq_job
|
135
|
-
expect(Sidekiq::Fairplay::Planner)
|
136
|
-
.to have_enqueued_sidekiq_job("FairplayJob")
|
137
|
-
.in(enqueue_interval.to_i)
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
context "with custom weights" do
|
142
|
-
let(:tenant_weights) do
|
143
|
-
->(tenant_ids) do
|
144
|
-
tenant_ids.to_h do |tid|
|
145
|
-
[tid, (tid == "t1") ? 1 : 0]
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
it "uses weights to prefer specific tenant" do
|
151
|
-
FairplayJob.perform_async("t1", "a")
|
152
|
-
FairplayJob.perform_async("t2", "b")
|
153
|
-
|
154
|
-
Sidekiq::Fairplay::Planner.perform_one
|
155
|
-
|
156
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job.exactly(1)
|
157
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t1", "a")
|
158
|
-
expect(FairplayJob).not_to have_enqueued_sidekiq_job("t2", "b")
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
context "when too many jobs in the queue" do
|
163
|
-
let(:enqueue_jobs) { 1 }
|
164
|
-
|
165
|
-
it "respects the enqueue_jobs limit" do
|
166
|
-
FairplayJob.perform_async("t1", "a")
|
167
|
-
FairplayJob.perform_async("t1", "b")
|
168
|
-
|
169
|
-
Sidekiq::Fairplay::Planner.perform_one
|
170
|
-
|
171
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job.exactly(1)
|
172
|
-
expect(FairplayJob)
|
173
|
-
.to have_enqueued_sidekiq_job("t1", "a")
|
174
|
-
.or have_enqueued_sidekiq_job("t1", "b")
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
describe "edge cases" do
|
180
|
-
it "ignores unknown job class" do
|
181
|
-
Sidekiq::Fairplay::Planner.new.perform("UnknownJob")
|
182
|
-
|
183
|
-
expect(Sidekiq::Fairplay::Planner).not_to have_enqueued_sidekiq_job
|
184
|
-
expect(FairplayJob).not_to have_enqueued_sidekiq_job
|
185
|
-
end
|
186
|
-
|
187
|
-
it "has no effect on regular jobs" do
|
188
|
-
RegularJob.perform_async("foo")
|
189
|
-
|
190
|
-
expect(RegularJob).to have_enqueued_sidekiq_job("foo")
|
191
|
-
expect(Sidekiq::Fairplay::Planner).not_to have_enqueued_sidekiq_job
|
192
|
-
end
|
193
|
-
|
194
|
-
it "has no effect on scheduled jobs" do
|
195
|
-
FairplayJob.perform_in(5, "t1", "a")
|
196
|
-
|
197
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t1", "a").in(5)
|
198
|
-
expect(Sidekiq::Fairplay::Planner).not_to have_enqueued_sidekiq_job
|
199
|
-
end
|
200
|
-
|
201
|
-
context "with zero weights for all tenants" do
|
202
|
-
let(:tenant_weights) do
|
203
|
-
->(tenant_ids) { tenant_ids.to_h { |tid| [tid, 0] } }
|
204
|
-
end
|
205
|
-
|
206
|
-
it "enqueues no jobs" do
|
207
|
-
FairplayJob.perform_async("t1", "a")
|
208
|
-
FairplayJob.perform_async("t2", "b")
|
209
|
-
|
210
|
-
Sidekiq::Fairplay::Planner.perform_one
|
211
|
-
|
212
|
-
expect(FairplayJob).not_to have_enqueued_sidekiq_job
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
describe "errors" do
|
218
|
-
let(:tenant_key) { ->(_tid, *_args) {} }
|
219
|
-
|
220
|
-
it "raises when tenant key resolves to nil" do
|
221
|
-
tenant_key
|
222
|
-
|
223
|
-
expect { FairplayJob.perform_async("t1", "a") }
|
224
|
-
.to raise_error(ArgumentError, /tenant key cannot be nil/)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
describe "implementation details" do
|
229
|
-
it "reschedules planning for the next interval" do
|
230
|
-
FairplayJob.perform_async("t1", "a")
|
231
|
-
|
232
|
-
Sidekiq::Fairplay::Planner.perform_one
|
233
|
-
|
234
|
-
expect(Sidekiq::Fairplay::Planner)
|
235
|
-
.to have_enqueued_sidekiq_job("FairplayJob")
|
236
|
-
.in(enqueue_interval.to_i)
|
237
|
-
end
|
238
|
-
|
239
|
-
context "when planner_lock_ttl is being held" do
|
240
|
-
let(:planner_lock_ttl) { 42 }
|
241
|
-
|
242
|
-
before do
|
243
|
-
redis = Sidekiq::Fairplay::Redis.new
|
244
|
-
redis.try_acquire_planner_lock(FairplayJob, "some_jid")
|
245
|
-
end
|
246
|
-
|
247
|
-
it "blocks planning until the TTL expires" do
|
248
|
-
FairplayJob.perform_async("t1", "a")
|
249
|
-
|
250
|
-
Sidekiq::Fairplay::Planner.perform_one
|
251
|
-
|
252
|
-
expect(FairplayJob).not_to have_enqueued_sidekiq_job
|
253
|
-
expect(Sidekiq::Fairplay::Planner)
|
254
|
-
.to have_enqueued_sidekiq_job("FairplayJob")
|
255
|
-
.in(enqueue_interval.to_i)
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
context "when tenant_key and tenant_weights refer to class methods" do
|
260
|
-
before do
|
261
|
-
class << FairplayJob
|
262
|
-
def static_tenant_key(tid, *_args) = tid
|
263
|
-
def static_tenant_weights(tids) = tids.to_h { |tid| [tid, 1] }
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
let(:tenant_key) { ->(tid, *args) { static_tenant_key(tid, *args) } }
|
268
|
-
let(:tenant_weights) { ->(tids) { static_tenant_weights(tids) } }
|
269
|
-
|
270
|
-
it "works as expected" do
|
271
|
-
FairplayJob.perform_async("t1", "a")
|
272
|
-
FairplayJob.perform_async("t2", "b")
|
273
|
-
|
274
|
-
Sidekiq::Fairplay::Planner.perform_one
|
275
|
-
|
276
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job.exactly(2)
|
277
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t1", "a")
|
278
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t2", "b")
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
context "when using ActiveSupport::Duration" do
|
283
|
-
let(:enqueue_interval) { 1.minute }
|
284
|
-
let(:latency_threshold) { 1.hour }
|
285
|
-
let(:planner_lock_ttl) { 10.seconds }
|
286
|
-
|
287
|
-
it "handles durations correctly" do
|
288
|
-
FairplayJob.perform_async("t1", "a")
|
289
|
-
|
290
|
-
Sidekiq::Fairplay::Planner.perform_one
|
291
|
-
|
292
|
-
expect(Sidekiq::Fairplay::Planner)
|
293
|
-
.to have_enqueued_sidekiq_job("FairplayJob")
|
294
|
-
.in(enqueue_interval.to_i)
|
295
|
-
|
296
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job.exactly(1)
|
297
|
-
expect(FairplayJob).to have_enqueued_sidekiq_job("t1", "a")
|
298
|
-
end
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
$LOAD_PATH << "." unless $LOAD_PATH.include?(".")
|
2
|
-
|
3
|
-
require "rubygems"
|
4
|
-
require "bundler/setup"
|
5
|
-
require "timecop"
|
6
|
-
require "simplecov"
|
7
|
-
|
8
|
-
require "sidekiq"
|
9
|
-
require "rspec-sidekiq"
|
10
|
-
require "sidekiq/fairplay"
|
11
|
-
require "pry"
|
12
|
-
|
13
|
-
SimpleCov.start do
|
14
|
-
add_filter "spec"
|
15
|
-
end
|
16
|
-
|
17
|
-
Sidekiq::Fairplay.logger = nil
|
18
|
-
|
19
|
-
Sidekiq.configure_client do |config|
|
20
|
-
config.redis = {db: 1}
|
21
|
-
config.logger = nil
|
22
|
-
|
23
|
-
config.client_middleware do |chain|
|
24
|
-
chain.add Sidekiq::Fairplay::Middleware
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
Sidekiq.configure_server do |config|
|
29
|
-
config.redis = {db: 1}
|
30
|
-
config.logger = nil
|
31
|
-
|
32
|
-
config.client_middleware do |chain|
|
33
|
-
chain.add Sidekiq::Fairplay::Middleware
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
RSpec::Sidekiq.configure do |config|
|
38
|
-
config.clear_all_enqueued_jobs = true
|
39
|
-
config.warn_when_jobs_not_processed_by_sidekiq = false
|
40
|
-
end
|
41
|
-
|
42
|
-
RSpec.configure do |config|
|
43
|
-
config.order = :random
|
44
|
-
config.run_all_when_everything_filtered = true
|
45
|
-
config.example_status_persistence_file_path = "spec/examples.txt"
|
46
|
-
|
47
|
-
config.before do
|
48
|
-
Sidekiq.redis do |conn|
|
49
|
-
keys = conn.call("KEYS", "fairplay*")
|
50
|
-
keys.each { |key| conn.call("DEL", key) }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
config.before do
|
55
|
-
Timecop.freeze
|
56
|
-
end
|
57
|
-
|
58
|
-
config.after do
|
59
|
-
Timecop.return
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
$LOAD_PATH << File.join(File.dirname(__FILE__), "..", "lib")
|