Priority is a decision, not a default

Free preview 3 min to read

No job in this app calls queue_with_priority; a search of app/jobs/ for priority finds nothing. Every job still has a priority, because every row in solid_queue_jobs does: db/queue_schema.rb makes the column null: false. This lesson is about where that number comes from when nobody asked for one.

Two defaults, not one

ActiveJob::QueuePriority, mixed into every job:

module ActiveJob
  module QueuePriority
    module ClassMethods
      mattr_accessor :default_priority

      def queue_with_priority(priority = nil, &block)
        if block_given?
          self.priority = block
        else
          self.priority = priority
        end
      end
    end

    included do
      class_attribute :priority, instance_accessor: false, default: default_priority
    end

    def priority
      if @priority.is_a?(Proc)
        @priority = instance_exec(&@priority)
      end
      @priority
    end
  end
end

default_priority is a plain mattr_accessor the app never sets, so it's nil. A job that never calls queue_with_priority returns nil from #priority: not 0, nil. Rails's own comment on the attribute, in active_job/core.rb, says what the number means once it exists: # Priority that the job will have (lower is more priority).

nil never reaches the database. Here's SolidQueue::Job.attributes_from_active_job:

def attributes_from_active_job(active_job)
  {
    queue_name: active_job.queue_name || DEFAULT_QUEUE_NAME,
    active_job_id: active_job.job_id,
    priority: active_job.priority || DEFAULT_PRIORITY,
    scheduled_at: active_job.scheduled_at,
    class_name: active_job.class.name,
    arguments: active_job.serialize,
    concurrency_key: active_job.concurrency_key
  }
end

DEFAULT_PRIORITY = 0 is a constant on SolidQueue::Job. So there are two defaults, from two libraries. Rails's default is "no opinion", nil, and Solid Queue's is the floor it substitutes at the last possible moment, the INSERT. The mid-level course "Solid Queue: job architecture" showed what the stored number controls once it's in the table: order(priority: :asc, job_id: :asc), the sort every ready claim runs. This lesson stops one step earlier, at the decision the number stands for.

queue_with_priority, run

Here's a throwaway job, run with bin/rails runner against the development queue database, inside a transaction that was rolled back:

class PriorityProbeJob < ApplicationJob
  self.queue_adapter = :solid_queue # development uses :async
  queue_as :probe
  queue_with_priority 20
  def perform(*); end
end

j1 = PriorityProbeJob.perform_later
SolidQueue::Job.find_by(active_job_id: j1.job_id).priority
stored priority = 20

20, the number on the class. queue_with_priority sets the class's priority, ActiveJob::Core#initialize copies it into the instance as @priority = self.class.priority, and attributes_from_active_job writes it straight into the row.

What the app's jobs never decided

# app/jobs/partners/fetch_site_icon_job.rb
class Partners::FetchSiteIconJob < ApplicationJob
  queue_as :default
  # no queue_with_priority anywhere in this file
end

So Partners::FetchSiteIconJob#priority is nil, attributes_from_active_job substitutes DEFAULT_PRIORITY, and every row the job creates carries priority: 0. Nobody decided 0 was right for it; nobody decided anything, and 0 is where the chain bottoms out. The same holds for every other job in the app. That's a fact about the app, not a criticism: the jobs that share a queue have never needed ordering among themselves. The rest of the course is about what changes when they do.

Key takeaways

  • ActiveJob::QueuePriority#priority is nil until a job calls queue_with_priority: Rails's default is no opinion, not 0.
  • SolidQueue::Job.attributes_from_active_job substitutes DEFAULT_PRIORITY = 0 only when it builds the row, with active_job.priority || DEFAULT_PRIORITY. That floor is Solid Queue's, not Rails's.
  • queue_with_priority 20 lands in solid_queue_jobs.priority unchanged: 20 in, 20 stored.
  • No job in the app calls queue_with_priority, so all of them get priority 0 by default, not by design.
Lesson 1 of 7 · free

Keep going with lesson 2

Static vs dynamic priority, and who wins

Compares static and block-based queue_with_priority with .set(priority:), which overrides the class value when present.

A free account also gives you the quizzes, your progress and notes, and the discussion.

Type to start searching...