FastAPI(Uvicorn)を使ってローカル開発をしている際、サーバーを止めようと Ctrl + C を押したのに無反応で、やむを得ず Ctrl + Z で終了させたことはありませんか?

その後、再度 uvicorn app:app --reload で起動しようとすると、以下のようなエラーに遭遇します。

ERROR: [Errno 48] Address already in use

今回は、このエラーが発生する仕組みと、一撃で解決するためのコマンド、そしてなぜ Ctrl + C が効かなくなるのかについてまとめました。

スポンサーリンク

1. エラーの原因:Ctrl + Z と Ctrl + C の決定的な違い

実は、サーバーを止める際のキーボードショートカットによって、プロセスの状態が大きく異なります。

  • Ctrl + C(正しい終了): プロセスに SIGINT(割り込み信号)を送り、使用していたポートを綺麗に開放して完全に終了させます。
  • Ctrl + Z(一時停止): プロセスを終了させず、バックグラウンドで一時停止(スリープ)状態にします。

Ctrl + Z を使うと、Uvicornは「ポート(デフォルトは 8000)を掴んだまま眠っている」状態になります。その状態で新しくUvicornを立ち上げようとするため、「そのポートはすでに使われています(Address already in use)」と怒られてしまうわけです。

2. 解決策:裏で眠っているプロセスを一撃で仕留める

このエラーを解消するには、ポート(8000 番)を占有して眠っている古いプロセスを強制終了する必要があります。

ターミナルで以下のコマンドを実行するだけで、一撃でポートを開放できます。

kill -9 $(lsof -t -i:8000)

コマンドの簡単な解説

  • lsof -t -i:8000:8000番ポートを使用しているプロセスの PID(プロセスID)のみを抽出 します。
  • $( ... ):抽出したPIDを、外側の kill コマンドに引き渡します。
  • kill -9:対象のプロセスを 即座に強制終了 します。

実行後、再度 uvicorn app:app --reload を叩くと、何事もなかったかのように正常に起動します。

スポンサーリンク

3. そもそも、なぜ最初の Ctrl + C が効かなかったのか?

「いや、最初に Ctrl + C を押したけど反応しなかったから、仕方なく Ctrl + Z を押したんだ」というケースも多いはずです。Uvicorn(特に --reload モード時)で Ctrl + C が無視される主な原因は以下の3つです。

--reload による子プロセスのスタック

Uvicornを --reload で起動すると、ファイル監視用の「親プロセス」と、アプリ実行用の「子プロセス」の2つが動きます。

コード内の無限ループや、データベース接続(DBコネクション)のクローズ処理、WebSocketなどの非同期処理がスタックすると、子プロセスが終了命令を無視してフリーズし、親プロセスごと巻き込んで無反応になります。

② 同期処理(def)によるイベントループのブロック

FastAPIで async def ではなく通常の def を使い、その中で極めて重い処理(時間のかかるファイルI/Oや重い計算)を実行すると、Uvicornのイベントループ全体がロックされます。

このロック状態の間は、Ctrl + C というシステム信号すら受け付けることができません。

③ 大量の例外ログ出力によるターミナルの過負荷

コードのエラーによってバックグラウンドで高速にエラーログが吐き出され続けている場合、ターミナルの描画処理が追いつかず、キーボード入力を受け付ける余裕がなくなってしまうことがあります。

4. 次回から「Ctrl + C」が効かなくなったときの回避策

もしまた Ctrl + C でサーバーが止まらなくなったら、Ctrl + Z を押す前に以下の方法を試してみてください。

  1. Ctrl + \ を試すCtrl + C(SIGINT: 優しく終了)が効かなくても、Ctrl + \(SIGQUIT: 即時強制終了)ならプロセスをその場で叩き潰して終了できることがあります。
  2. 新しいターミナルタブを開いて Kill するフリーズしたタブはそのままに、Cmd + T で新しいタブを開き、先ほどの kill -9 $(lsof -t -i:8000) を実行するのが最も確実で安全です。

まとめ

  • サーバーが止まらないときは、一時停止の Ctrl + Z ではなく Ctrl + \別タブからの kill -9 を使う。
  • ポートが塞がって起動しなくなったら kill -9 $(lsof -t -i:8000) で一発解決。


この記事が気に入ったら『目黒で働く分析担当の作業メモ』ご支援をお願いします!

※OFUSEに飛びます


おすすめの記事