retry_on の wait 引数の型バリデーションをクラス定義時に実施
retry_on の wait: オプションに不正な型が渡された場合、従来はジョブ実行中に RuntimeError が発生し、元の例外が隠蔽されていました。この PR は、その型チェックを retry_on メソッド自身に移し、クラス定義時に即座に ArgumentError を投げるように変更します。
背景
不正な wait: 型(例: wait: "soon")は determine_delay の else 節に到達し、ジョブ失敗後の rescue_from ハンドラ内で RuntimeError が送出されていました。結果として、アプリケーション例外が内部エラーで上書きされ、再キューイングが行われないという混乱が発生していました。
この挙動は、retry_on が実行時に型を判定せず、遅延算出ロジックに委譲していたことが根本原因です。ジョブが失敗したタイミングで例外が発生するため、デバッグ時に本来の失敗原因が見えにくくなっていました。
結論: 早期に型を検証し、ロード時に明示的なエラーメッセージを出すことで、開発者が問題を即座に認識できるようにする必要がありました。
技術的な変更
retry_on メソッド冒頭に case wait 文が追加され、サポート対象の型を明示しました。具体的には :polynomially_longer、Integer、Float、ActiveSupport::Duration、Proc が許容され、それ以外は以下のように ArgumentError を発生させます。
def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT, report: false)
case wait
when :polynomially_longer, Integer, Float, ActiveSupport::Duration, Proc
# Supported wait type, continue.
else
raise ArgumentError, "Unsupported argument type for :wait, expected an Integer, Float, " \
"ActiveSupport::Duration, Proc, or :polynomially_longer, but got #{wait.inspect}"
end
# ... existing logic ...
end
エラーメッセージは具体的に期待される型と実際に受け取ったオブジェクトを示すため、開発者が修正箇所を特定しやすくなっています。determine_delay の else 節は残して防御的フォールバックとし、将来的な拡張に備えています。
テストも同時に拡充され、無効な型で ArgumentError が発生すること、および全てのサポート型が例外なしで受け入れ可能であることを検証しています。
test "retry_on raises ArgumentError for an unsupported wait type" do
error = assert_raises(ArgumentError) do
Class.new(ActiveJob::Base) do
retry_on StandardError, wait: "soon"
end
end
assert_match(/Unsupported argument type for :wait/, error.message)
end
test "retry_on accepts all supported wait types" do
assert_nothing_raised do
Class.new(ActiveJob::Base) do
retry_on StandardError, wait: 5
retry_on StandardError, wait: 2.5
retry_on StandardError, wait: 5.minutes
retry_on StandardError, wait: :polynomially_longer
retry_on StandardError, wait: ->(executions) { executions * 2 }
end
end
end
結論: 変更は型チェックを retry_on に集中させ、既存の遅延算出ロジックを極力変更せずに、エラーメッセージの明確化と早期失敗を実現しています。
設計判断
この PR では「ロード時に検証」という方針が取られました。retry_on がクラス定義時に実行されるため、無効な wait: があればすぐに例外がスローされ、アプリケーションの起動フェーズで問題が露呈します。これにより、実行時エラーによる不透明なスタックトレースを回避できます。
型チェックを追加する実装は case 文だけで済んでおり、既存の API 互換性に影響を与えません。wait: に従来通り true/false などは受け付けないため、誤用のリスクは低減します。一方、determine_delay のフォールバックは残しているため、将来的に新しい型が導入された際にも安全に拡張可能です。
結論: 最小限のコード追加でエラーハンドリングを改善し、開発者体験を向上させる設計選択であると言えます。
まとめ
retry_on の wait: 引数に対する型バリデーションをクラス定義時に実施することで、無効な入力が即座に ArgumentError として通知されます。これにより、ジョブ実行中に内部エラーが混入する問題が解消され、デバッグが容易になります。変更は既存ロジックへの影響を最小化しつつ、今後の拡張性も確保しています。