inst-jobs 1.0.4 → 2.0.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 +4 -4
- data/lib/delayed/periodic.rb +14 -4
- data/lib/delayed/version.rb +1 -1
- data/lib/delayed/worker/health_check.rb +34 -19
- data/spec/delayed/periodic_spec.rb +39 -0
- data/spec/delayed/worker/health_check_spec.rb +9 -0
- metadata +5 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d674b7da21caf04eb87ff9823ed549c93a901219669316090d088f0699564e59
         | 
| 4 | 
            +
              data.tar.gz: 021456d34f12eff8cc988db866018d701fc77ffdbb57e9fb308fc1bd25a91ecb
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ad78cfdd9026db24b714c532c8ee837a875e443afc375909f0c130e3cfbf87d1f872344f982d931838bfa6649a2f1edc59430f6444a2baee08f8afb568015cfc
         | 
| 7 | 
            +
              data.tar.gz: e2b127477f0687958178505628b9544aa5c49e7aa1d0ceef32892250aa26aeb1c77f12bcacd6682e17c2bc379f987b154a0f982e029852432c39f7b3a5335df8
         | 
    
        data/lib/delayed/periodic.rb
    CHANGED
    
    | @@ -49,10 +49,20 @@ class Periodic | |
| 49 49 | 
             
              end
         | 
| 50 50 |  | 
| 51 51 | 
             
              def enqueue
         | 
| 52 | 
            -
                Delayed::Job.enqueue(self,  | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 52 | 
            +
                Delayed::Job.enqueue(self, **enqueue_args)
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              def enqueue_args
         | 
| 56 | 
            +
                inferred_args = {
         | 
| 57 | 
            +
                  max_attempts: 1,
         | 
| 58 | 
            +
                  run_at: @cron.next_time(Delayed::Periodic.now).utc.to_time,
         | 
| 59 | 
            +
                  singleton: (@job_args[:singleton] == false ? nil : tag),
         | 
| 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.
         | 
| 63 | 
            +
                  on_conflict: :patient
         | 
| 64 | 
            +
                }
         | 
| 65 | 
            +
                @job_args.merge(inferred_args)
         | 
| 56 66 | 
             
              end
         | 
| 57 67 |  | 
| 58 68 | 
             
              def perform
         | 
    
        data/lib/delayed/version.rb
    CHANGED
    
    
| @@ -22,31 +22,46 @@ module Delayed | |
| 22 22 |  | 
| 23 23 | 
             
                    def reschedule_abandoned_jobs
         | 
| 24 24 | 
             
                      return if Settings.worker_health_check_type == :none
         | 
| 25 | 
            +
                      Delayed::Job.transaction do
         | 
| 26 | 
            +
                        # this job is a special case, and is not a singleton
         | 
| 27 | 
            +
                        # because if it gets wiped out suddenly during execution
         | 
| 28 | 
            +
                        # it can't go clean up it's abandoned self.  Therefore,
         | 
| 29 | 
            +
                        # we try to get an advisory lock when it runs.  If we succeed,
         | 
| 30 | 
            +
                        # no other job is trying to do this right now (and if we abandon the
         | 
| 31 | 
            +
                        # job, the transaction will end, releasing the advisory lock).
         | 
| 32 | 
            +
                        result = attempt_advisory_lock
         | 
| 33 | 
            +
                        return unless result
         | 
| 34 | 
            +
                        checker = Worker::HealthCheck.build(
         | 
| 35 | 
            +
                          type: Settings.worker_health_check_type,
         | 
| 36 | 
            +
                          config: Settings.worker_health_check_config,
         | 
| 37 | 
            +
                          worker_name: 'cleanup-crew'
         | 
| 38 | 
            +
                        )
         | 
| 39 | 
            +
                        live_workers = checker.live_workers
         | 
| 25 40 |  | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
                              # double check that the job is still there. locked_by will immediately be reset
         | 
| 40 | 
            -
                              # to nil in this transaction by Job#reschedule
         | 
| 41 | 
            -
                              next unless Delayed::Job.where(id: job, locked_by: job.locked_by).update_all(locked_by: "abandoned job cleanup") == 1
         | 
| 42 | 
            -
                              job.reschedule
         | 
| 41 | 
            +
                        Delayed::Job.running_jobs.each do |job|
         | 
| 42 | 
            +
                          # prefetched jobs have their own way of automatically unlocking themselves
         | 
| 43 | 
            +
                          next if job.locked_by.start_with?("prefetch:")
         | 
| 44 | 
            +
                          unless live_workers.include?(job.locked_by)
         | 
| 45 | 
            +
                            begin
         | 
| 46 | 
            +
                              Delayed::Job.transaction do
         | 
| 47 | 
            +
                                # double check that the job is still there. locked_by will immediately be reset
         | 
| 48 | 
            +
                                # to nil in this transaction by Job#reschedule
         | 
| 49 | 
            +
                                next unless Delayed::Job.where(id: job, locked_by: job.locked_by).update_all(locked_by: "abandoned job cleanup") == 1
         | 
| 50 | 
            +
                                job.reschedule
         | 
| 51 | 
            +
                              end
         | 
| 52 | 
            +
                            rescue
         | 
| 53 | 
            +
                              ::Rails.logger.error "Failure rescheduling abandoned job #{job.id} #{$!.inspect}"
         | 
| 43 54 | 
             
                            end
         | 
| 44 | 
            -
                          rescue
         | 
| 45 | 
            -
                            ::Rails.logger.error "Failure rescheduling abandoned job #{job.id} #{$!.inspect}"
         | 
| 46 55 | 
             
                          end
         | 
| 47 56 | 
             
                        end
         | 
| 48 57 | 
             
                      end
         | 
| 49 58 | 
             
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def attempt_advisory_lock
         | 
| 61 | 
            +
                      lock_name = "Delayed::Worker::HealthCheck#reschedule_abandoned_jobs"
         | 
| 62 | 
            +
                      output = ActiveRecord::Base.connection.execute("SELECT pg_try_advisory_xact_lock(half_md5_as_bigint('#{lock_name}'));")
         | 
| 63 | 
            +
                      output.getvalue(0, 0)
         | 
| 64 | 
            +
                    end
         | 
| 50 65 | 
             
                  end
         | 
| 51 66 |  | 
| 52 67 | 
             
                  attr_accessor :config, :worker_name
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            RSpec.describe Delayed::Periodic do
         | 
| 6 | 
            +
              around(:each) do |block|
         | 
| 7 | 
            +
                # make sure we can use ".cron" and
         | 
| 8 | 
            +
                # such safely without leaking global state
         | 
| 9 | 
            +
                prev_sched = Delayed::Periodic.scheduled
         | 
| 10 | 
            +
                prev_ovr = Delayed::Periodic.overrides
         | 
| 11 | 
            +
                Delayed::Periodic.scheduled = {}
         | 
| 12 | 
            +
                Delayed::Periodic.overrides = {}
         | 
| 13 | 
            +
                block.call
         | 
| 14 | 
            +
              ensure
         | 
| 15 | 
            +
                Delayed::Periodic.scheduled = prev_sched
         | 
| 16 | 
            +
                Delayed::Periodic.overrides = prev_ovr
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              describe ".cron" do
         | 
| 20 | 
            +
                let(:job_name){ 'just a test'}
         | 
| 21 | 
            +
                it "provides a tag by default for periodic jobs" do
         | 
| 22 | 
            +
                  Delayed::Periodic.cron job_name, '*/10 * * * *' do
         | 
| 23 | 
            +
                    # no-op
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                  instance = Delayed::Periodic.scheduled[job_name]
         | 
| 26 | 
            +
                  expect(instance).to_not be_nil
         | 
| 27 | 
            +
                  expect(instance.enqueue_args[:singleton]).to eq("periodic: just a test")
         | 
| 28 | 
            +
                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 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -107,6 +107,15 @@ RSpec.describe Delayed::Worker::HealthCheck do | |
| 107 107 | 
             
                  @dead_job.reload
         | 
| 108 108 | 
             
                  expect(@dead_job.locked_by).to eq 'prefetch:some_node'
         | 
| 109 109 | 
             
                end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                it "bails immediately if advisory lock already taken" do
         | 
| 112 | 
            +
                  allow(Delayed::Worker::HealthCheck).to receive(:attempt_advisory_lock).and_return(false)
         | 
| 113 | 
            +
                  Delayed::Worker::HealthCheck.reschedule_abandoned_jobs
         | 
| 114 | 
            +
                  @dead_job.reload
         | 
| 115 | 
            +
                  expect(@dead_job.run_at.to_i).to eq(initial_run_at.to_i)
         | 
| 116 | 
            +
                  expect(@dead_job.locked_at).to_not be_nil
         | 
| 117 | 
            +
                  expect(@dead_job.locked_by).to_not be_nil
         | 
| 118 | 
            +
                end
         | 
| 110 119 | 
             
              end
         | 
| 111 120 |  | 
| 112 121 | 
             
              describe '#initialize' do
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: inst-jobs
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 2.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Tobias Luetke
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: exe
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2020- | 
| 12 | 
            +
            date: 2020-12-07 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: activerecord
         | 
| @@ -412,6 +412,7 @@ files: | |
| 412 412 | 
             
            - spec/delayed/cli_spec.rb
         | 
| 413 413 | 
             
            - spec/delayed/daemon_spec.rb
         | 
| 414 414 | 
             
            - spec/delayed/message_sending_spec.rb
         | 
| 415 | 
            +
            - spec/delayed/periodic_spec.rb
         | 
| 415 416 | 
             
            - spec/delayed/server_spec.rb
         | 
| 416 417 | 
             
            - spec/delayed/settings_spec.rb
         | 
| 417 418 | 
             
            - spec/delayed/work_queue/in_process_spec.rb
         | 
| @@ -448,7 +449,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 448 449 | 
             
              requirements:
         | 
| 449 450 | 
             
              - - ">="
         | 
| 450 451 | 
             
                - !ruby/object:Gem::Version
         | 
| 451 | 
            -
                  version: '2. | 
| 452 | 
            +
                  version: '2.6'
         | 
| 452 453 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 453 454 | 
             
              requirements:
         | 
| 454 455 | 
             
              - - ">="
         | 
| @@ -480,6 +481,7 @@ test_files: | |
| 480 481 | 
             
            - spec/delayed/cli_spec.rb
         | 
| 481 482 | 
             
            - spec/delayed/daemon_spec.rb
         | 
| 482 483 | 
             
            - spec/delayed/worker_spec.rb
         | 
| 484 | 
            +
            - spec/delayed/periodic_spec.rb
         | 
| 483 485 | 
             
            - spec/delayed/message_sending_spec.rb
         | 
| 484 486 | 
             
            - spec/delayed/settings_spec.rb
         | 
| 485 487 | 
             
            - spec/delayed/work_queue/in_process_spec.rb
         |