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
         |