Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ There are several settings that control how Solid Queue works that you can set a
- `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`.
- `clear_finished_jobs_after`: period to keep finished jobs around, in case `preserve_finished_jobs` is true — defaults to 1 day. When installing Solid Queue, [a recurring job](#recurring-tasks) is automatically configured to clear finished jobs every hour on the 12th minute in batches. You can edit the `recurring.yml` configuration to change this as you see fit.
- `default_concurrency_control_period`: the value to be used as the default for the `duration` parameter in [concurrency controls](#concurrency-controls). It defaults to 3 minutes.
- `recurring_task_job_class`: the name of the job class that runs [recurring tasks](#recurring-tasks) defined as a `command`—defaults to `"SolidQueue::RecurringJob"`.

### Validating the configuration

Expand Down Expand Up @@ -916,13 +917,13 @@ MyJob.perform_later(42, status: "custom_status")

Tasks are enqueued at their corresponding times by the scheduler, and each task schedules the next one. This is pretty much [inspired by what GoodJob does](https://github.com/bensheldon/good_job/blob/994ecff5323bf0337e10464841128fda100750e6/lib/good_job/cron_manager.rb).

For recurring tasks defined as a `command`, you can also change the job class that runs them as follows:
For recurring tasks defined as a `command`, you can also change the job class that runs them by setting its name in `config/application.rb` or your environment config:
```ruby
Rails.application.config.after_initialize do # or to_prepare
SolidQueue::RecurringTask.default_job_class = MyRecurringCommandJob
end
config.solid_queue.recurring_task_job_class = "MyRecurringCommandJob"
```

The class is resolved when a task is first enqueued, so your app's job class isn't loaded at boot.

It's possible to run multiple schedulers with the same `recurring_tasks` configuration, for example, if you have multiple servers for redundancy, and you run the `scheduler` in more than one of them. To avoid enqueuing duplicate tasks at the same time, an entry in a new `solid_queue_recurring_executions` table is created in the same transaction as the job is enqueued. This table has a unique index on `task_key` and `run_at`, ensuring only one entry per task per time will be created. This only works if you have `preserve_finished_jobs` set to `true` (the default), and the guarantee applies as long as you keep the jobs around.

**Note**: a single recurring schedule is supported, so you can have multiple schedulers using the same schedule, but not multiple schedulers using different configurations.
Expand Down
28 changes: 24 additions & 4 deletions app/models/solid_queue/recurring_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ class RecurringTask < Record

has_many :recurring_executions, foreign_key: :task_key, primary_key: :key

mattr_accessor :default_job_class
self.default_job_class = RecurringJob

class << self
def default_job_class
warn_about_default_job_class_deprecation
SolidQueue.recurring_task_job_class
end

def default_job_class=(job_class)
warn_about_default_job_class_deprecation
SolidQueue.recurring_task_job_class = job_class
end

def wrap(args)
args.is_a?(self) ? args : from_configuration(args.first, **args.second)
end
Expand Down Expand Up @@ -54,6 +61,14 @@ def create_or_update_all(tasks)
upsert_all tasks.map(&:attributes_for_upsert)
end
end

private
def warn_about_default_job_class_deprecation
SolidQueue.deprecator.warn(<<~DEPRECATION)
`SolidQueue::RecurringTask.default_job_class` is deprecated and will be removed in Solid Queue #{SolidQueue.next_major_version}.0.
Use `config.solid_queue.recurring_task_job_class` or `SolidQueue.recurring_task_job_class` instead.
DEPRECATION
end
end


Expand Down Expand Up @@ -190,7 +205,12 @@ def apply_default_time_zone_to(schedule)
end

def job_class
@job_class ||= class_name.present? ? class_name.safe_constantize : self.class.default_job_class
@job_class ||= class_name.present? ? class_name.safe_constantize : resolve_recurring_task_job_class
end

def resolve_recurring_task_job_class
job_class = SolidQueue.recurring_task_job_class
job_class.is_a?(String) ? job_class.constantize : job_class
end

def enqueue_options
Expand Down
1 change: 1 addition & 0 deletions lib/solid_queue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module SolidQueue
mattr_accessor :preserve_finished_jobs, default: true
mattr_accessor :clear_finished_jobs_after, default: 1.day
mattr_accessor :default_concurrency_control_period, default: 3.minutes
mattr_accessor :recurring_task_job_class, default: "SolidQueue::RecurringJob"

mattr_reader :time_zone

Expand Down
34 changes: 34 additions & 0 deletions test/models/solid_queue/recurring_task_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def perform
end
end

class CustomRecurringCommandJob < SolidQueue::RecurringJob
end

test "job without arguments" do
task = recurring_task_with(class_name: "JobWithoutArguments")
enqueue_and_assert_performed_with_result task, "job_without_arguments"
Expand Down Expand Up @@ -222,6 +225,37 @@ def perform
assert_equal "SolidQueue::RecurringJob", SolidQueue::Job.last.class_name
end

test "task configured with a command uses a custom recurring task job class" do
SolidQueue.stubs(:recurring_task_job_class).returns(CustomRecurringCommandJob)

task = recurring_task_with(command: "JobBuffer.add('from_a_command')")
enqueue_and_assert_performed_with_result(task, "from_a_command")

assert_equal "SolidQueue::RecurringTaskTest::CustomRecurringCommandJob", SolidQueue::Job.last.class_name
end

test "task configured with a command uses a custom recurring task job class given as a string" do
SolidQueue.stubs(:recurring_task_job_class).returns("SolidQueue::RecurringTaskTest::CustomRecurringCommandJob")

task = recurring_task_with(command: "JobBuffer.add('from_a_command')")
enqueue_and_assert_performed_with_result(task, "from_a_command")

assert_equal "SolidQueue::RecurringTaskTest::CustomRecurringCommandJob", SolidQueue::Job.last.class_name
end

test "default_job_class is deprecated in favor of SolidQueue.recurring_task_job_class" do
SolidQueue.with(recurring_task_job_class: SolidQueue.recurring_task_job_class) do
assert_deprecated(SolidQueue.deprecator) do
SolidQueue::RecurringTask.default_job_class = "SolidQueue::RecurringTaskTest::CustomRecurringCommandJob"
end
assert_equal "SolidQueue::RecurringTaskTest::CustomRecurringCommandJob", SolidQueue.recurring_task_job_class

assert_deprecated(SolidQueue.deprecator) do
assert_equal "SolidQueue::RecurringTaskTest::CustomRecurringCommandJob", SolidQueue::RecurringTask.default_job_class
end
end
end

test "schedule recurring tasks dynamically" do
SolidQueue::RecurringTask.create_dynamic_task("test 1", command: "puts 1", schedule: "every hour")
SolidQueue::RecurringTask.create_dynamic_task("test 2", command: "puts 2", schedule: "every minute", static: true)
Expand Down
Loading