ターミナルマルチプレクサのサーバーを更新するとき、稼働中のペインが問題になります。畳めば動いているプロセスは全部死ぬので、更新のたびに作業を止めることになります。
herdr には「ライブハンドオフ」という仕組みがあります。稼働中のサーバーに、ライブなペインを新しいサーバーへ引き渡させる機能です。ペインのプロセスは動いたまま、サーバーだけが入れ替わります。
手元では 0.7.5(protocol 17)→ 0.8.0(protocol 19)の差し替えを含めて複数回使っていて、ペインのプロセスは動いたまま保たれています。
なお Homebrew で入れている場合はひと工夫必要で、それは後半に書きます。
なぜサーバーの更新が問題になるのか
herdr は tmux と同じく、ヘッドレスサーバーがペインのプロセスを所有し、クライアントがそこにアタッチする構成です。
herdr status --json
{
"client": { "version": "0.8.0", "protocol": 19 },
"server": { "version": "0.8.0", "protocol": 19, "compatible": true,
"restart_needed": false, "capabilities": { "live_handoff": true } }
}
client は今 PATH にあるバイナリ、server は動いているサーバーです。両者は独立しているので、バイナリを新しくしてもサーバーは古いまま動き続けます。
そして両者のプロトコルにバージョン差が出ると compatible が false、restart_needed が true になり、CLI がサーバーに繋がらなくなります。素直に直すならサーバーの再起動ですが、そうすると全ペインのプロセスが死ぬ — というのが問題の構図です。
herdr の復元経路のうち、プロセスを生かせるのはどれか
herdr には状態を保つ経路が複数あります(session-state に整理されています)。混同しやすいので、プロセスが生き続けるかという 1 点で並べてみます。
| 経路 | サーバーは止まるか | ペインのプロセスは生きるか |
|---|---|---|
| デタッチ / 再アタッチ | 止まらない | 生きる |
| スナップショット復元 | 止まる | 死ぬ(ワークスペース・タブ・cwd・レイアウトだけ戻る) |
| ペイン画面履歴のリプレイ | 止まる | 死ぬ(直近の画面内容だけ戻る) |
| ライブハンドオフ | 交換される | 生きる(対応する稼働中サーバーでベストエフォート) |
真ん中の 2 つは「古いサーバーが停止した後に状態を再構築する」経路です。よくできていて、ワークスペースやタブの形は戻り、設定によっては直近の画面内容も戻せます。
それでもプロセスは別物になります。走っていた開発サーバー、テスト、ビルド、長時間の処理は落ちます。見た目の状態が戻っても、その中で実行途中だった処理は戻りません。
ライブハンドオフだけが違います。これは状態を作り直すのではなく、今動いているプロセスをそのまま新しいサーバーに引き継がせる経路です。ペインの PTY とプロセス、永続メタデータ、プラグインやセッションの状態が交換境界をまたいで保たれます。
「サーバーを止めずに済む」だけならデタッチでも足ります。サーバーを交換してなお生かせるのはライブハンドオフだけ、というのがこの機能の位置です。
使い方
正規の入口はアップデートコマンドのオプションです。
herdr update --handoff
素の herdr update はデフォルトで通常の再起動 / 停止フローを使うので、オプトインが必要です(リモートアタッチの herdr --remote にも同じオプションがあります)。
なお、ライブハンドオフはドキュメント上実験的機能と位置づけられています。日常の更新手段として当てにするより、稼働セッションを救いたいときの手段だと思っておくのが良さそうです。
実行後の確認
アタッチ中のクライアントは切断されるので、herdr で再アタッチします。ペインのプロセスは生きたままです。herdr status --json の compatible が true、restart_needed が false になっていることを確認します。
サーバーログにも記録が出ます。
$ grep -i handoff ~/.config/herdr/herdr-server.log | tail -3
INFO herdr::server::handoff: handoff import ready panes=<n>
INFO herdr::server::headless: handoff import server started
INFO herdr::server::headless: live handoff completed; old server exiting
panes=<n> が引き継がれたペイン数で、live handoff completed が出れば完了です。ペインの一覧は herdr api snapshot でも確認できます(識別子キーは pane_id。id ではありません)。
失敗しても、旧サーバーがペインを保持したまま残ります(live handoff failed, but ... is still running with your panes.)。ロールバックされるので、最悪でも普通に再起動した場合と同じ状況になるだけです。試すコストは低い。
Homebrew / mise / Nix で入れている場合
ここが手元でハマった点でした。
パッケージマネージャーで入れている場合、herdr update --handoff は使えません。ドキュメントにこう書かれています(install 0.8.0)。
ライブハンドオフは Homebrew、mise、Nix のパッケージマネージャー経由のアップデートには適用されません。それらのインストールではパッケージマネージャーでアップデートし、新しいサーバーを使う準備ができたらその Herdr セッションを再起動してください。
「セッションを再起動」= ペインのプロセスが死ぬ、です。せっかくの機能が使えないように読めます。
ただ、この文の主語は「パッケージマネージャー経由のアップデート」です。Homebrew では herdr 自身のアップデーターが無効化されているので、それに紐づく --handoff も使えない — そういう話です。外れるのは herdr update・preview チャンネル・update --handoff というバイナリ管理に紐づく機能で、サーバー側のハンドオフ能力は生きています。brew で入れた環境でも capabilities.live_handoff は true を返します(冒頭の出力がそれです)。
そして、ハンドオフを直接叩く低レベルのサブコマンドがあります。
herdr server live-handoff --import-exe /opt/homebrew/bin/herdr \
--expected-protocol 19 --expected-version 0.8.0
--import-exe は「新しいサーバーとして起動してほしいバイナリ」で、Homebrew が置いたパスを渡せばアップデーター経路を通らずにハンドオフできます。--expected-protocol / --expected-version は新しいサーバーに期待する値で、herdr status --json の client 側からそのまま取れます(server 側は今動いている古い値)。確認手順は前節と同じです。
このサブコマンドは herdr server --help の一覧には出てきません。存在しないサブコマンド(herdr server __nosuch など)を叩いたときのフォールバックヘルプに載っています。実装コメントを読むと、CLI の互換性ガードを意図的にバイパスして不一致状態のサーバーに到達できるよう作られたプロトコル不一致からの復帰パスだと分かります(src/cli/server.rs#L204-L205)。まさに brew upgrade 後の状況向けですが、サポートされたアップグレード経路ではないことは意識しておくべきです。
ハンドオフで保たれないもの
「プロセスが生きる」といっても、すべてが無傷なわけではありません。
-
一時的な協調状態は切れる
保たれるのはペインの PTY とプロセス、永続メタデータなど「サーバーが所有する長寿命の状態」です。処理中の CLI / API リクエスト、wait、購読ストリーム、クライアントソケット、ペイン間メッセージは中断されます。クライアント側は再接続してリトライする前提です。 -
ペインプロセスは reparent される
旧サーバーの子プロセスだったペインのシェルは、ハンドオフ後 ppid 1 に付け替わります。 -
ログに
WARN PaneDied for unknown paneが出ることがある
ハンドオフ後にタブやワークスペースを閉じると出ます。close 自体はoutcome="ok"で完了し、snapshot にも反映されるので実害は確認できていません。旧サーバー由来の内部ペイン番号の帳簿ずれだと思われます。
まとめ
- ライブハンドオフは、herdr の復元経路の中で唯一「サーバーを交換してもペインのプロセスを生かす」もの。他の経路(スナップショット復元・画面履歴リプレイ)はプロセスが別物になる
- 正規の入口は
herdr update --handoff(実験的機能・オプトイン) - brew / mise / Nix 運用で外れるのはバイナリ管理に紐づく機能だけで、サーバーのハンドオフ能力は生きている。
herdr server live-handoff --import-exe <brew のパス>で叩ける - 失敗してもロールバックされ、旧サーバーがペインを保持して残る
長く動いているプロセスをペインに抱えているほど、「更新のためにセッションを畳む」コストは上がります。畳まずに済む経路があると知っているだけで、更新のハードルが下がります。
Refs
- herdr 公式サイト
- セッション状態と復元 — 復元経路の一覧。本文の表の出典
- Herdr のインストール(0.8.0 時点のドキュメント) — Homebrew / mise / Nix のアップデートに関する記述
-
src/cli/server.rs—live-handoffが CLI の互換性ガードをバイパスする実装 - homebrew/core の herdr formula — 独自 tap ではなく core に入っている