◆ PM2 の --no-treekill が動かない
◆ pm2 stop コマンドのときにつけても意味がなかった
◆ 起動時の pm2 start のときにつけないといけない
◆ 終了方法やその時の状況で選べない

以前 PM2 を使ったときに困った部分の 1 つに no-treekill が効かないというのがありました
4.5 になってそろそろ直ってるかなと試してみると 相変わらずだったのですが ソースコードまで見てみると想定していたのとは使い方が違うようでした

常に treekill される

PM2 でプロセスを停止したいときに

pm2 stop index.js

で停止すると index.js が起動したサブプロセスも含めて treekill されます
基本的にはそれでいいのですが 通常の kill コマンドみたいにそのプロセスだけを止めたいことがあります
index.js がサブプロセスで処理を実行していて そっちが途中で強制終了されると面倒なことになるので 実行した以上は最後までやってほしいとかです

PM2 のオプションを見ていると --no-treekill というオプションがあります
これをつけると treekill されなくなるようなので

pm2 stop index.js --no-treekill

としてみましたが 特に効果はなく treekill されます
オプションの場所にも意味がありそうなので

pm2 stop index.js --no-treekill
pm2 stop --no-treekill index.js
pm2 --no-treekill stop index.js

といろいろ試しましたが効果ははなかったです

当時 issue を探すと 2 つか 3 つくらいこの treekill の問題が原因らしき issue がありました
しかし 放置されたままで 最近になって探すと誰も反応がなく bot が強制クローズしていました

原因

ソースコードを見てみると treekill の分岐を行っているのは 「lib/God/Methods.js」 の God.killProcesses 関数の中です
まずはここの最初で pm2_env がどうなっているのか見てみます

[lib/God/Methods.js]
  God.killProcess = function(pid, pm2_env, cb) {
// add
require("fs").appendFileSync("/tmp/log", JSON.stringify(pm2_env) + "\n")

if (!pid) return cb({msg : 'no pid passed or null'});

pm2_env の中身は多すぎたので省略しますが treekill は true でした
一応 --no-treekill を入れています
正しく設定できていないようですね

ここが呼び出されるまでのスタックを見てみると

stack:
at Object.God.killProcess (/usr/local/lib/node_modules/pm2/lib/God/Methods.js:209:46)
at God.stopProcessId (/usr/local/lib/node_modules/pm2/lib/God/ActionMethods.js:330:9)
at Server.onmessage (/usr/local/lib/node_modules/pm2/node_modules/pm2-axon-rpc/lib/server.js:104:6)
at RepSocket.emit (events.js:315:20)
at RepSocket.EventEmitter.emit (domain.js:483:12)
at Parser.<anonymous> (/usr/local/lib/node_modules/pm2/node_modules/pm2-axon/lib/sockets/rep.js:51:15)
at Parser.emit (events.js:315:20)
at Parser.EventEmitter.emit (domain.js:506:15)
at Parser._write (/usr/local/lib/node_modules/pm2/node_modules/amp/lib/stream.js:91:16)
at doWrite (_stream_writable.js:403:12)

実質上の 2 行だけみたいです

PM2 では 常に動いている God Daemon と pm2 コマンドのプロセスは別物です
コマンドのプロセスは God Daemon と通信して コマンドの命令を送り 実行結果を表示して終わります
God Daemon 側はコマンドからの命令を受け取って処理を実行しますが その受け取ったデータをパースして命令に応じた関数を呼び出すまでの処理がスタック表示の下の方です

呼び出し元が ActionMethods.js ファイルとわかったのでこの呼び出し元を見てみます

[lib/God/ActionMethods.js]
  God.stopProcessId = function(id, cb) {
if (typeof id == 'object' && 'id' in id)
id = id.id;

if (!(id in God.clusters_db))
return cb(God.logAndGenerateError(id + ' : id unknown'), {});

var proc = God.clusters_db[id];

ここの 「proc.pm2_env.treekill」 に treekill の情報が入っています
しかし clusters_db から id で取得するのみで 「pm2 stop」 コマンドのときに渡したオプションを反映しているようには見えません

この clusters_db に入ってる proc がどこで作られるのかを探すと プロセスの起動時でした

[lib/God.js]
    /**
* Fork mode logic
*/
God.forkMode(env_copy, function forkMode(err, clu) {
if (cb && err) return cb(err);
if (err) return false;

var old_env = God.clusters_db[clu.pm2_env.pm_id];
if (old_env) old_env = null;

God.clusters_db[env_copy.pm_id] = clu;

となると 「pm2 stop」 のときではなく 「pm2 start」 の時に指定すれば動いたりするのでしょうか……

pm2 start

pm2 start index.js --no-treekill

で起動して

pm2 stop index.js

で停止してみました

すると…… treekill は false でサブプロセスは生き延びました

使い方が違ってたようですが 終了のしかたを起動時に指定するのはなんか変に感じます
Windows の taskkill だって treekill をするかどうかは停止時に /T をつけるかどうかで決まります
実際にその設定を使うのは stop のときですし そのときに指定するのが普通だと思います
事前に指定すると stop するときの状況で切り替えができません
stop なら完全に treekill だけど restart のときは treekill しないとかもできなくなります

実際にあったケースだと PM2 で起動したプロセスが PM2 モジュールをロードして自身や別プロセスを操作します
基本は treekill なのですが 操作によっては shellscript をバックグラウンド実行した直後にその終了を待たずに自分を再起動させる処理があります
shellscript は動き続けないといけないので treekill されては困ります
PM2 は JavaScript 以外のプロセスも実行できたはずなので PM2 で shellscript を autorestart しない設定で起動してしまえば treekill の影響は受けないので treekill されることを防げますが そのために PM2 で起動するのもイマイチな方法に思います
停止系オプションは停止コマンドで指定できるようになってほしいですね