複数CRON生成を検出して警告する機能を追加
Solid Queueは、自然言語で記述されたスケジュールが複数のCRON式を生成する場合、最初のCRON式のみを使用し残りを無視していました。本PRは、この問題を検出してバリデーションエラーを発生させる機能を追加します。
背景
Solid QueueはFugitライブラリを使用してスケジュールをパースします。every day at 00:40 and 15:20 のような自然言語表記は、異なる分数を持つ複数の時刻を指定するため、単一のCRON式では表現できません。この場合、Fugitは最初の時刻(40 0 * * *)のみを返し、2番目の時刻(15:20)は無視されていました。#703 がこの問題を報告しています。
同じ分数を持つ複数の時刻(every day at 00:40 and 15:40)は 40 0,15 * * * のように単一のCRON式で表現できるため問題になりません。しかし、異なる分数を持つ場合は複数のCRON式が必要であり、それが暗黙的に切り捨てられることでタスクの実行漏れが発生していました。
技術的な変更
app/models/solid_queue/recurring_task.rb の parsed_schedule メソッドが、multi: :fail オプションを指定するよう変更されました。
変更前:
def parsed_schedule
@parsed_schedule ||= Fugit.parse(schedule)
end
変更後:
def parsed_schedule
@parsed_schedule ||= Fugit.parse(schedule, multi: :fail)
end
multi: :fail を指定すると、Fugitが複数のCRON式を生成する必要がある場合に ArgumentError を発生させます。この例外を ensure_schedule_supported バリデーションメソッドでキャッチし、エラーメッセージに変換します。
def ensure_schedule_supported
unless parsed_schedule.instance_of?(Fugit::Cron)
errors.add :schedule, :unsupported, message: "is not a supported recurring schedule"
end
rescue ArgumentError => error
message = if error.message.include?("multiple crons")
"generates multiple cron schedules. Please use separate recurring tasks for each schedule, " +
"or use explicit cron syntax (e.g., '40 0,15 * * *' for multiple times with the same minutes)"
else
error.message
end
errors.add :schedule, :unsupported, message: message
end
エラーメッセージは、問題の原因と解決策の両方を提示します。複数のタスクに分割するか、同じ分数なら明示的なCRON構文(40 0,15 * * *)を使用するよう案内します。
test/models/solid_queue/recurring_task_test.rb には、以下の3つのケースをカバーするテストが追加されました。
- 単一の時刻指定(
every day at 00:40)は有効 - 同じ分数の複数時刻(
every day at 00:40 and 15:40)は単一CRON式40 0,15 * * *として有効 - 異なる分数の複数時刻(
every day at 00:40 and 15:20)は無効で、適切なエラーメッセージが表示される
task = recurring_task_with(class_name: "JobWithoutArguments", schedule: "every day at 00:40 and 15:20")
assert_not task.valid?
error_message = task.errors[:schedule].first
assert_includes error_message, "generates multiple cron schedules"
assert_includes error_message, "Please use separate recurring tasks"
設計判断
Fugitの multi: :fail オプションを活用する方式 が採用されました。
この判断により、複数CRON検出のロジックをSolid Queue側で実装する必要がなくなり、Fugitライブラリの既存機能を活用できます。ArgumentError の発生をバリデーション層でキャッチしてユーザーフレンドリーなメッセージに変換することで、エラーハンドリングとユーザビリティの両立を実現しています。
エラーメッセージには、問題の説明だけでなく具体的な解決策(タスク分割またはCRON構文の使用)を含めることで、ユーザーが次のアクションを判断しやすくなっています。バリデーションメソッド名も supported_schedule から ensure_schedule_supported へリネームされ、検証内容がより明確になりました。
まとめ
本PRは、Fugitの multi: :fail オプションを活用して、複数CRON式を生成するスケジュール設定を検出し、バリデーションエラーとして報告する機能を追加しました。これにより、タスクの実行漏れが設定時に発見できるようになり、複数の時刻でタスクを実行したい場合の正しい設定方法が明示されます。