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,35 @@
|
|
|
1
|
+
module Joblin::Uniqueness
|
|
2
|
+
module Strategy
|
|
3
|
+
# Implements two locks - one while enqueued and one while performing
|
|
4
|
+
class UntilAndWhileExecuting < Base
|
|
5
|
+
locks_on :enqueue, :perform
|
|
6
|
+
|
|
7
|
+
def on_enqueue
|
|
8
|
+
# Obtain lock
|
|
9
|
+
lock!(:enqueue)
|
|
10
|
+
|
|
11
|
+
# Proceed with enqueuing the job, wrapping it in a batch
|
|
12
|
+
runtime_lock.on_enqueue do
|
|
13
|
+
yield
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def on_perform
|
|
18
|
+
# Obtain Runtime lock
|
|
19
|
+
runtime_lock.on_perform do
|
|
20
|
+
# Release Queue lock
|
|
21
|
+
unlock()
|
|
22
|
+
|
|
23
|
+
# Run the job
|
|
24
|
+
yield
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def runtime_lock
|
|
31
|
+
@runtime_lock ||= Strategy::WhileExecuting.new(lock_context)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Joblin::Uniqueness
|
|
2
|
+
module Strategy
|
|
3
|
+
class UntilExecuted < Base
|
|
4
|
+
locks_on :enqueue, :perform
|
|
5
|
+
|
|
6
|
+
def on_enqueue
|
|
7
|
+
lock!(:enqueue)
|
|
8
|
+
|
|
9
|
+
wrap_in_batch do
|
|
10
|
+
yield
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def on_perform
|
|
15
|
+
lock!(:perform)
|
|
16
|
+
yield
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Joblin::Uniqueness
|
|
2
|
+
module Strategy
|
|
3
|
+
class UntilExecuting < Base
|
|
4
|
+
locks_on :enqueue
|
|
5
|
+
|
|
6
|
+
def on_enqueue
|
|
7
|
+
lock!(:enqueue)
|
|
8
|
+
yield
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def on_perform
|
|
12
|
+
unlock
|
|
13
|
+
yield
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# TODO Define behavior when an error occurs during perform().
|
|
17
|
+
# SUJ's behavior is to relock, but this has some edge-cases (like how do we handle if another job already took the lock?)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Joblin::Uniqueness
|
|
2
|
+
module Strategy
|
|
3
|
+
class WhileExecuting < Base
|
|
4
|
+
locks_on :perform
|
|
5
|
+
|
|
6
|
+
RUN_SUFFIX = ":RUN"
|
|
7
|
+
|
|
8
|
+
def on_enqueue
|
|
9
|
+
wrap_in_batch do
|
|
10
|
+
yield
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def on_perform
|
|
15
|
+
lock!(:perform)
|
|
16
|
+
yield
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
def key
|
|
22
|
+
super + RUN_SUFFIX
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Joblin::Uniqueness
|
|
2
|
+
module Strategy
|
|
3
|
+
extend ActiveSupport::Autoload
|
|
4
|
+
|
|
5
|
+
autoload :Base
|
|
6
|
+
|
|
7
|
+
autoload :UntilExpired
|
|
8
|
+
autoload :UntilExecuted
|
|
9
|
+
autoload :UntilExecuting
|
|
10
|
+
autoload :UntilAndWhileExecuting
|
|
11
|
+
autoload :WhileExecuting
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def lookup(strategy)
|
|
15
|
+
matching_strategy(strategy.to_s.camelize) ||
|
|
16
|
+
Joblin::Uniqueness.config.lock_strategies[strategy] ||
|
|
17
|
+
raise(ArgumentError, "strategy: #{strategy} is not found. Is it declared in the configuration?")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def matching_strategy(const)
|
|
23
|
+
const_get(const, false) if const_defined?(const, false)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
|
|
2
|
+
module Joblin::Uniqueness
|
|
3
|
+
module UniqueJobCommon
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
class_attribute :unique_job_options, instance_writer: false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class_methods do
|
|
11
|
+
# ensure_uniqueness(
|
|
12
|
+
# strategy: :until_executed, # :until_executed, :until_executing, :until_expired, :until_and_while_executing, :while_executing
|
|
13
|
+
# on_conflict: :raise, # :raise, :log, :reject, :reschedule, { enqueue: ..., perform: ... }, proc
|
|
14
|
+
# lock_ttl: 7.days, # seconds
|
|
15
|
+
# lock_timeout: 0, # seconds
|
|
16
|
+
|
|
17
|
+
# scope: :per_queue, # :global, :per_queue, string ("<class>-<queue>"), proc
|
|
18
|
+
# hash: ->{ { ... } },
|
|
19
|
+
|
|
20
|
+
# # In the case of UntilExecuted and WhileExecuting, how should the execution lock be released in an error condition?
|
|
21
|
+
# # :any - Release the lock when the Job's (implicit) Batch is :complete
|
|
22
|
+
# # :death - Release the lock when the Job's Batch receives the :death callback
|
|
23
|
+
# # :stagnant - Release the lock when the Job's Batch receives the :stagnant callback
|
|
24
|
+
# # :expire - Do not release the lock until it expires
|
|
25
|
+
# unlock_on_failure: :stagnant,
|
|
26
|
+
# )
|
|
27
|
+
def ensure_uniqueness(**kwargs)
|
|
28
|
+
if self.unique_job_options.present?
|
|
29
|
+
raise ArgumentError, "ensure_uniqueness can only be called once per job class"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
require "sidekiq_unique_jobs"
|
|
34
|
+
rescue LoadError
|
|
35
|
+
raise LoadError, "sidekiq-unique-jobs is required for ensure_uniqueness"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
OnConflict.validate!(kwargs[:on_conflict], kwargs[:strategy]) if kwargs[:on_conflict].present?
|
|
39
|
+
|
|
40
|
+
kwargs[:scope] ||= :per_queue
|
|
41
|
+
kwargs[:ttl] ||= 30.days.to_i
|
|
42
|
+
kwargs[:timeout] ||= 0
|
|
43
|
+
kwargs[:limit] ||= 1
|
|
44
|
+
|
|
45
|
+
self.unique_job_options = kwargs
|
|
46
|
+
|
|
47
|
+
include UniqueJobMethods
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module UniqueJobMethods
|
|
52
|
+
extend ActiveSupport::Concern
|
|
53
|
+
|
|
54
|
+
class_methods do
|
|
55
|
+
def unlock!(jid = nil, args: nil, kwargs: nil, queue: nil)
|
|
56
|
+
queue = self.try(:default_queue_name) || try(:queue)
|
|
57
|
+
raise ArgumentError, "Must specify queue:" unless queue.is_a?(String) || queue.is_a?(Symbol)
|
|
58
|
+
|
|
59
|
+
temp_context = LockContext.new({ job_clazz: self, jid: jid, queue: queue, args: args, kwargs: kwargs })
|
|
60
|
+
strategy = temp_context.lock_strategy
|
|
61
|
+
locksmith = strategy.send(:locksmith)
|
|
62
|
+
|
|
63
|
+
if jid
|
|
64
|
+
locksmith.unlock!()
|
|
65
|
+
else
|
|
66
|
+
locksmith.locked_jids.each do |jid|
|
|
67
|
+
unlock!(jid, args: args, kwargs: kwargs, queue: queue)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# def unlock_all!(queue: :all)
|
|
73
|
+
# # TODO Public API to manually remove all locks for this class
|
|
74
|
+
# end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/joblin.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
require "active_support"
|
|
3
|
+
require "active_support/core_ext"
|
|
4
|
+
require "active_support/lazy_load_hooks"
|
|
5
|
+
|
|
6
|
+
require "rediconn"
|
|
7
|
+
|
|
8
|
+
require "joblin/engine"
|
|
9
|
+
|
|
10
|
+
require "joblin/lazy_access"
|
|
11
|
+
require "joblin/batching/batch"
|
|
12
|
+
require "joblin/uniqueness/job_uniqueness"
|
|
13
|
+
|
|
14
|
+
module Joblin
|
|
15
|
+
Batch = Joblin::Batching::Batch
|
|
16
|
+
|
|
17
|
+
ActiveSupport.on_load(:active_record) do
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def redis_pool
|
|
22
|
+
require 'rediconn'
|
|
23
|
+
@redis_pool ||= RediConn::RedisConnection.create(env_prefix: "BEARCAT")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def redis(&blk)
|
|
27
|
+
redis_pool.lazy_with(&blk)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def logger
|
|
31
|
+
return @logger if defined? @logger
|
|
32
|
+
@logger = Logger.new(STDOUT)
|
|
33
|
+
@logger.level = Logger::WARN
|
|
34
|
+
@logger
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|