`Configuration#clamp` の二重呼び出しを修正し、冪等性を保証
Launcher が Configuration#clamp を initialize と run の両方で呼び出していたバグを修正し、single {} / cluster {} ブロックのコールバックが二重実行される問題を解消しました。あわせて clamp に冪等性ガードを追加し、重複呼び出しをdeprecation warningとして検出できるようにしています。
背景
#3935 で報告されたこの問題の本質は、clamp が副作用を持つ処理を内包していることにあります。Configuration#clamp は内部で run_mode_hooks を呼び出しており、single {} や cluster {} ブロックに登録されたコールバックを実行します。Launcher#initialize でのクランプ後、Launcher#run でも再びクランプが呼ばれていたため、これらのコールバックが2回実行されていました。
initialize 側のクランプは必須です。直後の @config.options 参照が NotClampedError を発生させないようにするために必要であり、run 側の呼び出しは完全に冗長でした。initialize から run の間に行われる設定変更はすべて同一のミュータブルなオプションハッシュへの書き込みであり、クランプ前のDSL状態には影響しないため、run 側を削除しても安全です。
Launcher を生成する3つの呼び出し元(CLI、Rack::Handler::Puma、テストヘルパー)はいずれも initialize を経由するため、この修正でカバーされないパスは存在しません。
技術的な変更
変更は launcher.rb からの冗長コード削除と、configuration.rb への冪等性ガード追加の2点に集約されます。
Launcher#run から clamp 呼び出しを削除:
def run
previous_env = get_env
- @config.clamp
-
@config.plugins.fire_starts self
Configuration#clamp に二重呼び出し検出ガードを追加:
def clamp
if @clamped
warn "Configuration already clamped, calling clamp multiple times is deprecated and will raise in Puma v9"
return options
end
load unless @loaded
run_mode_hooks
set_conditional_default_options
# ...
end
@clamped フラグが立っている場合は warn を出力して即座に options を返すことで、Puma v9での raise への移行を段階的に案内します。この挙動を保証するテスト test_clamp_warns_if_called_twice と、v9でdeprecation warningをraiseに置き換えることを自動検出する test_double_clamp_is_removed_in_v9(Gem::Version を使ったバージョンアサーション)も追加されています。
あわせて、run_mode_hooks 内のエラーメッセージ先頭が小文字だった点を大文字に統一しています("cannot change..." → "Cannot change...")。
テストコードでは test_cli.rb の4箇所から手動 conf.clamp 呼び出しが削除されました。これらは Puma::CLI.new の内部で既にクランプが完了しているため不要だったものです。
設計判断
「削除」と「ガード」を組み合わせたアプローチが採用されました。根本的な修正は run からの削除のみで完結しますが、今後同様のコードが混入した際にサイレントに副作用が二重実行されるリスクに備え、clamp 自体を防御的にしています。
deprecation warning → Puma v9での raise という段階的な移行戦略は、既存のコードベースに手動 clamp 呼び出しが残っている場合でも互換性を保ちながら問題を顕在化させます。test_double_clamp_is_removed_in_v9 でバージョン条件チェックをテストとして記述しておくことで、v9リリース時にwarningをraiseへ変更する作業の見落としを防ぐ仕組みになっています。
まとめ
この修正は、clamp の冗長呼び出しという1行の削除に留まらず、設定の冪等性を @clamped フラグとdeprecation mechanismで制度として保証した変更です。Puma v9への移行パスを見据えたテストと段階的警告により、今後の重複呼び出しも安全に検出・排除できる基盤が整いました。