0.0
The project is in a healthy, maintained state
Cancellation, progress, checkpoints, heartbeats, and lifecycle tracking for Active Job.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 8.1, < 9.0
>= 8.1, < 9.0
 Project Readme

JobControl

JobControl gives long-running Active Job jobs a small, practical control surface: a heartbeat and a safe way to ask a job to stop.

Cancellation is cooperative. JobControl never kills a worker thread or interrupts a database transaction. Your job finishes the unit it is working on, notices the request at its next checkpoint, and exits cleanly. It works best when the job can divide its work into small, idempotent units.

What it supports

JobControl requires Ruby 3.2+ and Rails 8.1–8.x.

Feature Availability
Lifecycle, heartbeats, checkpoints, and cooperative cancellation Any Active Job adapter
Cleanup after a permanent discard Solid Queue 1.4–1.7 with the optional adapter
Job detail UI and cancellation button mission_control-jobs 1.3.x with the optional integration
Cleanup after Sidekiq or GoodJob discard Not included yet

The core behavior lives at the Active Job layer, so it works with any adapter. Permanent discard is different: every queue exposes that operation differently, so it needs a queue-specific adapter.

Installation

bundle add job_control
bin/rails generate job_control:install
bin/rails db:migrate

Run the generator again when upgrading JobControl. It skips migrations that are already present and copies any new ones.

Your first controlled job

Include JobControl::Job in jobs that need cooperative cancellation. Add checkpoints between units that are safe to finish independently.

class ExampleJob < ApplicationJob
  include JobControl::Job

  def perform
    100.times do
      perform_one_unit_of_work
      checkpoint!
    end
  end

  private

  def perform_one_unit_of_work
  end
end
job = ExampleJob.perform_later
JobControl.cancel(job.job_id)

checkpoint! updates the heartbeat and raises JobControl::Cancelled if cancellation was requested. Put it after a blocking wait and between batches or loop iterations. It does not interrupt code already running.

A checkpoint performs one database reload and one timestamp update. In practice, checkpoint once per batch or every few seconds of work—not once per record—unless near-instant cancellation matters more than the extra writes.

Lifecycle

A job_controls row is created when a controlled job is enqueued and becomes running when execution begins.

What happens What JobControl does
The job succeeds Publishes job_control.succeeded and deletes the row immediately.
The job sees a cancellation request at a checkpoint Publishes job_control.cancelled and deletes the row immediately.
The job fails without a cancellation request Publishes job_control.failed and deletes the row. A retry gets a fresh active control with the same job ID.
A cancellation request is followed by an error Leaves the row as cancel_requested, so a retry stops before doing work.
Solid Queue permanently discards the job The Solid Queue adapter deletes any remaining row after the queue transaction commits.

There is no retention window and no cleanup cron. For an unsupported queue, a cancelled attempt that fails and is never retried cannot be detected through generic Active Job callbacks; add a queue-specific adapter before relying on permanent-discard cleanup.

Runtime API

control = JobControl.find(job.job_id)

control.status
control.cancellable?
control.cancel_requested?
control.heartbeat_at
control.last_checkpoint_at

control.cancel!

Within a controlled job, call checkpoint! directly or pass cancellation to a service with its own loop:

SomeService.call(cancellation: cancellation)

Observability

A control is stale after five minutes without a heartbeat by default:

JobControl.configure do |config|
  config.stale_after = 15.minutes
end

JobControl publishes Active Support notifications with control: in the payload:

  • job_control.started
  • job_control.checkpoint
  • job_control.cancel_requested
  • job_control.cancelled
  • job_control.succeeded
  • job_control.failed

Solid Queue

The optional adapter supports Solid Queue 1.4–1.7 and removes controls after direct and batched discard operations.

require "job_control/adapters/solid_queue"

Rails.application.config.to_prepare do
  JobControl::Adapters::SolidQueue::Adapter.install!
end

Mission Control Jobs

With mission_control-jobs 1.3.x, JobControl adds a Job control section to the existing job detail page. It uses Mission Control's own styles and controller authentication.

require "job_control/integrations/mission_control_jobs"

Rails.application.config.to_prepare do
  JobControl::Integrations::MissionControlJobs.install!
end

Mission Control is assumed to be mounted at /admin/jobs. For another location:

JobControl::Integrations::MissionControlJobs.install!(mount_path: "/operations/jobs")

The endpoint, client code, controller, and translations come from JobControl. There is no application view override, custom controller, or hand-written route to maintain. Restart the web process after enabling it.

Development

bundle exec rake

Out of scope

JobControl does not provide hard process termination, transaction rollback, progress tracking, pause/resume, cursor recovery, throttling, batch cancellation, or an administrative adapter for every queue backend.

Contributing

Issues and pull requests are welcome at kha-wogi/job_control.