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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/app/models/joblin/background_task/api_access.rb +148 -0
  4. data/app/models/joblin/background_task/attachments.rb +47 -0
  5. data/app/models/joblin/background_task/executor.rb +63 -0
  6. data/app/models/joblin/background_task/options.rb +75 -0
  7. data/app/models/joblin/background_task/retention_policy.rb +28 -0
  8. data/app/models/joblin/background_task.rb +72 -0
  9. data/app/models/joblin/concerns/job_working_dirs.rb +21 -0
  10. data/db/migrate/20250903184852_create_background_tasks.rb +12 -0
  11. data/joblin.gemspec +35 -0
  12. data/lib/joblin/batching/batch.rb +537 -0
  13. data/lib/joblin/batching/callback.rb +135 -0
  14. data/lib/joblin/batching/chain_builder.rb +247 -0
  15. data/lib/joblin/batching/compat/active_job.rb +108 -0
  16. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/css/styles.less +182 -0
  17. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
  18. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/util.js +2 -0
  19. data/lib/joblin/batching/compat/sidekiq/web/helpers.rb +41 -0
  20. data/lib/joblin/batching/compat/sidekiq/web/views/_batch_tree.erb +6 -0
  21. data/lib/joblin/batching/compat/sidekiq/web/views/_batches_table.erb +44 -0
  22. data/lib/joblin/batching/compat/sidekiq/web/views/_common.erb +13 -0
  23. data/lib/joblin/batching/compat/sidekiq/web/views/_jobs_table.erb +21 -0
  24. data/lib/joblin/batching/compat/sidekiq/web/views/_pagination.erb +26 -0
  25. data/lib/joblin/batching/compat/sidekiq/web/views/batch.erb +81 -0
  26. data/lib/joblin/batching/compat/sidekiq/web/views/batches.erb +23 -0
  27. data/lib/joblin/batching/compat/sidekiq/web/views/pool.erb +137 -0
  28. data/lib/joblin/batching/compat/sidekiq/web/views/pools.erb +47 -0
  29. data/lib/joblin/batching/compat/sidekiq/web.rb +218 -0
  30. data/lib/joblin/batching/compat/sidekiq.rb +149 -0
  31. data/lib/joblin/batching/compat.rb +20 -0
  32. data/lib/joblin/batching/context_hash.rb +157 -0
  33. data/lib/joblin/batching/hier_batch_ids.lua +25 -0
  34. data/lib/joblin/batching/jobs/base_job.rb +7 -0
  35. data/lib/joblin/batching/jobs/concurrent_batch_job.rb +20 -0
  36. data/lib/joblin/batching/jobs/managed_batch_job.rb +175 -0
  37. data/lib/joblin/batching/jobs/serial_batch_job.rb +20 -0
  38. data/lib/joblin/batching/pool.rb +254 -0
  39. data/lib/joblin/batching/pool_refill.lua +47 -0
  40. data/lib/joblin/batching/schedule_callback.lua +14 -0
  41. data/lib/joblin/batching/status.rb +89 -0
  42. data/lib/joblin/engine.rb +15 -0
  43. data/lib/joblin/lazy_access.rb +72 -0
  44. data/lib/joblin/uniqueness/compat/active_job.rb +75 -0
  45. data/lib/joblin/uniqueness/compat/sidekiq.rb +135 -0
  46. data/lib/joblin/uniqueness/compat.rb +20 -0
  47. data/lib/joblin/uniqueness/configuration.rb +25 -0
  48. data/lib/joblin/uniqueness/job_uniqueness.rb +49 -0
  49. data/lib/joblin/uniqueness/lock_context.rb +199 -0
  50. data/lib/joblin/uniqueness/locksmith.rb +92 -0
  51. data/lib/joblin/uniqueness/on_conflict/base.rb +32 -0
  52. data/lib/joblin/uniqueness/on_conflict/log.rb +13 -0
  53. data/lib/joblin/uniqueness/on_conflict/null_strategy.rb +9 -0
  54. data/lib/joblin/uniqueness/on_conflict/raise.rb +11 -0
  55. data/lib/joblin/uniqueness/on_conflict/reject.rb +21 -0
  56. data/lib/joblin/uniqueness/on_conflict/reschedule.rb +20 -0
  57. data/lib/joblin/uniqueness/on_conflict.rb +62 -0
  58. data/lib/joblin/uniqueness/strategy/base.rb +107 -0
  59. data/lib/joblin/uniqueness/strategy/until_and_while_executing.rb +35 -0
  60. data/lib/joblin/uniqueness/strategy/until_executed.rb +20 -0
  61. data/lib/joblin/uniqueness/strategy/until_executing.rb +20 -0
  62. data/lib/joblin/uniqueness/strategy/until_expired.rb +16 -0
  63. data/lib/joblin/uniqueness/strategy/while_executing.rb +26 -0
  64. data/lib/joblin/uniqueness/strategy.rb +27 -0
  65. data/lib/joblin/uniqueness/unique_job_common.rb +79 -0
  66. data/lib/joblin/version.rb +3 -0
  67. data/lib/joblin.rb +37 -0
  68. data/spec/batching/batch_spec.rb +493 -0
  69. data/spec/batching/callback_spec.rb +38 -0
  70. data/spec/batching/compat/active_job_spec.rb +107 -0
  71. data/spec/batching/compat/sidekiq_spec.rb +127 -0
  72. data/spec/batching/context_hash_spec.rb +54 -0
  73. data/spec/batching/flow_spec.rb +82 -0
  74. data/spec/batching/integration/fail_then_succeed.rb +42 -0
  75. data/spec/batching/integration/integration.rb +57 -0
  76. data/spec/batching/integration/nested.rb +88 -0
  77. data/spec/batching/integration/simple.rb +47 -0
  78. data/spec/batching/integration/workflow.rb +134 -0
  79. data/spec/batching/integration_helper.rb +50 -0
  80. data/spec/batching/pool_spec.rb +161 -0
  81. data/spec/batching/status_spec.rb +76 -0
  82. data/spec/batching/support/base_job.rb +19 -0
  83. data/spec/batching/support/sample_callback.rb +2 -0
  84. data/spec/internal/config/database.yml +5 -0
  85. data/spec/internal/config/routes.rb +5 -0
  86. data/spec/internal/config/storage.yml +3 -0
  87. data/spec/internal/db/combustion_test.sqlite +0 -0
  88. data/spec/internal/db/schema.rb +6 -0
  89. data/spec/internal/log/test.log +48200 -0
  90. data/spec/internal/public/favicon.ico +0 -0
  91. data/spec/models/background_task_spec.rb +41 -0
  92. data/spec/spec_helper.rb +29 -0
  93. data/spec/uniqueness/compat/active_job_spec.rb +49 -0
  94. data/spec/uniqueness/compat/sidekiq_spec.rb +68 -0
  95. data/spec/uniqueness/lock_context_spec.rb +106 -0
  96. data/spec/uniqueness/on_conflict/log_spec.rb +11 -0
  97. data/spec/uniqueness/on_conflict/raise_spec.rb +10 -0
  98. data/spec/uniqueness/on_conflict/reschedule_spec.rb +63 -0
  99. data/spec/uniqueness/on_conflict_spec.rb +16 -0
  100. data/spec/uniqueness/spec_helper.rb +19 -0
  101. data/spec/uniqueness/strategy/base_spec.rb +100 -0
  102. data/spec/uniqueness/strategy/until_and_while_executing_spec.rb +48 -0
  103. data/spec/uniqueness/strategy/until_executed_spec.rb +23 -0
  104. data/spec/uniqueness/strategy/until_executing_spec.rb +23 -0
  105. data/spec/uniqueness/strategy/until_expired_spec.rb +23 -0
  106. data/spec/uniqueness/strategy/while_executing_spec.rb +33 -0
  107. data/spec/uniqueness/support/lock_strategy.rb +28 -0
  108. data/spec/uniqueness/support/on_conflict.rb +24 -0
  109. data/spec/uniqueness/support/test_worker.rb +19 -0
  110. data/spec/uniqueness/unique_job_common_spec.rb +45 -0
  111. 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
@@ -0,0 +1,2 @@
1
+ class SampleCallback; end
2
+ class SampleCallback2; end
@@ -0,0 +1,5 @@
1
+ test:
2
+ database: joblin-test
3
+ adapter: postgresql
4
+ encoding: unicode
5
+ pool: 15
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ # Add your own routes here, or remove this file if you don't have need for it.
5
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ service: Disk
3
+ root: tmp/storage
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define do
4
+ # Set up any tables you need to exist for your test suite that don't belong
5
+ # in migrations.
6
+ end