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,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,16 @@
1
+ module Joblin::Uniqueness
2
+ module Strategy
3
+ class UntilExpired < UntilExecuted
4
+ locks_on :enqueue, :perform
5
+
6
+ def on_enqueue
7
+ lock!(:enqueue)
8
+ yield
9
+ end
10
+
11
+ def on_perform
12
+ lock!(:perform)
13
+ end
14
+ end
15
+ end
16
+ 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
@@ -0,0 +1,3 @@
1
+ module Joblin
2
+ VERSION = "0.1.0".freeze
3
+ 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