Float を待ち時間に指定できるように `retry_on` が拡張された
retry_on のドキュメントは「秒単位での遅延(デフォルト: 3 秒)」と記述し、整数の wait: は動作していた。 しかし Float を直接指定した場合、determine_delay が ActiveSupport::Duration と Integer のみをマッチ対象としていたため else ブランチに入り、内部エラーがスローされてジョブが再キューされなかった。 このバグは Float が除外された結果、元の例外が失われてデバッグが困難になるという実務上の問題を引き起こしていた。
背景
retry_on のドキュメントは「秒単位での遅延(デフォルト: 3 秒)」と記述し、整数の wait: は動作していた。 しかし Float を直接指定した場合、determine_delay が ActiveSupport::Duration と Integer のみをマッチ対象としていたため else ブランチに入り、内部エラーがスローされてジョブが再キューされなかった。 このバグは Float が除外された結果、元の例外が失われてデバッグが困難になるという実務上の問題を引き起こしていた。
技術的な変更
activejob/lib/active_job/exceptions.rb の determine_delay メソッドで、ケース分岐に Float を追加した。 変更前は when ActiveSupport::Duration, Integer だったが、変更後は when ActiveSupport::Duration, Integer, Float となり、seconds_or_duration_or_algorithm.to_i が実行されるようになった。
- when ActiveSupport::Duration, Integer
+ when ActiveSupport::Duration, Integer, Float
delay = seconds_or_duration_or_algorithm.to_i
Float は to_i によって小数部が切り捨てられ、整数秒として扱われるため、既存の遅延粒度は変わらない。 さらにテストコードが追加され、retry_on FloatWaitError, wait: 2.5 の動作を検証した。
+class FloatWaitError < StandardError; end
@@
retry_on LongWaitError, wait: 1.hour, attempts: 10
+ retry_on FloatWaitError, wait: 2.5, attempts: 10
retry_on ShortWaitTenAttemptsError, wait: 1.second, attempts: 10
+ test "float wait job" do
+ travel_to Time.now
+ random_amount = 1
+ delay_for_jitter = random_amount * 2 * ActiveJob::Base.retry_jitter
+
+ Kernel.stub(:rand, random_amount) do
+ RetryJob.perform_later "FloatWaitError", 2, :log_scheduled_at
+ assert_equal [
+ "Raised FloatWaitError for the 1st time",
+ "Next execution scheduled at #{(Time.now + 2.seconds + delay_for_jitter).to_f}",
+ "Successfully completed job"
+ ], JobBuffer.values
+ end
+ end
設計判断
マッチ対象に Float を明示的に追加 する方針が採られた。 代替案として Numeric での包括的マッチが議論されたが、既存コードの意図と後方互換性を保つため、最小限の変更である Float のみ追加が選択された。 これにより、整数と同様に秒単位での遅延が保証され、サブ秒対応はアダプタ側の実装次第で別途検討できる設計となっている。
まとめ
retry_on が Float 待ち時間を受け入れるようになり、ジョブ再試行時の例外置換が防止された。 変更はケース分岐への型追加のみで影響範囲が小さく、既存の遅延ロジックと互換性を維持しつつ、実務上の使い勝手を向上させている。