joblin 0.1.0
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 +7 -0
- data/README.md +1 -0
- data/app/models/joblin/background_task/api_access.rb +148 -0
- data/app/models/joblin/background_task/attachments.rb +47 -0
- data/app/models/joblin/background_task/executor.rb +63 -0
- data/app/models/joblin/background_task/options.rb +75 -0
- data/app/models/joblin/background_task/retention_policy.rb +28 -0
- data/app/models/joblin/background_task.rb +72 -0
- data/app/models/joblin/concerns/job_working_dirs.rb +21 -0
- data/db/migrate/20250903184852_create_background_tasks.rb +12 -0
- data/joblin.gemspec +35 -0
- data/lib/joblin/batching/batch.rb +537 -0
- data/lib/joblin/batching/callback.rb +135 -0
- data/lib/joblin/batching/chain_builder.rb +247 -0
- data/lib/joblin/batching/compat/active_job.rb +108 -0
- data/lib/joblin/batching/compat/sidekiq/web/batches_assets/css/styles.less +182 -0
- data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
- data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/util.js +2 -0
- data/lib/joblin/batching/compat/sidekiq/web/helpers.rb +41 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/_batch_tree.erb +6 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/_batches_table.erb +44 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/_common.erb +13 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/_jobs_table.erb +21 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/_pagination.erb +26 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/batch.erb +81 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/batches.erb +23 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/pool.erb +137 -0
- data/lib/joblin/batching/compat/sidekiq/web/views/pools.erb +47 -0
- data/lib/joblin/batching/compat/sidekiq/web.rb +218 -0
- data/lib/joblin/batching/compat/sidekiq.rb +149 -0
- data/lib/joblin/batching/compat.rb +20 -0
- data/lib/joblin/batching/context_hash.rb +157 -0
- data/lib/joblin/batching/hier_batch_ids.lua +25 -0
- data/lib/joblin/batching/jobs/base_job.rb +7 -0
- data/lib/joblin/batching/jobs/concurrent_batch_job.rb +20 -0
- data/lib/joblin/batching/jobs/managed_batch_job.rb +175 -0
- data/lib/joblin/batching/jobs/serial_batch_job.rb +20 -0
- data/lib/joblin/batching/pool.rb +254 -0
- data/lib/joblin/batching/pool_refill.lua +47 -0
- data/lib/joblin/batching/schedule_callback.lua +14 -0
- data/lib/joblin/batching/status.rb +89 -0
- data/lib/joblin/engine.rb +15 -0
- data/lib/joblin/lazy_access.rb +72 -0
- data/lib/joblin/uniqueness/compat/active_job.rb +75 -0
- data/lib/joblin/uniqueness/compat/sidekiq.rb +135 -0
- data/lib/joblin/uniqueness/compat.rb +20 -0
- data/lib/joblin/uniqueness/configuration.rb +25 -0
- data/lib/joblin/uniqueness/job_uniqueness.rb +49 -0
- data/lib/joblin/uniqueness/lock_context.rb +199 -0
- data/lib/joblin/uniqueness/locksmith.rb +92 -0
- data/lib/joblin/uniqueness/on_conflict/base.rb +32 -0
- data/lib/joblin/uniqueness/on_conflict/log.rb +13 -0
- data/lib/joblin/uniqueness/on_conflict/null_strategy.rb +9 -0
- data/lib/joblin/uniqueness/on_conflict/raise.rb +11 -0
- data/lib/joblin/uniqueness/on_conflict/reject.rb +21 -0
- data/lib/joblin/uniqueness/on_conflict/reschedule.rb +20 -0
- data/lib/joblin/uniqueness/on_conflict.rb +62 -0
- data/lib/joblin/uniqueness/strategy/base.rb +107 -0
- data/lib/joblin/uniqueness/strategy/until_and_while_executing.rb +35 -0
- data/lib/joblin/uniqueness/strategy/until_executed.rb +20 -0
- data/lib/joblin/uniqueness/strategy/until_executing.rb +20 -0
- data/lib/joblin/uniqueness/strategy/until_expired.rb +16 -0
- data/lib/joblin/uniqueness/strategy/while_executing.rb +26 -0
- data/lib/joblin/uniqueness/strategy.rb +27 -0
- data/lib/joblin/uniqueness/unique_job_common.rb +79 -0
- data/lib/joblin/version.rb +3 -0
- data/lib/joblin.rb +37 -0
- data/spec/batching/batch_spec.rb +493 -0
- data/spec/batching/callback_spec.rb +38 -0
- data/spec/batching/compat/active_job_spec.rb +107 -0
- data/spec/batching/compat/sidekiq_spec.rb +127 -0
- data/spec/batching/context_hash_spec.rb +54 -0
- data/spec/batching/flow_spec.rb +82 -0
- data/spec/batching/integration/fail_then_succeed.rb +42 -0
- data/spec/batching/integration/integration.rb +57 -0
- data/spec/batching/integration/nested.rb +88 -0
- data/spec/batching/integration/simple.rb +47 -0
- data/spec/batching/integration/workflow.rb +134 -0
- data/spec/batching/integration_helper.rb +50 -0
- data/spec/batching/pool_spec.rb +161 -0
- data/spec/batching/status_spec.rb +76 -0
- data/spec/batching/support/base_job.rb +19 -0
- data/spec/batching/support/sample_callback.rb +2 -0
- data/spec/internal/config/database.yml +5 -0
- data/spec/internal/config/routes.rb +5 -0
- data/spec/internal/config/storage.yml +3 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +6 -0
- data/spec/internal/log/test.log +48200 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/models/background_task_spec.rb +41 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/uniqueness/compat/active_job_spec.rb +49 -0
- data/spec/uniqueness/compat/sidekiq_spec.rb +68 -0
- data/spec/uniqueness/lock_context_spec.rb +106 -0
- data/spec/uniqueness/on_conflict/log_spec.rb +11 -0
- data/spec/uniqueness/on_conflict/raise_spec.rb +10 -0
- data/spec/uniqueness/on_conflict/reschedule_spec.rb +63 -0
- data/spec/uniqueness/on_conflict_spec.rb +16 -0
- data/spec/uniqueness/spec_helper.rb +19 -0
- data/spec/uniqueness/strategy/base_spec.rb +100 -0
- data/spec/uniqueness/strategy/until_and_while_executing_spec.rb +48 -0
- data/spec/uniqueness/strategy/until_executed_spec.rb +23 -0
- data/spec/uniqueness/strategy/until_executing_spec.rb +23 -0
- data/spec/uniqueness/strategy/until_expired_spec.rb +23 -0
- data/spec/uniqueness/strategy/while_executing_spec.rb +33 -0
- data/spec/uniqueness/support/lock_strategy.rb +28 -0
- data/spec/uniqueness/support/on_conflict.rb +24 -0
- data/spec/uniqueness/support/test_worker.rb +19 -0
- data/spec/uniqueness/unique_job_common_spec.rb +45 -0
- metadata +308 -0
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::BackgroundTask, type: :model do
|
|
4
|
+
let(:task) { Joblin::BackgroundTask.create!(type: 'Joblin::BackgroundTask') }
|
|
5
|
+
|
|
6
|
+
describe '#options' do
|
|
7
|
+
it 'saves changes' do
|
|
8
|
+
task.options['foo'] = 'bar'
|
|
9
|
+
task.save!
|
|
10
|
+
expect(task.reload.extra_options).to eq({ "foo" => 'bar' })
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'stores references' do
|
|
14
|
+
task.options['ref'] = task
|
|
15
|
+
task.save!
|
|
16
|
+
task.reload
|
|
17
|
+
expect(task.options['ref']).to eq(task)
|
|
18
|
+
expect(task.extra_options.to_h).to eq({ 'ref' => task.to_gid.to_s })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'is indifferent' do
|
|
22
|
+
task.options[:foo] = 'bar'
|
|
23
|
+
task.save!
|
|
24
|
+
task.reload
|
|
25
|
+
expect(task.options[:foo]).to eq('bar')
|
|
26
|
+
expect(task.options['foo']).to eq('bar')
|
|
27
|
+
expect(task.options[:updated_at]).to eq(task.updated_at)
|
|
28
|
+
expect(task.options['updated_at']).to eq(task.updated_at)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'reads from columns' do
|
|
32
|
+
expect(task.options[:updated_at]).to eq(task.updated_at)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'writes to columns' do
|
|
36
|
+
dt = 3.days.ago
|
|
37
|
+
task.options[:updated_at] = dt
|
|
38
|
+
expect(task.updated_at).to eq(dt)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'bundler'
|
|
2
|
+
|
|
3
|
+
Bundler.require :default, :development
|
|
4
|
+
|
|
5
|
+
Combustion.initialize! :active_record, :active_job
|
|
6
|
+
|
|
7
|
+
require 'redis'
|
|
8
|
+
|
|
9
|
+
require 'rspec/rails'
|
|
10
|
+
# If you're using Capybara:
|
|
11
|
+
# require 'capybara/rails'
|
|
12
|
+
|
|
13
|
+
require 'sidekiq'
|
|
14
|
+
require 'sidekiq/rails'
|
|
15
|
+
require 'sidekiq/testing'
|
|
16
|
+
Sidekiq::Testing.fake!
|
|
17
|
+
Sidekiq::Testing.server_middleware do |chain|
|
|
18
|
+
chain.add Joblin::Batching::Compat::Sidekiq::ServerMiddleware
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Dir[File.dirname(__FILE__) + "/batching/support/**/*.rb"].each {|f| require f }
|
|
22
|
+
|
|
23
|
+
RSpec.configure do |config|
|
|
24
|
+
config.use_transactional_fixtures = true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def set_batch_context(ctx)
|
|
28
|
+
allow_any_instance_of(ActiveJob::Base).to receive(:batch_context).and_return(ctx.with_indifferent_access)
|
|
29
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Compat::ActiveJob do
|
|
4
|
+
context 'Job Extension' do
|
|
5
|
+
it 'includes UniqueJobExtension' do
|
|
6
|
+
expect(ActiveJob::Base < Joblin::Uniqueness::Compat::ActiveJob::JobExtension).to be true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'has the ensure_uniqueness method' do
|
|
10
|
+
expect(ActiveJob::Base.method(:ensure_uniqueness)).to be_present
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context 'Job' do
|
|
15
|
+
let(:test_job) do
|
|
16
|
+
Class.new(ActiveJob::Base) do
|
|
17
|
+
ensure_uniqueness(
|
|
18
|
+
strategy: :until_executed,
|
|
19
|
+
)
|
|
20
|
+
def perform; end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
before(:each) do
|
|
25
|
+
stub_const('TestJob', test_job)
|
|
26
|
+
ActiveJob::Base.queue_adapter = :sidekiq
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'runs as expected' do
|
|
30
|
+
strategy = Joblin::Uniqueness::Strategy::UntilExecuted.new(nil)
|
|
31
|
+
allow_any_instance_of(Joblin::Uniqueness::LockContext).to receive(:lock_strategy) do |lock_context|
|
|
32
|
+
strategy.instance_variable_set(:@lock_context, lock_context)
|
|
33
|
+
strategy
|
|
34
|
+
end
|
|
35
|
+
allow(Joblin::Uniqueness::Strategy::UntilExecuted).to receive(:new).and_return(strategy)
|
|
36
|
+
|
|
37
|
+
expect(strategy).to receive(:on_enqueue).and_call_original
|
|
38
|
+
expect(strategy).to receive(:on_perform).and_call_original
|
|
39
|
+
expect(strategy).to receive(:batch_callback).with(:complete, anything).and_call_original
|
|
40
|
+
expect(strategy).to receive(:batch_callback).with(:success, anything).and_call_original
|
|
41
|
+
|
|
42
|
+
expect_any_instance_of(test_job).to receive(:perform)
|
|
43
|
+
|
|
44
|
+
Sidekiq::Testing.inline! do
|
|
45
|
+
test_job.perform_later
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Compat::Sidekiq do
|
|
4
|
+
let(:config) { defined?(Sidekiq::Config) ? double(Sidekiq::Config) : class_double(Sidekiq) }
|
|
5
|
+
let(:client_middleware) { double(Sidekiq::Middleware::Chain) }
|
|
6
|
+
|
|
7
|
+
context 'client' do
|
|
8
|
+
it 'adds client middleware' do
|
|
9
|
+
allow(Sidekiq).to receive(:configure_client).and_yield(config)
|
|
10
|
+
expect(config).to receive(:client_middleware).and_yield(client_middleware)
|
|
11
|
+
expect(client_middleware).to receive(:insert_before).with(anything, Joblin::Uniqueness::Compat::Sidekiq::ClientMiddleware)
|
|
12
|
+
Joblin::Uniqueness::Compat::Sidekiq.configure
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context 'server' do
|
|
17
|
+
let(:server_middleware) { double(Sidekiq::Middleware::Chain) }
|
|
18
|
+
|
|
19
|
+
it 'adds client and server middleware' do
|
|
20
|
+
allow(Sidekiq).to receive(:configure_server).and_yield(config)
|
|
21
|
+
expect(config).to receive(:client_middleware).and_yield(client_middleware)
|
|
22
|
+
expect(config).to receive(:server_middleware).and_yield(server_middleware)
|
|
23
|
+
expect(client_middleware).to receive(:insert_before).with(anything, Joblin::Uniqueness::Compat::Sidekiq::ClientMiddleware)
|
|
24
|
+
expect(server_middleware).to receive(:insert_after).with(anything, Joblin::Uniqueness::Compat::Sidekiq::ServerMiddleware)
|
|
25
|
+
Joblin::Uniqueness::Compat::Sidekiq.configure
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'Job Extension' do
|
|
30
|
+
let!(:dummy_worker) do
|
|
31
|
+
Class.new do
|
|
32
|
+
include Sidekiq::Worker
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'includes UniqueJobExtension' do
|
|
37
|
+
expect(dummy_worker < Joblin::Uniqueness::Compat::Sidekiq::WorkerExtension).to be true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'has the ensure_uniqueness method' do
|
|
41
|
+
expect(dummy_worker.method(:ensure_uniqueness)).to be_present
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'Job' do
|
|
46
|
+
include_context 'with TestWorker'
|
|
47
|
+
|
|
48
|
+
it 'runs as expected' do
|
|
49
|
+
strategy = Joblin::Uniqueness::Strategy::UntilExecuted.new(nil)
|
|
50
|
+
allow_any_instance_of(Joblin::Uniqueness::LockContext).to receive(:lock_strategy) do |lock_context|
|
|
51
|
+
strategy.instance_variable_set(:@lock_context, lock_context)
|
|
52
|
+
strategy
|
|
53
|
+
end
|
|
54
|
+
allow(Joblin::Uniqueness::Strategy::UntilExecuted).to receive(:new).and_return(strategy)
|
|
55
|
+
|
|
56
|
+
expect(strategy).to receive(:on_enqueue).and_call_original
|
|
57
|
+
expect(strategy).to receive(:on_perform).and_call_original
|
|
58
|
+
expect(strategy).to receive(:batch_callback).with(:complete, anything).and_call_original
|
|
59
|
+
expect(strategy).to receive(:batch_callback).with(:success, anything).and_call_original
|
|
60
|
+
|
|
61
|
+
expect_any_instance_of(TestWorker).to receive(:perform)
|
|
62
|
+
|
|
63
|
+
Sidekiq::Testing.inline! do
|
|
64
|
+
worker.perform_async
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::LockContext do
|
|
4
|
+
let!(:worker) do
|
|
5
|
+
Class.new do
|
|
6
|
+
include Sidekiq::Worker
|
|
7
|
+
|
|
8
|
+
ensure_uniqueness(
|
|
9
|
+
strategy: :until_executed,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def perform; end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before(:each) { stub_const('TestWorker', worker) }
|
|
17
|
+
|
|
18
|
+
let(:lock_context) { described_class.new(context_data) }
|
|
19
|
+
let(:context_data) { { queue: 'default', job_clazz: 'TestWorker', args: [1,2,3], kwargs: { foo: 'bar' } } }
|
|
20
|
+
|
|
21
|
+
describe '#base_key' do
|
|
22
|
+
let(:scope) { :per_queue }
|
|
23
|
+
before(:each) do
|
|
24
|
+
TestWorker.unique_job_options[:scope] = scope
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'returns matching keys for equal hashes' do
|
|
28
|
+
context_data[:args] = [{ foo: 'bar', bar: 'foo' }]
|
|
29
|
+
key1 = lock_context.base_key
|
|
30
|
+
lock_context.instance_variable_set(:@base_key, nil)
|
|
31
|
+
|
|
32
|
+
context_data[:args] = [{ bar: 'foo', foo: 'bar' }]
|
|
33
|
+
key2 = lock_context.base_key
|
|
34
|
+
|
|
35
|
+
expect(key1).to eq key2
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'when scope is a Proc' do
|
|
39
|
+
let(:scope) { ->(queue:) { "blob" } }
|
|
40
|
+
|
|
41
|
+
it 'returns the base key' do
|
|
42
|
+
expect(lock_context.base_key).to match /^uniquejob:blob:.*/
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'when scope is :global' do
|
|
47
|
+
let(:scope) { :global }
|
|
48
|
+
|
|
49
|
+
it 'returns the base key' do
|
|
50
|
+
expect(lock_context.base_key).to match /^uniquejob:TestWorker:.*/
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'when scope is :per_queue' do
|
|
55
|
+
let(:scope) { :per_queue }
|
|
56
|
+
|
|
57
|
+
it 'returns the base key' do
|
|
58
|
+
expect(lock_context.base_key).to match /^uniquejob:TestWorker:default:.*/
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'when scope is not a Proc, :global, or :per_queue' do
|
|
63
|
+
let(:scope) { 'foo' }
|
|
64
|
+
|
|
65
|
+
it 'returns the base key' do
|
|
66
|
+
expect(lock_context.base_key).to match /^uniquejob:foo:.*/
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '#call_conflict_strategy' do
|
|
72
|
+
before(:each) do
|
|
73
|
+
worker.unique_job_options[:on_conflict] = :raise
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'invokes the conflict strategy' do
|
|
77
|
+
expect_any_instance_of(Joblin::Uniqueness::OnConflict::Raise).to receive(:call)
|
|
78
|
+
lock_context.send(:call_conflict_strategy, :perform)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '#conflict_strategy_for' do
|
|
83
|
+
before(:each) do
|
|
84
|
+
worker.unique_job_options[:on_conflict] = :raise
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'resolves a united conflict strategy' do
|
|
88
|
+
expect(lock_context.send(:conflict_strategy_for, :perform)).to be_a Joblin::Uniqueness::OnConflict::Raise
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'resolves enqueue strategy' do
|
|
92
|
+
worker.unique_job_options[:on_conflict] = { enqueue: :log, perform: :raise }
|
|
93
|
+
expect(lock_context.send(:conflict_strategy_for, :enqueue)).to be_a Joblin::Uniqueness::OnConflict::Log
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'resolves perform strategy' do
|
|
97
|
+
worker.unique_job_options[:on_conflict] = { enqueue: :log, perform: :reject }
|
|
98
|
+
expect(lock_context.send(:conflict_strategy_for, :perform)).to be_a Joblin::Uniqueness::OnConflict::Reject
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'resolves null strategy' do
|
|
102
|
+
worker.unique_job_options[:on_conflict] = { enqueue: :log }
|
|
103
|
+
expect(lock_context.send(:conflict_strategy_for, :perform)).to be_a Joblin::Uniqueness::OnConflict::NullStrategy
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::OnConflict::Log do
|
|
4
|
+
include_context "on_conflict specs"
|
|
5
|
+
include_examples "OnConflict is compatible with", %i[until_executing while_executing until_executed until_and_while_executing until_expired]
|
|
6
|
+
|
|
7
|
+
it "logs" do
|
|
8
|
+
expect(Joblin::Uniqueness.logger).to receive(:info)
|
|
9
|
+
on_conflict.call
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::OnConflict::Raise do
|
|
4
|
+
include_context "on_conflict specs"
|
|
5
|
+
include_examples "OnConflict is compatible with", %i[until_executing while_executing until_executed until_and_while_executing until_expired]
|
|
6
|
+
|
|
7
|
+
it "raises an error" do
|
|
8
|
+
expect { on_conflict.call }.to raise_error(Joblin::Uniqueness::Conflict)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::OnConflict::Reschedule do
|
|
4
|
+
include_context "on_conflict specs"
|
|
5
|
+
include_examples "OnConflict is compatible with", %i[while_executing]
|
|
6
|
+
|
|
7
|
+
it "calls reenqueue" do
|
|
8
|
+
expect(lock_context).to receive(:reenqueue) do |*args, **kwargs|
|
|
9
|
+
expect(Thread.current[:unique_jobs_previous_context]).to be_present
|
|
10
|
+
end
|
|
11
|
+
on_conflict.call
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "reschedules successfully" do
|
|
15
|
+
TestWorker.unique_job_options.merge!({ strategy: :until_and_while_executing, on_conflict: { enqueue: :raise, perform: :reschedule }})
|
|
16
|
+
|
|
17
|
+
lock_context.handle_lifecycle!(:enqueue) {}
|
|
18
|
+
lock_context.handle_lifecycle!(:perform) {}
|
|
19
|
+
|
|
20
|
+
lock_context2 = Joblin::Uniqueness::Compat::Sidekiq::SidekiqLockContext.new({ jid: 'j2', queue: 'default', job_clazz: 'TestWorker'}, job_instance: {})
|
|
21
|
+
lock_context2.handle_lifecycle!(:enqueue) {}
|
|
22
|
+
lock_context2.handle_lifecycle!(:perform) {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "maintains a consistent lock_id" do
|
|
26
|
+
TestWorker.clear
|
|
27
|
+
TestWorker.unique_job_options.merge!({ strategy: :while_executing, on_conflict: :reschedule })
|
|
28
|
+
|
|
29
|
+
lock_context.handle_lifecycle!(:enqueue) {}
|
|
30
|
+
lock_context.handle_lifecycle!(:perform) {}
|
|
31
|
+
|
|
32
|
+
TestWorker.perform_async
|
|
33
|
+
j1 = TestWorker.jobs[0]
|
|
34
|
+
TestWorker.perform_one
|
|
35
|
+
j2 = TestWorker.jobs[0]
|
|
36
|
+
|
|
37
|
+
expect(j1).not_to eq(j2)
|
|
38
|
+
expect(j1['uniqueness_cache_data']['lid']).to be_present
|
|
39
|
+
expect(j1['uniqueness_cache_data']['lid']).to eq(j2['uniqueness_cache_data']['lid'])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "unlocks after rescheduling" do
|
|
43
|
+
TestWorker.clear
|
|
44
|
+
TestWorker.unique_job_options.merge!({ strategy: :while_executing, on_conflict: :reschedule })
|
|
45
|
+
|
|
46
|
+
lock_context.handle_lifecycle!(:enqueue) {}
|
|
47
|
+
lock_context.handle_lifecycle!(:perform) {}
|
|
48
|
+
|
|
49
|
+
TestWorker.perform_async
|
|
50
|
+
expect(TestWorker.jobs.size).to eq(1)
|
|
51
|
+
TestWorker.perform_one
|
|
52
|
+
expect(TestWorker.jobs.size).to eq(1)
|
|
53
|
+
|
|
54
|
+
lock_context.lock_strategy.send(:unlock)
|
|
55
|
+
Sidekiq::Worker.drain_all
|
|
56
|
+
expect(TestWorker.jobs.size).to eq(0)
|
|
57
|
+
|
|
58
|
+
# Run another worker to validate that the lock was released
|
|
59
|
+
TestWorker.perform_async
|
|
60
|
+
TestWorker.perform_one
|
|
61
|
+
expect(TestWorker.jobs.size).to eq(0)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::OnConflict do
|
|
4
|
+
|
|
5
|
+
describe ".validate!" do
|
|
6
|
+
it "passes a valid pair" do
|
|
7
|
+
described_class.validate!(:reject, :while_executing)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "raises an error if the strategy is not valid for the given action" do
|
|
11
|
+
expect do
|
|
12
|
+
described_class.validate!(:reject, :until_executed)
|
|
13
|
+
end.to raise_error(ArgumentError)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
require "joblin/uniqueness/job_uniqueness"
|
|
3
|
+
|
|
4
|
+
require 'sidekiq/testing'
|
|
5
|
+
|
|
6
|
+
require 'redis'
|
|
7
|
+
Redis.silence_deprecations = true
|
|
8
|
+
|
|
9
|
+
Dir[File.join(File.dirname(__FILE__), "./support/**/*.rb")].sort.each { |f| require f }
|
|
10
|
+
|
|
11
|
+
Sidekiq::Testing.server_middleware do |chain|
|
|
12
|
+
chain.insert_after Joblin::Batching::Compat::Sidekiq::ServerMiddleware, Joblin::Uniqueness::Compat::Sidekiq::ServerMiddleware
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
RSpec.configure do |config|
|
|
16
|
+
config.before(:each) do
|
|
17
|
+
Joblin.redis.flushdb
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Strategy::Base do
|
|
4
|
+
include_context 'with TestWorker'
|
|
5
|
+
|
|
6
|
+
let!(:jid) { worker.perform_async }
|
|
7
|
+
let(:context) { Joblin::Uniqueness::LockContext.new({ jid: jid, queue: 'default', job_clazz: 'TestWorker' }) }
|
|
8
|
+
let(:strategy) { Joblin::Uniqueness::Strategy::UntilExecuted.new(context) }
|
|
9
|
+
|
|
10
|
+
describe '#wrap_in_batch' do
|
|
11
|
+
it 'does not wrap if re-enqueuing' do
|
|
12
|
+
Thread.current[:unique_jobs_previous_context] = context
|
|
13
|
+
expect(Joblin::Batching::Batch).not_to receive(:new)
|
|
14
|
+
strategy.send(:wrap_in_batch, &->{ })
|
|
15
|
+
ensure
|
|
16
|
+
Thread.current[:unique_jobs_previous_context] = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'when the job fails' do
|
|
20
|
+
before(:each) do
|
|
21
|
+
TestWorker.unique_job_options[:strategy] = :until_executed
|
|
22
|
+
allow_any_instance_of(worker).to receive(:perform) do |w|
|
|
23
|
+
@bid = w.bid
|
|
24
|
+
raise 'error'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context 'unlock_on_failure: :any' do
|
|
29
|
+
before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :any }
|
|
30
|
+
|
|
31
|
+
it 'unlocks on any failure' do
|
|
32
|
+
expect(strategy).to be_locked
|
|
33
|
+
expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
|
|
34
|
+
Sidekiq::Worker.drain_all
|
|
35
|
+
expect(strategy).not_to be_locked
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'unlock_on_failure: :death' do
|
|
40
|
+
before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :death }
|
|
41
|
+
|
|
42
|
+
it 'does not unlock on any failure' do
|
|
43
|
+
expect(strategy).to be_locked
|
|
44
|
+
expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
|
|
45
|
+
Sidekiq::Worker.drain_all
|
|
46
|
+
expect(strategy).to be_locked
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'it unlocks on death' do
|
|
50
|
+
expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
|
|
51
|
+
Sidekiq::Worker.drain_all
|
|
52
|
+
expect(strategy).to be_locked
|
|
53
|
+
Joblin::Batching::Batch.process_dead_job(@bid, jid)
|
|
54
|
+
Sidekiq::Worker.drain_all
|
|
55
|
+
expect(strategy).not_to be_locked
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'unlock_on_failure: :stagnant' do
|
|
60
|
+
before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :stagnant }
|
|
61
|
+
|
|
62
|
+
it 'does not unlock on any failure' do
|
|
63
|
+
expect(strategy).to be_locked
|
|
64
|
+
expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
|
|
65
|
+
Sidekiq::Worker.drain_all
|
|
66
|
+
expect(strategy).to be_locked
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'it unlocks on stagnation' do
|
|
70
|
+
expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
|
|
71
|
+
Sidekiq::Worker.drain_all
|
|
72
|
+
expect(strategy).to be_locked
|
|
73
|
+
Joblin::Batching::Batch.process_dead_job(@bid, jid)
|
|
74
|
+
Sidekiq::Worker.drain_all
|
|
75
|
+
expect(strategy).not_to be_locked
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context 'unlock_on_failure: :expire' do
|
|
80
|
+
before(:each) { TestWorker.unique_job_options[:unlock_on_failure] = :expire }
|
|
81
|
+
|
|
82
|
+
it 'does not unlock on any failure' do
|
|
83
|
+
expect(strategy).to be_locked
|
|
84
|
+
expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
|
|
85
|
+
Sidekiq::Worker.drain_all
|
|
86
|
+
expect(strategy).to be_locked
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'does not unlock on death' do
|
|
90
|
+
expect{ Sidekiq::Worker.drain_all }.to raise_error('error')
|
|
91
|
+
Sidekiq::Worker.drain_all
|
|
92
|
+
expect(strategy).to be_locked
|
|
93
|
+
Joblin::Batching::Batch.process_dead_job(@bid, jid)
|
|
94
|
+
Sidekiq::Worker.drain_all
|
|
95
|
+
expect(strategy).to be_locked
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Strategy::UntilAndWhileExecuting do
|
|
4
|
+
include_context 'lock strategy specs', strategy: :until_and_while_executing
|
|
5
|
+
|
|
6
|
+
include_examples "a lock implementation"
|
|
7
|
+
|
|
8
|
+
describe "#on_enqueue" do
|
|
9
|
+
it "locks the queue" do
|
|
10
|
+
process_one.on_enqueue {}
|
|
11
|
+
expect(process_one).to be_locked
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "does not lock the runtime lock" do
|
|
15
|
+
process_one.on_enqueue {}
|
|
16
|
+
expect(process_one.send(:runtime_lock)).to_not be_locked
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#on_perform" do
|
|
21
|
+
it "unlocks the queue" do
|
|
22
|
+
process_one.on_perform do
|
|
23
|
+
expect(process_one).to_not be_locked
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "locks the runtime lock" do
|
|
28
|
+
process_one.on_perform do
|
|
29
|
+
expect(process_one.send(:runtime_lock)).to be_locked
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "unlocks the runtime lock after processing" do
|
|
34
|
+
process_one.on_perform do
|
|
35
|
+
process_one.send(:runtime_lock).batch_callback(:success, nil)
|
|
36
|
+
expect(process_one.send(:runtime_lock)).to_not be_locked
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "prevents other processes from executing" do
|
|
41
|
+
process_one.on_perform do
|
|
42
|
+
expect do
|
|
43
|
+
process_two.on_perform { }
|
|
44
|
+
end.to raise_error(Joblin::Uniqueness::CouldNotLockError)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Strategy::UntilExecuted do
|
|
4
|
+
include_context 'lock strategy specs', strategy: :until_executed
|
|
5
|
+
|
|
6
|
+
include_examples "a lock implementation"
|
|
7
|
+
|
|
8
|
+
describe "#on_enqueue" do
|
|
9
|
+
it "locks the queue" do
|
|
10
|
+
process_one.on_enqueue {}
|
|
11
|
+
expect(process_one).to be_locked
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "#on_perform" do
|
|
16
|
+
it "unlocks after processing" do
|
|
17
|
+
process_one.on_enqueue {}
|
|
18
|
+
process_one.on_perform {}
|
|
19
|
+
process_one.batch_callback(:success, nil)
|
|
20
|
+
expect(process_one).to_not be_locked
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Strategy::UntilExecuting do
|
|
4
|
+
include_context 'lock strategy specs', strategy: :until_executing
|
|
5
|
+
|
|
6
|
+
include_examples "a lock implementation"
|
|
7
|
+
|
|
8
|
+
describe "#on_enqueue" do
|
|
9
|
+
it "locks the queue" do
|
|
10
|
+
process_one.on_enqueue {}
|
|
11
|
+
expect(process_one).to be_locked
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "#on_perform" do
|
|
16
|
+
it "unlocks before processing" do
|
|
17
|
+
process_one.on_enqueue {}
|
|
18
|
+
process_one.on_perform do
|
|
19
|
+
expect(process_one).to_not be_locked
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Strategy::UntilExpired do
|
|
4
|
+
include_context 'lock strategy specs', strategy: :until_expired
|
|
5
|
+
|
|
6
|
+
# include_examples "a lock implementation"
|
|
7
|
+
|
|
8
|
+
describe "#on_enqueue" do
|
|
9
|
+
it "locks the queue" do
|
|
10
|
+
process_one.on_enqueue {}
|
|
11
|
+
expect(process_one).to be_locked
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "#on_perform" do
|
|
16
|
+
it "does not unlock after processing" do
|
|
17
|
+
process_one.on_enqueue {}
|
|
18
|
+
process_one.on_perform {}
|
|
19
|
+
process_one.batch_callback(:success, nil)
|
|
20
|
+
expect(process_one).to be_locked
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'uniqueness/spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Uniqueness::Strategy::WhileExecuting do
|
|
4
|
+
include_context 'lock strategy specs', strategy: :while_executing
|
|
5
|
+
|
|
6
|
+
include_examples "a lock implementation"
|
|
7
|
+
|
|
8
|
+
describe "#on_enqueue" do
|
|
9
|
+
it "does not lock jobs" do
|
|
10
|
+
expect(process_one.on_enqueue {})
|
|
11
|
+
expect(process_one).not_to be_locked
|
|
12
|
+
|
|
13
|
+
expect(process_two.on_enqueue {})
|
|
14
|
+
expect(process_two).not_to be_locked
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "#on_perform" do
|
|
19
|
+
it "locks the process" do
|
|
20
|
+
process_one.on_perform do
|
|
21
|
+
expect(process_one).to be_locked
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "prevents other processes from executing" do
|
|
26
|
+
process_one.on_perform do
|
|
27
|
+
expect do
|
|
28
|
+
process_two.on_perform { }
|
|
29
|
+
end.to raise_error(Joblin::Uniqueness::CouldNotLockError)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|