「fork: Resource temporarily unavailable」の対処 - プロセス上限
「fork: Resource temporarily unavailable」とは何が起きているのか?
結論: 新しいプロセス / スレッドを作る
fork(2)(またはclone(2))が、上限に達して一時的に拒否された状態。カーネルがEAGAINを返し、シェルやアプリは「Resource temporarily unavailable」と表示する。メモリ不足とは限らず、プロセス数・スレッド数の上限が原因のことが多い。
このエラーは、プログラムが子プロセスやスレッドを生成しようとした瞬間に出る。fork() / clone() が EAGAIN(errno 11)を返し、それが文字列化されて表示される。
$ ./myapp fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable bash: fork: Resource temporarily unavailable
シェル自身が出すこともあり、その場合は新しいコマンドすら起動できない。重要なのは、これがディスクやネットワークの問題ではない点だ。カーネルが管理する「同時に存在できるタスク数」の枠を使い切ったために、新規生成だけが弾かれている。既存プロセスは動き続けるため、症状は「新しいプロセスが作れない」に限定される。
前提(対象環境)
- OS: Ubuntu / 一般的な Linux(systemd 環境)
- 症状:
fork/cloneがResource temporarily unavailableで失敗する ulimit//proc/systemctlを参照できる前提(一部の恒久設定はsudo必須)
なぜ fork が EAGAIN で失敗するのか?
結論: 原因は「①ユーザー別のプロセス上限(
RLIMIT_NPROC=ulimit -u)②システム全体の PID / スレッド上限(pid_max/threads-max)③cgroup のタスク上限(systemdTasksMax)④メモリ枯渇」の 4 つに整理できる。多くは①か③の cgroup 系。
fork が EAGAIN を返す経路を上限別に並べると次のとおり。
| 原因 | 上限の正体 | 確認の起点 |
|---|---|---|
| ユーザー別プロセス上限 | RLIMIT_NPROC(ulimit -u) |
ulimit -u / ps -u <user> |
| システム全体の PID 上限 | kernel.pid_max |
/proc/sys/kernel/pid_max |
| システム全体のスレッド上限 | kernel.threads-max |
/proc/sys/kernel/threads-max |
| cgroup のタスク上限 | systemd TasksMax(pids cgroup) |
systemctl show -p TasksMax |
| メモリ枯渇 | プロセス構造体を割り当てられない | free -h / dmesg |
切り分けは 近い枠から外へ 進めるのが速い。まず「そのユーザー自身に課された上限(ulimit -u)」を見て、次に「サービスを束ねる cgroup の TasksMax」、最後に「システム全体の pid_max / threads-max」の順だ。Web アプリやコンテナでスレッドを大量に作る構成では、③の cgroup 上限に最初に当たることが多い。
ulimit -u は「プロセス数」と言われるが、Linux の内部ではスレッドも 1 タスクとして数える。マルチスレッドアプリでは「プロセス数は少ないのに上限に達する」ことがあるため、必ずスレッド単位で現在数を数える。
現在のプロセス / スレッド数と上限をどう確認するのか?
結論: まず
ulimit -uで上限、ps -eLf | wc -l(または対象ユーザー分)で現在数を数え、両者を突き合わせる。現在数が上限に張り付いていれば、その枠が犯人と確定する。
最初に、いま自分のシェルに効いている上限を見る。
$ ulimit -u
4096
次に、現在のタスク(スレッド込み)数を数える。ps -eLf の -L がスレッドを 1 行ずつ展開するため、fork/clone が数える単位と一致する。
# システム全体のスレッド総数(概算、ヘッダ1行込み) $ ps -eLf | wc -l # 特定ユーザーのスレッド数だけを数える $ ps -L -u www-data | wc -l
3987
上限 4096 に対して現在 3987 のように張り付いていれば、その枠を使い切っている。どのプロセスがタスクを量産しているかは、スレッド数(nlwp)で降順に並べると一目で分かる。
$ ps -eo pid,nlwp,user,comm --sort=-nlwp | head
PID NLWP USER COMMAND 2314 1820 www-data java 1190 214 mysql mysqld
NLWP(スレッド数)が突出したプロセスが原因の本体だ。意図せぬスレッドリーク(接続プールやワーカーの設定ミス)なら、上限を上げる前にアプリ側を疑う。
上限を上げる前に「正常な数なのに足りない」のか「リークで異常に増えている」のかを必ず見極める。リークを上限引き上げで覆い隠すと、いずれ同じ壁により高い位置で再発する。
ユーザー別の上限(ulimit -u / nproc)を直すには?
結論: 一時的には
ulimit -u <数>で広げられるが、ログイン全体に効かせるには/etc/security/limits.conf(またはlimits.d/)のnprocを設定する。デーモンには limits.conf が効かないため systemd 側の設定が必要。
まず現在のシェルだけ一時的に広げて、症状が消えるかを確認する。
# ソフトリミットをハードリミットの範囲で引き上げる $ ulimit -u 8192
恒久設定はログイン経路で異なる。対話ログイン(SSH / コンソール) には limits.conf が効く。
$ sudo nano /etc/security/limits.conf
# <domain> <type> <item> <value> www-data soft nproc 8192 www-data hard nproc 16384 * soft nproc 4096
soft は既定値、hard は引き上げの上限。これらは PAM の pam_limits.so 経由で適用されるため、設定後はログインし直す必要がある。なお Ubuntu では /etc/security/limits.d/*.conf に分割して置くのが推奨で、メインファイルより後に読まれる。
limits.conf が効くのは PAM を通るログインセッションだけ。systemd が起動するデーモン(Nginx・MySQL 等)には適用されない。サービスのプロセス上限は次のセクションの cgroup / systemd 設定で行う。ここを取り違えると「設定したのに直らない」に陥る。
cgroup / systemd の TasksMax が原因の場合は?
結論: systemd 管理下のサービスやログインセッションには cgroup の
pids.max(=TasksMax)が効く。limits.confを直しても変わらないなら、ほぼこれが犯人。systemctl show -p TasksMaxで現在値を確認し、ドロップインで引き上げる。
systemd は各サービス・各ユーザーセッションを cgroup で束ね、その中のタスク総数を TasksMax で制限する。まず効いている値を見る。
# 特定サービスの上限 $ systemctl show -p TasksMax nginx.service # システム既定値(UserTasksMax 等) $ systemctl show -p DefaultTasksMax
TasksMax=4915
DefaultTasksMax はカーネルの pid_max に対する割合(既定 15%)で決まることが多く、サービス個別指定が無ければこの既定値が効く。サービスの上限を上げるにはドロップインを作る。
$ sudo systemctl edit nginx.service
エディタに以下を書く([Service] セクションに TasksMax)。
[Service] TasksMax=infinity
保存後に反映する。
$ sudo systemctl daemon-reload $ sudo systemctl restart nginx.service $ systemctl show -p TasksMax nginx.service
ログインユーザー側の枠は user-<UID>.slice に対する UserTasksMax(/etc/systemd/logind.conf)で決まる。多数のプロセスを動かす対話ユーザーで詰まる場合はこちらも確認する。
TasksMax=infinity は「上限なし」を意味するが、無制限にするとリーク時にシステム全体(pid_max)を巻き込む。原因がリークでないと確認できた場合のみ使い、通常は必要数 + 余裕の具体値を設定するのが安全。
システム全体の上限(pid_max / threads-max)を調整するには?
結論: 全ユーザー合計でも枠が足りないなら
kernel.pid_maxとkernel.threads-maxを引き上げる。sysctlで一時変更し、/etc/sysctl.d/で恒久化する。多数のコンテナ / 大規模並行処理のホストで該当する。
システム全体の同時 PID 数とスレッド数の天井を確認する。
$ cat /proc/sys/kernel/pid_max $ cat /proc/sys/kernel/threads-max
4194304 65536
一時的に引き上げるには sysctl -w。恒久化は /etc/sysctl.d/ にファイルを置く。
# 一時変更(再起動で消える) $ sudo sysctl -w kernel.pid_max=4194304 $ sudo sysctl -w kernel.threads-max=131072 # 恒久化 $ echo 'kernel.pid_max = 4194304' | sudo tee /etc/sysctl.d/99-pids.conf $ sudo sysctl --system
pid_max は 64bit 環境で最大 4194304(約 419 万)まで設定できる。ただしスレッドはそれぞれカーネルメモリを消費するため、闇雲に上げるとメモリ側が先に枯渇する。threads-max の既定はメモリ量から自動計算されるので、引き上げ時は free -h で余力を確認する。
pid_max / threads-max を上げてもメモリが足りなければ、今度は fork が ENOMEM(Cannot allocate memory)で失敗する。EAGAIN が ENOMEM に変わったら、上限ではなくメモリが律速。OOM killer 発動時の対処 を参照。
緊急復旧:シェルすら fork できないときは?
結論: 新規コマンドが起動できない状態でも、シェルの組み込み(builtin)コマンドは
forkせずに動く。kill・exec・ジョブ制御を使い、外部コマンドを起動せずに暴走プロセスを止めて枠を空ける。
fork が枯渇していると ps や kill /usr/bin/kill のような外部コマンドすら起動できない。このときは新しいプロセスを作らない bash 組み込みだけで戦う。
# 組み込みの kill(外部 /bin/kill ではない)でジョブや PID にシグナル送信 $ kill %1 $ kill 2314
PID の特定も、外部コマンドを呼ばずに /proc を組み込みの echo・グロブで覗ける。
# 自分のプロセスの cmdline をグロブで列挙(外部コマンド不要) $ for p in /proc/[0-9]*; do echo "$p"; done
それでも収拾がつかなければ、exec で暴走の元になっている自分のセッションを置き換えるか、別の既存ログインセッション(まだ生きている SSH 等)から対処する。最終手段はコンソール / クラウドの管理画面からの再起動だが、原因(リーク元)を特定しないと再発する。
SSH が新規に繋がらなくなる前に、復旧用のセッションを 1 つ繋ぎっぱなしにしておくと安全。上限系のトラブルは「気づいたときには新規ログインも弾かれる」ため、作業用とは別の保険セッションが効く。
それでも直らないときのチェックリスト
結論: fork の EAGAIN は「タスク数の枠の枯渇」が確定した状態。ユーザー別
ulimit -u→ cgroupTasksMax→ システムpid_max/threads-maxの順に、近い枠から外へ確認すれば原因はこの層のどこかに収束する。リークが無いことの確認を忘れない。
- [ ] エラーは
EAGAIN(Resource temporarily unavailable)かENOMEM(Cannot allocate memory)か区別したか - [ ]
ulimit -uの値と、ps -Lでの現在スレッド数を突き合わせたか - [ ]
ps -eo pid,nlwp,... --sort=-nlwpでスレッドを量産している犯人プロセスを特定したか - [ ] それは正常な数か、それともスレッド / プロセスのリークか
- [ ] 対象はログインセッションか、systemd デーモンか(
limits.confかTasksMaxか)を切り分けたか - [ ]
systemctl show -p TasksMax <service>の値は十分か - [ ] 全体合計で足りないなら
kernel.pid_max/kernel.threads-maxを確認したか - [ ] 上限を上げた後、メモリ(
free -h)に余力があるか