inst-jobs 2.0.0 → 2.2.1
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 +4 -4
- data/lib/delayed/backend/base.rb +9 -1
- data/lib/delayed/lifecycle.rb +1 -0
- data/lib/delayed/periodic.rb +1 -4
- data/lib/delayed/pool.rb +31 -0
- data/lib/delayed/settings.rb +1 -0
- data/lib/delayed/version.rb +1 -1
- data/lib/delayed/worker/consul_health_check.rb +17 -20
- data/lib/delayed/worker/health_check.rb +7 -6
- data/lib/delayed/worker.rb +46 -25
- data/spec/delayed/periodic_spec.rb +1 -9
- data/spec/delayed/worker/consul_health_check_spec.rb +23 -36
- data/spec/delayed/worker_spec.rb +23 -3
- data/spec/spec_helper.rb +3 -0
- metadata +30 -16
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9550c353f31dc1d34a15993bdfbf279d87979ebb625ae776fb6727d7dc9e897e
         | 
| 4 | 
            +
              data.tar.gz: 379f8faac5d3369551b4012a00f522d7871a4001776120c4e02535b70bdef8a3
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 4dbb7a9366256f16a0fd7fbf22938d94c7754cdd3d9189d3d7d94e0d8deda59859ec4822f1e3c1d05cc7a2a67c8501a79c83d7b0b69f91fd106d9b24b4f35b06
         | 
| 7 | 
            +
              data.tar.gz: 637e09a985feeef6144e5b172a04c8ceee5980eed3b1b49f2597e33bbf26522c43244bc2778905ba400cf0de6485166258103e09aa7a765e261f8971b89b2c06
         | 
    
        data/lib/delayed/backend/base.rb
    CHANGED
    
    | @@ -87,6 +87,10 @@ module Delayed | |
| 87 87 | 
             
                        batches[batch_enqueue_args] << kwargs
         | 
| 88 88 | 
             
                        return true
         | 
| 89 89 | 
             
                      else
         | 
| 90 | 
            +
                        if kwargs[:on_conflict].present?
         | 
| 91 | 
            +
                          Delayed::Logging.logger.warn("[DELAYED_JOB] WARNING: providing 'on_conflict' as an option to a non-singleton job will have no effect.  Discarding.")
         | 
| 92 | 
            +
                          kwargs.delete(:on_conflict)
         | 
| 93 | 
            +
                        end
         | 
| 90 94 | 
             
                        job = self.create(**kwargs)
         | 
| 91 95 | 
             
                      end
         | 
| 92 96 |  | 
| @@ -178,6 +182,10 @@ module Delayed | |
| 178 182 | 
             
                    expires_at && (self.class.db_time_now >= expires_at)
         | 
| 179 183 | 
             
                  end
         | 
| 180 184 |  | 
| 185 | 
            +
                  def inferred_max_attempts
         | 
| 186 | 
            +
                    self.max_attempts || Delayed::Settings.max_attempts
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
             | 
| 181 189 | 
             
                  # Reschedule the job in the future (when a job fails).
         | 
| 182 190 | 
             
                  # Uses an exponential scale depending on the number of failed attempts.
         | 
| 183 191 | 
             
                  def reschedule(error = nil, time = nil)
         | 
| @@ -190,7 +198,7 @@ module Delayed | |
| 190 198 |  | 
| 191 199 | 
             
                    self.attempts += 1 unless return_code == :unlock
         | 
| 192 200 |  | 
| 193 | 
            -
                    if self.attempts >=  | 
| 201 | 
            +
                    if self.attempts >= self.inferred_max_attempts
         | 
| 194 202 | 
             
                      permanent_failure error || "max attempts reached"
         | 
| 195 203 | 
             
                    elsif expired?
         | 
| 196 204 | 
             
                      permanent_failure error || "job has expired"
         | 
    
        data/lib/delayed/lifecycle.rb
    CHANGED
    
    
    
        data/lib/delayed/periodic.rb
    CHANGED
    
    | @@ -56,10 +56,7 @@ class Periodic | |
| 56 56 | 
             
                inferred_args = {
         | 
| 57 57 | 
             
                  max_attempts: 1,
         | 
| 58 58 | 
             
                  run_at: @cron.next_time(Delayed::Periodic.now).utc.to_time,
         | 
| 59 | 
            -
                  singleton:  | 
| 60 | 
            -
                  # yes, checking for whether it is actually the boolean literal false,
         | 
| 61 | 
            -
                  # which means the consuming code really does not want this job to be
         | 
| 62 | 
            -
                  # a singleton at all.
         | 
| 59 | 
            +
                  singleton: tag,
         | 
| 63 60 | 
             
                  on_conflict: :patient
         | 
| 64 61 | 
             
                }
         | 
| 65 62 | 
             
                @job_args.merge(inferred_args)
         | 
    
        data/lib/delayed/pool.rb
    CHANGED
    
    | @@ -39,6 +39,7 @@ class Pool | |
| 39 39 | 
             
                Process.wait unlock_pid
         | 
| 40 40 |  | 
| 41 41 | 
             
                spawn_periodic_auditor
         | 
| 42 | 
            +
                spawn_abandoned_job_cleanup
         | 
| 42 43 | 
             
                spawn_all_workers
         | 
| 43 44 | 
             
                say "Workers spawned"
         | 
| 44 45 | 
             
                join
         | 
| @@ -111,6 +112,34 @@ class Pool | |
| 111 112 | 
             
                end
         | 
| 112 113 | 
             
              end
         | 
| 113 114 |  | 
| 115 | 
            +
              def spawn_abandoned_job_cleanup
         | 
| 116 | 
            +
                return if Settings.disable_abandoned_job_cleanup
         | 
| 117 | 
            +
                cleanup_interval_in_minutes = 60
         | 
| 118 | 
            +
                @abandoned_cleanup_thread = Thread.new do
         | 
| 119 | 
            +
                  # every hour (staggered by process)
         | 
| 120 | 
            +
                  # check for dead jobs and cull them.
         | 
| 121 | 
            +
                  # Will actually be more often based on the
         | 
| 122 | 
            +
                  # number of worker nodes in the pool.  This will actually
         | 
| 123 | 
            +
                  # be a max of N times per hour where N is the number of workers,
         | 
| 124 | 
            +
                  # but they won't overrun each other because the health check
         | 
| 125 | 
            +
                  # takes an advisory lock internally
         | 
| 126 | 
            +
                  sleep(rand(cleanup_interval_in_minutes * 60))
         | 
| 127 | 
            +
                  loop do
         | 
| 128 | 
            +
                    schedule_abandoned_job_cleanup
         | 
| 129 | 
            +
                    sleep(cleanup_interval_in_minutes * 60)
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
                end
         | 
| 132 | 
            +
              end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
              def schedule_abandoned_job_cleanup
         | 
| 135 | 
            +
                pid = fork_with_reconnects do
         | 
| 136 | 
            +
                  # we want to avoid db connections in the main pool process
         | 
| 137 | 
            +
                  $0 = "delayed_abandoned_job_cleanup"
         | 
| 138 | 
            +
                  Delayed::Worker::HealthCheck.reschedule_abandoned_jobs
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
                workers[pid] = :abandoned_job_cleanup
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
             | 
| 114 143 | 
             
              def spawn_periodic_auditor
         | 
| 115 144 | 
             
                return if Settings.disable_periodic_jobs
         | 
| 116 145 |  | 
| @@ -217,6 +246,8 @@ class Pool | |
| 217 246 | 
             
                  case worker
         | 
| 218 247 | 
             
                  when :periodic_audit
         | 
| 219 248 | 
             
                    say "ran auditor: #{worker}"
         | 
| 249 | 
            +
                  when :abandoned_job_cleanup
         | 
| 250 | 
            +
                    say "ran cleanup: #{worker}"
         | 
| 220 251 | 
             
                  when :work_queue
         | 
| 221 252 | 
             
                    say "work queue exited, restarting", :info
         | 
| 222 253 | 
             
                    spawn_work_queue
         | 
    
        data/lib/delayed/settings.rb
    CHANGED
    
    
    
        data/lib/delayed/version.rb
    CHANGED
    
    
| @@ -9,52 +9,49 @@ module Delayed | |
| 9 9 | 
             
                class ConsulHealthCheck < HealthCheck
         | 
| 10 10 | 
             
                  self.type_name = :consul
         | 
| 11 11 |  | 
| 12 | 
            -
                  CONSUL_CONFIG_KEYS = %w{url  | 
| 12 | 
            +
                  CONSUL_CONFIG_KEYS = %w{url acl_token}.map(&:freeze).freeze
         | 
| 13 13 | 
             
                  DEFAULT_SERVICE_NAME = 'inst-jobs_worker'.freeze
         | 
| 14 | 
            -
                  attr_reader : | 
| 14 | 
            +
                  attr_reader :service_client, :health_client
         | 
| 15 15 |  | 
| 16 16 | 
             
                  def initialize(*, **)
         | 
| 17 17 | 
             
                    super
         | 
| 18 18 | 
             
                    # Because we don't want the consul client to be a hard dependency we're
         | 
| 19 19 | 
             
                    # only requiring it once it's absolutely needed
         | 
| 20 | 
            -
                    require ' | 
| 20 | 
            +
                    require 'diplomat'
         | 
| 21 21 |  | 
| 22 22 | 
             
                    if config.keys.any? { |k| CONSUL_CONFIG_KEYS.include?(k) }
         | 
| 23 | 
            -
                      consul_config =  | 
| 23 | 
            +
                      consul_config = Diplomat::Configuration.new.tap do |conf|
         | 
| 24 24 | 
             
                        CONSUL_CONFIG_KEYS.each do |key|
         | 
| 25 25 | 
             
                          conf.send("#{key}=", config[key]) if config[key]
         | 
| 26 26 | 
             
                        end
         | 
| 27 27 | 
             
                      end
         | 
| 28 | 
            -
                      @ | 
| 29 | 
            -
                      @ | 
| 28 | 
            +
                      @service_client = Diplomat::Service.new(configuration: consul_config)
         | 
| 29 | 
            +
                      @health_client = Diplomat::Health.new(configuration: consul_config)
         | 
| 30 30 | 
             
                    else
         | 
| 31 | 
            -
                      @ | 
| 32 | 
            -
                      @ | 
| 31 | 
            +
                      @service_client = Diplomat::Service.new
         | 
| 32 | 
            +
                      @health_client = Diplomat::Health.new
         | 
| 33 33 | 
             
                    end
         | 
| 34 34 | 
             
                  end
         | 
| 35 35 |  | 
| 36 36 | 
             
                  def start
         | 
| 37 | 
            -
                     | 
| 37 | 
            +
                    @service_client.register({
         | 
| 38 38 | 
             
                      id: worker_name,
         | 
| 39 39 | 
             
                      name: service_name,
         | 
| 40 | 
            +
                      check: check_attributes
         | 
| 40 41 | 
             
                    })
         | 
| 41 | 
            -
                    service.add_check(check_attributes)
         | 
| 42 | 
            -
                    response = @agent_client.register_service(service)
         | 
| 43 | 
            -
                    response.ok?
         | 
| 44 42 | 
             
                  end
         | 
| 45 43 |  | 
| 46 44 | 
             
                  def stop
         | 
| 47 | 
            -
                     | 
| 48 | 
            -
                    response.ok? || response.not_found?
         | 
| 45 | 
            +
                    @service_client.deregister(worker_name)
         | 
| 49 46 | 
             
                  end
         | 
| 50 47 |  | 
| 51 48 | 
             
                  def live_workers
         | 
| 52 | 
            -
                     | 
| 53 | 
            -
                     | 
| 54 | 
            -
                       | 
| 55 | 
            -
                     | 
| 56 | 
            -
             | 
| 57 | 
            -
                     | 
| 49 | 
            +
                    # Filter out critical workers (probably nodes failing their serf health check)
         | 
| 50 | 
            +
                    live_nodes = @health_client.service(service_name, {
         | 
| 51 | 
            +
                      filter: 'not Checks.Status == critical'
         | 
| 52 | 
            +
                    })
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    live_nodes.map { |n| n.Service['ID']}
         | 
| 58 55 | 
             
                  end
         | 
| 59 56 |  | 
| 60 57 | 
             
                  private
         | 
| @@ -23,12 +23,13 @@ module Delayed | |
| 23 23 | 
             
                    def reschedule_abandoned_jobs
         | 
| 24 24 | 
             
                      return if Settings.worker_health_check_type == :none
         | 
| 25 25 | 
             
                      Delayed::Job.transaction do
         | 
| 26 | 
            -
                        # this  | 
| 26 | 
            +
                        # this action is a special case, and SHOULD NOT be a periodic job
         | 
| 27 27 | 
             
                        # because if it gets wiped out suddenly during execution
         | 
| 28 28 | 
             
                        # it can't go clean up it's abandoned self.  Therefore,
         | 
| 29 | 
            -
                        # we  | 
| 30 | 
            -
                        #  | 
| 31 | 
            -
                        #  | 
| 29 | 
            +
                        # we expect it to get run from it's own process forked from the job pool
         | 
| 30 | 
            +
                        # and we try to get an advisory lock when it runs.  If we succeed,
         | 
| 31 | 
            +
                        # no other worker is trying to do this right now (and if we abandon the
         | 
| 32 | 
            +
                        # operation, the transaction will end, releasing the advisory lock).
         | 
| 32 33 | 
             
                        result = attempt_advisory_lock
         | 
| 33 34 | 
             
                        return unless result
         | 
| 34 35 | 
             
                        checker = Worker::HealthCheck.build(
         | 
| @@ -59,8 +60,8 @@ module Delayed | |
| 59 60 |  | 
| 60 61 | 
             
                    def attempt_advisory_lock
         | 
| 61 62 | 
             
                      lock_name = "Delayed::Worker::HealthCheck#reschedule_abandoned_jobs"
         | 
| 62 | 
            -
                       | 
| 63 | 
            -
                       | 
| 63 | 
            +
                      conn = ActiveRecord::Base.connection
         | 
| 64 | 
            +
                      conn.select_value("SELECT pg_try_advisory_xact_lock(#{conn.quote_table_name('half_md5_as_bigint')}('#{lock_name}'));")
         | 
| 64 65 | 
             
                    end
         | 
| 65 66 | 
             
                  end
         | 
| 66 67 |  | 
    
        data/lib/delayed/worker.rb
    CHANGED
    
    | @@ -3,6 +3,17 @@ | |
| 3 3 | 
             
            module Delayed
         | 
| 4 4 |  | 
| 5 5 | 
             
            class TimeoutError < RuntimeError; end
         | 
| 6 | 
            +
            class RetriableError < RuntimeError
         | 
| 7 | 
            +
              # this error is a special case.  You _should_ raise
         | 
| 8 | 
            +
              # it from inside the rescue block for another error, 
         | 
| 9 | 
            +
              # because it indicates: "something made this job fail
         | 
| 10 | 
            +
              # but we're pretty sure it's transient and it's safe to try again".
         | 
| 11 | 
            +
              # the workflow is still the same (retry will happen unless
         | 
| 12 | 
            +
              # retries are exhausted), but it won't call the :error
         | 
| 13 | 
            +
              # callback unless it can't retry anymore.  It WILL call the
         | 
| 14 | 
            +
              # separate ":retry" callback, which is ONLY activated
         | 
| 15 | 
            +
              # for this kind of error.
         | 
| 16 | 
            +
            end
         | 
| 6 17 |  | 
| 7 18 | 
             
            require 'tmpdir'
         | 
| 8 19 | 
             
            require 'set'
         | 
| @@ -94,7 +105,11 @@ class Worker | |
| 94 105 | 
             
              end
         | 
| 95 106 |  | 
| 96 107 | 
             
              def exit?
         | 
| 97 | 
            -
                 | 
| 108 | 
            +
                !!@exit || parent_exited?
         | 
| 109 | 
            +
              end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
              def parent_exited?
         | 
| 112 | 
            +
                @parent_pid && @parent_pid != Process.ppid
         | 
| 98 113 | 
             
              end
         | 
| 99 114 |  | 
| 100 115 | 
             
              def wake_up
         | 
| @@ -198,32 +213,38 @@ class Worker | |
| 198 213 | 
             
              end
         | 
| 199 214 |  | 
| 200 215 | 
             
              def perform(job)
         | 
| 201 | 
            -
                 | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
                   | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
                     | 
| 208 | 
            -
                       | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
                       | 
| 216 | 
            +
                begin
         | 
| 217 | 
            +
                  count = 1
         | 
| 218 | 
            +
                  raise Delayed::Backend::JobExpired, "job expired at #{job.expires_at}" if job.expired?
         | 
| 219 | 
            +
                  self.class.lifecycle.run_callbacks(:perform, self, job) do
         | 
| 220 | 
            +
                    set_process_name("run:#{Settings.worker_procname_prefix}#{job.id}:#{job.name}")
         | 
| 221 | 
            +
                    logger.info("Processing #{log_job(job, :long)}")
         | 
| 222 | 
            +
                    runtime = Benchmark.realtime do
         | 
| 223 | 
            +
                      if job.batch?
         | 
| 224 | 
            +
                        # each job in the batch will have perform called on it, so we don't
         | 
| 225 | 
            +
                        # need a timeout around this
         | 
| 226 | 
            +
                        count = perform_batch(job)
         | 
| 227 | 
            +
                      else
         | 
| 228 | 
            +
                        job.invoke_job
         | 
| 229 | 
            +
                      end
         | 
| 230 | 
            +
                      job.destroy
         | 
| 213 231 | 
             
                    end
         | 
| 214 | 
            -
                    job | 
| 232 | 
            +
                    logger.info("Completed #{log_job(job)} #{"%.0fms" % (runtime * 1000)}")
         | 
| 233 | 
            +
                  end
         | 
| 234 | 
            +
                rescue ::Delayed::RetriableError => re
         | 
| 235 | 
            +
                  can_retry = job.attempts + 1 < job.inferred_max_attempts
         | 
| 236 | 
            +
                  callback_type = can_retry ? :retry : :error
         | 
| 237 | 
            +
                  self.class.lifecycle.run_callbacks(callback_type, self, job, re) do
         | 
| 238 | 
            +
                    handle_failed_job(job, re)
         | 
| 239 | 
            +
                  end
         | 
| 240 | 
            +
                rescue SystemExit => se
         | 
| 241 | 
            +
                  # There wasn't really a failure here so no callbacks and whatnot needed,
         | 
| 242 | 
            +
                  # still reschedule the job though.
         | 
| 243 | 
            +
                  job.reschedule(se)
         | 
| 244 | 
            +
                rescue Exception => e
         | 
| 245 | 
            +
                  self.class.lifecycle.run_callbacks(:error, self, job, e) do
         | 
| 246 | 
            +
                    handle_failed_job(job, e)
         | 
| 215 247 | 
             
                  end
         | 
| 216 | 
            -
                  logger.info("Completed #{log_job(job)} #{"%.0fms" % (runtime * 1000)}")
         | 
| 217 | 
            -
                end
         | 
| 218 | 
            -
                count
         | 
| 219 | 
            -
              rescue SystemExit => se
         | 
| 220 | 
            -
                # There wasn't really a failure here so no callbacks and whatnot needed,
         | 
| 221 | 
            -
                # still reschedule the job though.
         | 
| 222 | 
            -
                job.reschedule(se)
         | 
| 223 | 
            -
                count
         | 
| 224 | 
            -
              rescue Exception => e
         | 
| 225 | 
            -
                self.class.lifecycle.run_callbacks(:error, self, job, e) do
         | 
| 226 | 
            -
                  handle_failed_job(job, e)
         | 
| 227 248 | 
             
                end
         | 
| 228 249 | 
             
                count
         | 
| 229 250 | 
             
              end
         | 
| @@ -14,6 +14,7 @@ RSpec.describe Delayed::Periodic do | |
| 14 14 | 
             
              ensure
         | 
| 15 15 | 
             
                Delayed::Periodic.scheduled = prev_sched
         | 
| 16 16 | 
             
                Delayed::Periodic.overrides = prev_ovr
         | 
| 17 | 
            +
                Delayed::Job.delete_all
         | 
| 17 18 | 
             
              end
         | 
| 18 19 |  | 
| 19 20 | 
             
              describe ".cron" do
         | 
| @@ -26,14 +27,5 @@ RSpec.describe Delayed::Periodic do | |
| 26 27 | 
             
                  expect(instance).to_not be_nil
         | 
| 27 28 | 
             
                  expect(instance.enqueue_args[:singleton]).to eq("periodic: just a test")
         | 
| 28 29 | 
             
                end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                it "uses no singleton if told to skip" do
         | 
| 31 | 
            -
                  Delayed::Periodic.cron job_name, '*/10 * * * *', {singleton: false} do
         | 
| 32 | 
            -
                    # no-op
         | 
| 33 | 
            -
                  end
         | 
| 34 | 
            -
                  instance = Delayed::Periodic.scheduled[job_name]
         | 
| 35 | 
            -
                  expect(instance).to_not be_nil
         | 
| 36 | 
            -
                  expect(instance.enqueue_args[:singleton]).to be_nil
         | 
| 37 | 
            -
                end
         | 
| 38 30 | 
             
              end
         | 
| 39 31 | 
             
            end
         | 
| @@ -1,76 +1,63 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'spec_helper'
         | 
| 4 | 
            -
            require 'imperium'
         | 
| 5 4 |  | 
| 6 5 | 
             
            RSpec.describe Delayed::Worker::ConsulHealthCheck do
         | 
| 7 6 | 
             
              let(:health_check) { Delayed::Worker::ConsulHealthCheck.new(worker_name: 'foobar') }
         | 
| 8 7 |  | 
| 9 | 
            -
              # can't use a verifying double for the response because the methods we're
         | 
| 10 | 
            -
              # tryig to stub are actually on HTTP::Message
         | 
| 11 | 
            -
              let(:response) { double('Imperium::Response') }
         | 
| 12 | 
            -
              let(:agent_client) { instance_double(Imperium::Agent) }
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              before do
         | 
| 15 | 
            -
                allow(Imperium::Agent).to receive(:default_client).and_return(agent_client)
         | 
| 16 | 
            -
              end
         | 
| 17 | 
            -
             | 
| 18 8 | 
             
              describe '#initialize' do
         | 
| 19 | 
            -
                it 'must use  | 
| 9 | 
            +
                it 'must use a default service client when the config is mostly empty' do
         | 
| 20 10 | 
             
                  check = Delayed::Worker::ConsulHealthCheck.new(worker_name: 'foobar')
         | 
| 21 | 
            -
                  expect(check. | 
| 11 | 
            +
                  expect(check.service_client.configuration.url.to_s).to eq 'http://localhost:8500'
         | 
| 22 12 | 
             
                end
         | 
| 23 13 |  | 
| 24 | 
            -
                it 'must create a new  | 
| 14 | 
            +
                it 'must create a new service API client when the config has relevant keys set' do
         | 
| 25 15 | 
             
                  check = Delayed::Worker::ConsulHealthCheck.new(worker_name: 'foobar', config: {url: 'http://consul.example.com:8500'})
         | 
| 26 | 
            -
                   | 
| 27 | 
            -
                  expect( | 
| 28 | 
            -
                  expect(agent_client.config.url.to_s).to eq 'http://consul.example.com:8500'
         | 
| 16 | 
            +
                  service_client = check.service_client
         | 
| 17 | 
            +
                  expect(service_client.configuration.url.to_s).to eq 'http://consul.example.com:8500'
         | 
| 29 18 | 
             
                end
         | 
| 30 19 | 
             
              end
         | 
| 31 20 |  | 
| 32 21 | 
             
              describe '#start' do
         | 
| 33 22 | 
             
                it 'must register this process as a service with consul' do
         | 
| 34 | 
            -
                   | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
                    .and_return(response)
         | 
| 23 | 
            +
                  stub = stub_request(:put, "localhost:8500/v1/agent/service/register")
         | 
| 24 | 
            +
                    .with(body: hash_including({id: 'foobar' }))
         | 
| 25 | 
            +
             | 
| 38 26 | 
             
                  health_check.start
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  expect(stub).to have_been_requested
         | 
| 39 29 | 
             
                end
         | 
| 40 30 |  | 
| 41 31 |  | 
| 42 32 | 
             
                it 'must supply a args style check' do
         | 
| 43 | 
            -
                   | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
                    expect(check.args).to_not be_nil
         | 
| 47 | 
            -
                    response
         | 
| 48 | 
            -
                  }
         | 
| 33 | 
            +
                  stub = stub_request(:put, "localhost:8500/v1/agent/service/register")
         | 
| 34 | 
            +
                    .with(body: hash_including({check:  WebMock::API.hash_including({args: anything})}))
         | 
| 35 | 
            +
             | 
| 49 36 | 
             
                  health_check.start
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  expect(stub).to have_been_requested
         | 
| 50 39 | 
             
                end
         | 
| 51 40 |  | 
| 52 41 | 
             
                it 'must include the docker container id when the docker option is set to true' do
         | 
| 42 | 
            +
                  stub = stub_request(:put, "localhost:8500/v1/agent/service/register")
         | 
| 43 | 
            +
                    .with(body: hash_including({check:  WebMock::API.hash_including({docker_container_id: anything})}))
         | 
| 44 | 
            +
             | 
| 53 45 | 
             
                  local_health_check = Delayed::Worker::ConsulHealthCheck.new(
         | 
| 54 46 | 
             
                    worker_name: 'foobar',
         | 
| 55 47 | 
             
                    config: {docker: true}
         | 
| 56 48 | 
             
                  )
         | 
| 57 | 
            -
                  allow(response).to receive(:ok?).and_return(true)
         | 
| 58 | 
            -
                  allow(agent_client).to receive(:register_service) { |service|
         | 
| 59 | 
            -
                    check = service.checks.first
         | 
| 60 | 
            -
                    expect(check.docker_container_id).to_not be_nil
         | 
| 61 | 
            -
                    response
         | 
| 62 | 
            -
                  }
         | 
| 63 49 | 
             
                  local_health_check.start
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  expect(stub).to have_been_requested
         | 
| 64 52 | 
             
                end
         | 
| 65 53 | 
             
              end
         | 
| 66 54 |  | 
| 67 55 | 
             
              describe '#stop' do
         | 
| 68 56 | 
             
                it 'must deregister the service from consul' do
         | 
| 69 | 
            -
                   | 
| 70 | 
            -
             | 
| 71 | 
            -
                    .with(health_check.worker_name)
         | 
| 72 | 
            -
                    .and_return(response)
         | 
| 57 | 
            +
                  stub = stub_request(:put, "localhost:8500/v1/agent/service/deregister/foobar")
         | 
| 58 | 
            +
             | 
| 73 59 | 
             
                  health_check.stop
         | 
| 60 | 
            +
                  expect(stub).to have_been_requested
         | 
| 74 61 | 
             
                end
         | 
| 75 62 | 
             
              end
         | 
| 76 63 | 
             
            end
         | 
    
        data/spec/delayed/worker_spec.rb
    CHANGED
    
    | @@ -6,6 +6,11 @@ describe Delayed::Worker do | |
| 6 6 | 
             
              let(:worker_config) { {
         | 
| 7 7 | 
             
                  queue: "test", min_priority: 1, max_priority: 2, stuff: "stuff",
         | 
| 8 8 | 
             
              }.freeze }
         | 
| 9 | 
            +
              let(:job_attrs) { {
         | 
| 10 | 
            +
                id: 42, name: "testjob", full_name: "testfullname", :last_error= => nil,
         | 
| 11 | 
            +
                attempts: 1, reschedule: nil, :expired? => false,
         | 
| 12 | 
            +
                payload_object: {}, priority: 25
         | 
| 13 | 
            +
              }.freeze }
         | 
| 9 14 | 
             
              subject { described_class.new(worker_config.dup) }
         | 
| 10 15 |  | 
| 11 16 | 
             
              after { Delayed::Worker.lifecycle.reset! }
         | 
| @@ -14,9 +19,24 @@ describe Delayed::Worker do | |
| 14 19 | 
             
                it "fires off an error callback when a job raises an exception" do
         | 
| 15 20 | 
             
                  fired = false
         | 
| 16 21 | 
             
                  Delayed::Worker.lifecycle.before(:error) {|worker, exception| fired = true}
         | 
| 17 | 
            -
                  job = double( | 
| 18 | 
            -
                  subject.perform(job)
         | 
| 22 | 
            +
                  job = double(job_attrs)
         | 
| 23 | 
            +
                  output_count = subject.perform(job)
         | 
| 19 24 | 
             
                  expect(fired).to be_truthy
         | 
| 25 | 
            +
                  expect(output_count).to eq(1)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                it "uses the retry callback for a retriable exception" do
         | 
| 29 | 
            +
                  error_fired = retry_fired = false
         | 
| 30 | 
            +
                  Delayed::Worker.lifecycle.before(:error) {|worker, exception| error_fired = true }
         | 
| 31 | 
            +
                  Delayed::Worker.lifecycle.before(:retry) {|worker, exception| retry_fired = true}
         | 
| 32 | 
            +
                  job = Delayed::Job.new(payload_object: {}, priority: 25, strand: "test_jobs", max_attempts: 3)
         | 
| 33 | 
            +
                  expect(job).to receive(:invoke_job) do
         | 
| 34 | 
            +
                    raise Delayed::RetriableError, "that's all this job does"
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                  output_count = subject.perform(job)
         | 
| 37 | 
            +
                  expect(error_fired).to be_falsey
         | 
| 38 | 
            +
                  expect(retry_fired).to be_truthy
         | 
| 39 | 
            +
                  expect(output_count).to eq(1)
         | 
| 20 40 | 
             
                end
         | 
| 21 41 |  | 
| 22 42 | 
             
                it "reloads" do
         | 
| @@ -35,7 +55,7 @@ describe Delayed::Worker do | |
| 35 55 | 
             
                    expect(ActionDispatch::Reloader).to receive(:prepare!).once
         | 
| 36 56 | 
             
                    expect(ActionDispatch::Reloader).to receive(:cleanup!).once
         | 
| 37 57 | 
             
                  end
         | 
| 38 | 
            -
                  job = double( | 
| 58 | 
            +
                  job = double(job_attrs)
         | 
| 39 59 | 
             
                  subject.perform(job)
         | 
| 40 60 | 
             
                end
         | 
| 41 61 | 
             
              end
         | 
    
        data/spec/spec_helper.rb
    CHANGED
    
    | @@ -7,6 +7,8 @@ require 'database_cleaner' | |
| 7 7 | 
             
            require 'rack/test'
         | 
| 8 8 | 
             
            require 'test_after_commit' if ::Rails.version < '5'
         | 
| 9 9 | 
             
            require 'timecop'
         | 
| 10 | 
            +
            require 'webmock/rspec'
         | 
| 11 | 
            +
             | 
| 10 12 | 
             
            require 'pry'
         | 
| 11 13 | 
             
            require 'byebug'
         | 
| 12 14 |  | 
| @@ -19,6 +21,7 @@ RSpec.configure do |config| | |
| 19 21 | 
             
              config.before(:suite) do
         | 
| 20 22 | 
             
                DatabaseCleaner.strategy = :transaction
         | 
| 21 23 | 
             
                DatabaseCleaner.clean_with(:truncation)
         | 
| 24 | 
            +
                WebMock.disable_net_connect!
         | 
| 22 25 | 
             
              end
         | 
| 23 26 |  | 
| 24 27 | 
             
              config.before(:each) do |example|
         | 
    
        metadata
    CHANGED
    
    | @@ -1,15 +1,15 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: inst-jobs
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.2.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Tobias Luetke
         | 
| 8 8 | 
             
            - Brian Palmer
         | 
| 9 | 
            -
            autorequire: | 
| 9 | 
            +
            autorequire:
         | 
| 10 10 | 
             
            bindir: exe
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date:  | 
| 12 | 
            +
            date: 2021-02-19 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: activerecord
         | 
| @@ -172,33 +172,33 @@ dependencies: | |
| 172 172 | 
             
                  - !ruby/object:Gem::Version
         | 
| 173 173 | 
             
                    version: 1.6.1
         | 
| 174 174 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 175 | 
            -
              name:  | 
| 175 | 
            +
              name: diplomat
         | 
| 176 176 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 177 177 | 
             
                requirements:
         | 
| 178 | 
            -
                - - " | 
| 178 | 
            +
                - - "~>"
         | 
| 179 179 | 
             
                  - !ruby/object:Gem::Version
         | 
| 180 | 
            -
                    version:  | 
| 180 | 
            +
                    version: 2.5.1
         | 
| 181 181 | 
             
              type: :development
         | 
| 182 182 | 
             
              prerelease: false
         | 
| 183 183 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 184 184 | 
             
                requirements:
         | 
| 185 | 
            -
                - - " | 
| 185 | 
            +
                - - "~>"
         | 
| 186 186 | 
             
                  - !ruby/object:Gem::Version
         | 
| 187 | 
            -
                    version:  | 
| 187 | 
            +
                    version: 2.5.1
         | 
| 188 188 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 189 189 | 
             
              name: pg
         | 
| 190 190 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 191 191 | 
             
                requirements:
         | 
| 192 | 
            -
                - - " | 
| 192 | 
            +
                - - ">="
         | 
| 193 193 | 
             
                  - !ruby/object:Gem::Version
         | 
| 194 | 
            -
                    version: ' | 
| 194 | 
            +
                    version: '0'
         | 
| 195 195 | 
             
              type: :development
         | 
| 196 196 | 
             
              prerelease: false
         | 
| 197 197 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 198 198 | 
             
                requirements:
         | 
| 199 | 
            -
                - - " | 
| 199 | 
            +
                - - ">="
         | 
| 200 200 | 
             
                  - !ruby/object:Gem::Version
         | 
| 201 | 
            -
                    version: ' | 
| 201 | 
            +
                    version: '0'
         | 
| 202 202 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 203 203 | 
             
              name: pry
         | 
| 204 204 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -311,6 +311,20 @@ dependencies: | |
| 311 311 | 
             
                - - '='
         | 
| 312 312 | 
             
                  - !ruby/object:Gem::Version
         | 
| 313 313 | 
             
                    version: 0.7.1
         | 
| 314 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 315 | 
            +
              name: webmock
         | 
| 316 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 317 | 
            +
                requirements:
         | 
| 318 | 
            +
                - - ">="
         | 
| 319 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 320 | 
            +
                    version: '0'
         | 
| 321 | 
            +
              type: :development
         | 
| 322 | 
            +
              prerelease: false
         | 
| 323 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 324 | 
            +
                requirements:
         | 
| 325 | 
            +
                - - ">="
         | 
| 326 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 327 | 
            +
                    version: '0'
         | 
| 314 328 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 315 329 | 
             
              name: wwtd
         | 
| 316 330 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -325,7 +339,7 @@ dependencies: | |
| 325 339 | 
             
                - - "~>"
         | 
| 326 340 | 
             
                  - !ruby/object:Gem::Version
         | 
| 327 341 | 
             
                    version: 1.4.0
         | 
| 328 | 
            -
            description: | 
| 342 | 
            +
            description:
         | 
| 329 343 | 
             
            email:
         | 
| 330 344 | 
             
            - brianp@instructure.com
         | 
| 331 345 | 
             
            executables:
         | 
| @@ -441,7 +455,7 @@ files: | |
| 441 455 | 
             
            homepage: https://github.com/instructure/inst-jobs
         | 
| 442 456 | 
             
            licenses: []
         | 
| 443 457 | 
             
            metadata: {}
         | 
| 444 | 
            -
            post_install_message: | 
| 458 | 
            +
            post_install_message:
         | 
| 445 459 | 
             
            rdoc_options: []
         | 
| 446 460 | 
             
            require_paths:
         | 
| 447 461 | 
             
            - lib
         | 
| @@ -456,8 +470,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 456 470 | 
             
                - !ruby/object:Gem::Version
         | 
| 457 471 | 
             
                  version: '0'
         | 
| 458 472 | 
             
            requirements: []
         | 
| 459 | 
            -
            rubygems_version: 3. | 
| 460 | 
            -
            signing_key: | 
| 473 | 
            +
            rubygems_version: 3.1.4
         | 
| 474 | 
            +
            signing_key:
         | 
| 461 475 | 
             
            specification_version: 4
         | 
| 462 476 | 
             
            summary: Instructure-maintained fork of delayed_job
         | 
| 463 477 | 
             
            test_files:
         |