« ^ »

EmacsのサブプロセスをGraceful Shutdownする

所要時間: 約 3分

AWS ECSの停止の挙動

ECSは以下の順序でタスクを終了する。

  1. コンテナで動作しているプロセスにSIGTERMを送信する。
  2. 30秒待つ。
  3. プロセスが停止していなかったらSIGKILLを送信する。

この挙動は変更できないらしい。SIGKILLを送信するまでの秒数を長くしたりはできない。

停止処理をshellで雑に書く

shell scriptで記述するとこのようになる。

  TARGET_PID=
  kill -TERM $TARGET_PID
  sleep 30
  kill -KILL $TARGET_PID

今回はEmacs上からこのような処理をさせる実装を行う。

Emacsでプロセスを停止させる関数

Emacsでプロセスを停止させる関数はいくつかある。

  • delete-process
  • kill-process
  • stop-process
  • process-send-eof
  • signal-process

今回はシグナルを指定して送信した処理にしたいので signal-process を利用する。

プロセスのリストを取得する

process-list 関数はプロセスのリストを返す。

(process-list)

中身を確認すると実際にprocessのlistであることがわかる。


  (type-of (car (process-list)))
process

プロセスをinteractiveで選択する

ミニバッファで一覧から選択させる場合 completing-read 関数を使う。 https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffer-Completion.html


  (defun example (procname)
    (interactive (list
                  (completing-read "Process: " (mapcar #'process-name (process-list)))))
    (print (get-process procname)))

プロセスにシグナルを送信する

list-processesのtabulated-modeから停止できるように拡張していく。

PSIXシグナル

今回はSIGTERMとSIGKILLを使用する。

シグナル番号
SIGTERM15
SIGKILL9

Emacsでそれぞれのシグナルを表すシンボルは用意されていないが数字よりも シグナル名で記述できたほうがわかりやすくなるため定義しておく。


  (setq
   SIGKILL 9
   SIGTERM 15)

使うものだけを定義した。

https://linuxjm.osdn.jp/html/LDP_man-pages/man7/signal.7.html

シグナルを送信する


  (signal-process proc signal)

実行中のプロセスにシグナルを送信する

実行中のプロセスにシグナルを送信する関数を書くと次のようになる。


  (defun process-graceful-shutdown-send-signal-running-process (proc signal-number)
    "実行中のプロセスにシグナルを送信する。"
    (when (eq (process-status proc) 'run)
      (signal-process proc signal-number)))

タイマーを利用して30秒後にSIGKILLを送信する

30秒後にSIGKILLを送信する処理を行うためタイマーを利用する。プロセスが 生存していればSIGKILLを送信し、生存していなければ何もしない関数を作成 する。


  (run-at-time 30 nil #'process-graceful-shutdown-send-signal-running-process proc SIGKILL)
30秒後にSIGKILLを送信する

list-processesのプロセス一覧からポイントしているプロセスを取得する

list-processesで表示されるプロセス一覧はtabulated-list-modeで実装されている。 カレントバッファがtabulated-list-modeの場合 tabulated-list-get-id 関数でidを取得できる。 プロセス一覧のバッファの場合、idはプロセス名になっている。

つまり以下のようなlispでプロセス自体を取得できる。


  (get-process (tabulated-list-get-id))

ちなみにtabulated-list-modeではない場合 (tabulated-list-get-id) を評価するとnilを返す。

今回はminiバッファでの選択時のデフォルト値にそれを設定したい。 completing-read 関数は引数でデフォルト値をするようにし、コマンドとして呼び出す関数 process-graceful-shutdown を実装した。


  (defun process-graceful-shutdown (proc)
    "Graceful shutdownする。"
    (interactive (list 
                  (completing-read "Process: " (mapcar #'process-name (process-list))
                                   nil nil nil nil (tabulated-list-get-id))))
    (process-graceful-shutdown-send-signal-running-process proc SIGTERM)
    (run-at-time 30 nil #'process-graceful-shutdown-send-signal-running-process proc SIGKILL))

最終的な実装

最終的に以下のような実装になった。

  (setq
   SIGKILL 9
   SIGTERM 15)

  (defun process-graceful-shutdown-send-signal-running-process (proc signal)
    "実行中のプロセスにシグナルを送信する。"
    (when (eq (process-status proc) 'run)
      (signal-process proc signal)))
  

  (defun process-graceful-shutdown (proc)
    "Graceful shutdownする。"
    (interactive (list 
                  (completing-read "Process: " (mapcar #'process-name (process-list))
                                   nil nil nil nil (tabulated-list-get-id))))
    (process-graceful-shutdown-send-signal-running-process proc SIGTERM)
    (run-at-time 30 nil #'process-graceful-shutdown-send-signal-running-process proc SIGKILL))

複雑なことはしてないのでシンプルな実装になった。

パッケージ化するほどのものでもないのでinit.elにでも書いてしまうことにする。