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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Batching::Pool do
|
|
4
|
+
include ActiveJob::TestHelper
|
|
5
|
+
|
|
6
|
+
subject { described_class.new(order: pool_order, concurrency: pool_concurrency) }
|
|
7
|
+
|
|
8
|
+
let(:pool) { subject }
|
|
9
|
+
|
|
10
|
+
let(:pool_order) { :fifo }
|
|
11
|
+
let(:pool_concurrency) { 2 }
|
|
12
|
+
|
|
13
|
+
describe '#initialize' do
|
|
14
|
+
subject { described_class }
|
|
15
|
+
|
|
16
|
+
it 'creates pid when called without it' do
|
|
17
|
+
expect(subject.new.pid).not_to be_nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'reuses pid when called with it' do
|
|
21
|
+
batch = subject.new('dayPO5KxuRXXxw')
|
|
22
|
+
expect(batch.pid).to eq('dayPO5KxuRXXxw')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load_pool(count = 3)
|
|
27
|
+
jobs = count.times.map do |i|
|
|
28
|
+
{ job: "BatchTestJobBase", args: [1] }
|
|
29
|
+
end
|
|
30
|
+
subject.add_jobs(jobs, skip_refill: true)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "#job_checked_in" do
|
|
34
|
+
it "gets called" do
|
|
35
|
+
expect(Joblin::Batching::Pool).to receive(:job_checked_in).twice
|
|
36
|
+
load_pool
|
|
37
|
+
perform_enqueued_jobs do
|
|
38
|
+
pool.send :refill_allotment
|
|
39
|
+
Sidekiq::Worker.drain_all
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "refills the pool" do
|
|
44
|
+
expect(Joblin::Batching::Pool).to receive(:job_checked_in).and_call_original.exactly(3).times
|
|
45
|
+
|
|
46
|
+
load_pool
|
|
47
|
+
perform_enqueued_jobs do
|
|
48
|
+
pool.send :refill_allotment
|
|
49
|
+
Sidekiq::Worker.drain_all
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "#cleanup_if_empty" do
|
|
55
|
+
it "cleans if pool is empty and allowed to close" do
|
|
56
|
+
expect(pool).to receive(:cleanup_redis)
|
|
57
|
+
pool.cleanup_if_empty
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "doesn't clean if pool has pending" do
|
|
61
|
+
load_pool
|
|
62
|
+
expect(pool).to_not receive(:cleanup_redis)
|
|
63
|
+
pool.cleanup_if_empty
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "doesn't clean if pool has active" do
|
|
67
|
+
subject.redis.hset("#{subject.send(:redis_key)}-active", "blocked", "{}")
|
|
68
|
+
expect(pool).to_not receive(:cleanup_redis)
|
|
69
|
+
pool.cleanup_if_empty
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "doesn't clean if pool has activating" do
|
|
73
|
+
subject.redis.hincrby(subject.send(:redis_key), "_active_count", 1)
|
|
74
|
+
expect(pool).to_not receive(:cleanup_redis)
|
|
75
|
+
pool.cleanup_if_empty
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "doesn't clean if pool is empty but is kept open" do
|
|
79
|
+
pool.keep_open!
|
|
80
|
+
expect(pool).to_not receive(:cleanup_redis)
|
|
81
|
+
pool.cleanup_if_empty
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "doesn't clean if pool is empty but clean_when_empty is false" do
|
|
85
|
+
subject.redis.hset(subject.send(:redis_key), "clean_when_empty", "false")
|
|
86
|
+
expect(pool).to_not receive(:cleanup_redis)
|
|
87
|
+
pool.cleanup_if_empty
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
shared_examples "basic pool tests" do
|
|
92
|
+
describe "#push_job_to_pool" do
|
|
93
|
+
it "adds a job to the pool" do
|
|
94
|
+
subject.send(:push_job_to_pool, { job: 'job1' })
|
|
95
|
+
expect(subject.pending_count).to eq(1)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe "#refill_allotment" do
|
|
100
|
+
it "refills the pool with jobs" do
|
|
101
|
+
load_pool
|
|
102
|
+
expect(Joblin::Batching::ChainBuilder).to receive(:enqueue_job).twice
|
|
103
|
+
subject.send(:refill_allotment)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "limits to the concurrency count" do
|
|
107
|
+
load_pool
|
|
108
|
+
expect(Joblin::Batching::ChainBuilder).to receive(:enqueue_job).twice
|
|
109
|
+
expect(subject.send(:refill_allotment)).to eql 2
|
|
110
|
+
expect(subject.pending_count).to eql 1
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "considers already active jobs against concurrency" do
|
|
114
|
+
load_pool
|
|
115
|
+
subject.redis.hset("#{subject.send(:redis_key)}-active", "blocked", "{}")
|
|
116
|
+
expect(Joblin::Batching::ChainBuilder).to receive(:enqueue_job).once
|
|
117
|
+
expect(subject.send(:refill_allotment)).to eql 2
|
|
118
|
+
expect(subject.pending_count).to eql 2
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "considers activating jobs against concurrency" do
|
|
122
|
+
load_pool
|
|
123
|
+
subject.redis.hincrby(subject.send(:redis_key), "_active_count", 1)
|
|
124
|
+
expect(Joblin::Batching::ChainBuilder).to receive(:enqueue_job).once
|
|
125
|
+
expect(subject.send(:refill_allotment)).to eql 2
|
|
126
|
+
expect(subject.pending_count).to eql 2
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "doesn't fail if the pool is gone" do
|
|
130
|
+
load_pool
|
|
131
|
+
subject.cleanup_redis
|
|
132
|
+
expect(Joblin::Batching::ChainBuilder).not_to receive(:enqueue_job)
|
|
133
|
+
expect(subject.send(:refill_allotment)).to eql -1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "doesn't fail if the pool is empty" do
|
|
137
|
+
expect(subject.send(:refill_allotment)).to eql 0
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context "FIFO Pool" do
|
|
143
|
+
let(:pool_order) { :fifo }
|
|
144
|
+
it_behaves_like "basic pool tests"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
context "LIFO Pool" do
|
|
148
|
+
let(:pool_order) { :lifo }
|
|
149
|
+
it_behaves_like "basic pool tests"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context "Random Pool" do
|
|
153
|
+
let(:pool_order) { :random }
|
|
154
|
+
it_behaves_like "basic pool tests"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
context "Priority Pool" do
|
|
158
|
+
let(:pool_order) { :priority }
|
|
159
|
+
it_behaves_like "basic pool tests"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Joblin::Batching::Batch::Status do
|
|
4
|
+
let(:batch) { Joblin::Batching::Batch.new() }
|
|
5
|
+
let(:bid) { batch.bid }
|
|
6
|
+
subject { described_class.new(bid) }
|
|
7
|
+
|
|
8
|
+
describe '#join' do
|
|
9
|
+
it 'raises info' do
|
|
10
|
+
expect { subject.join }.to raise_error('Not supported')
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#pending' do
|
|
15
|
+
context 'when not initalized' do
|
|
16
|
+
it 'returns 0 pending jobs' do
|
|
17
|
+
expect(subject.pending).to eq(0)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context 'when more than 0' do
|
|
22
|
+
before { batch.jobs do BatchTestWorker.perform_async end }
|
|
23
|
+
it 'returns pending jobs' do
|
|
24
|
+
expect(subject.pending).to eq(1)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#failures' do
|
|
30
|
+
context 'when not initalized' do
|
|
31
|
+
it 'returns 0 failed jobs' do
|
|
32
|
+
expect(subject.failures).to eq(0)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'when more than 0' do
|
|
37
|
+
before { batch.append_jobs(bid) }
|
|
38
|
+
before { Joblin::Batching::Batch.process_failed_job(bid, 'FAILEDID') }
|
|
39
|
+
|
|
40
|
+
it 'returns failed jobs' do
|
|
41
|
+
expect(subject.failures).to eq(1)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '#failure_info' do
|
|
47
|
+
context 'when not initalized' do
|
|
48
|
+
it 'returns empty array' do
|
|
49
|
+
expect(subject.failure_info).to eq([])
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'when with error' do
|
|
54
|
+
before { batch.jobs{}; Joblin::Batching::Batch.process_failed_job(bid, 'jid123') }
|
|
55
|
+
|
|
56
|
+
it 'returns array with failed jids' do
|
|
57
|
+
expect(subject.failure_info).to eq(['jid123'])
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe '#data' do
|
|
63
|
+
it 'returns batch description' do
|
|
64
|
+
expect(subject.data).to include(failures: 0, pending: 0, created_at: nil, complete: false, failure_info: [], parent_bid: nil)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#created_at' do
|
|
69
|
+
it 'returns time' do
|
|
70
|
+
batch = Joblin::Batching::Batch.new
|
|
71
|
+
batch.jobs do BatchTestWorker.perform_async end
|
|
72
|
+
status = described_class.new(batch.bid)
|
|
73
|
+
expect(status.created_at).not_to be_nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class BatchTestJobBase < ActiveJob::Base
|
|
2
|
+
def perform(*args, **kwargs)
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
def self.perform_async(*args)
|
|
6
|
+
perform_later(*args)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class FailingBatchTestJobBase < BatchTestJobBase
|
|
11
|
+
def perform
|
|
12
|
+
raise "Foo"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class BatchTestWorker < BatchTestJobBase
|
|
17
|
+
def perform
|
|
18
|
+
end
|
|
19
|
+
end
|
|
Binary file
|