プロセスが死んでも残る — いちばん賢くない方法で
5 分目標MCP サーバー講座 5 編目。待ち人数が変数にあればプロセスと一緒に消える。ファイルに書くのだが、毎回すべてを書き直す。部分更新より遅い代わりに、書きかけの記録が残らない。
4 編目まで待ち人数はこう生きていた。
var waiting = 3;プロセスが死ねば 3 に戻る。デスクの画面を抜き差しすれば行列が無かったことになる。
書き留める
const _statePath = 'desk-state.json';
int _load() {
final f = File(_statePath);
if (!f.existsSync()) return 3;
final m = jsonDecode(f.readAsStringSync()) as Map<String, dynamic>;
return (m['waiting'] as num).toInt();
}
void _save(int waiting) =>
File(_statePath).writeAsStringSync(jsonEncode({'waiting': waiting}));そして値が変わるときに呼ぶ。
waiting -= n;
_save(waiting);なぜ全体を書き直すのか
値ひとつが変わるのにファイル全体を書き直す。非効率だ。ところがこの方式には性質がひとつある。
書きかけの記録が生まれない。 書き込みが起きたか起きなかったかのどちらかだ。途中で死ねば古いファイルがそのまま残る。
部分更新はそうではない。レコードひとつをその場で直していて死ねば どちらでもないファイル が残り、次の起動でパースが壊れる。そこからは復旧が仕事になる。
記録が数万件になればこの方式は使えない。そこが DB を使う場所だ。ただしそれまではこれがいちばん安全だ。
どの層に置くか
状態が置かれうる場所は三つある。
| 層 | 複数の画面が同じ値を見るか | プロセスが死ぬと |
|---|---|---|
| 画面の中 | いいえ — 画面ごとに別の値 | 消える |
| サーバープロセス | はい | 消える |
| サーバーの外(ファイル・DB) | はい | 残る |
4 編目までは二つ目だった。この編が三つ目へ移す。
決めなければ二つ目になる。 そしてそれが問題になる日はたいてい夜だ。
検証 — 実際にプロセスを殺す
永続は「保存した」を見てはいけない。切って繋ぎ直して読まねばならない。
rm -f desk-state.json
ask step5 '...{"name":"desk.admit","arguments":{"count":2}}}' >/dev/null # プロセス 1
ask step5 '...{"name":"desk.state","arguments":{}}}' > captures/s5.txt # プロセス 2
grep -q 'waiting.*1' captures/s5.txt || die "the count did not survive a restart"ask は毎回新しいプロセスを立てる。最初の呼び出しが終われば、そのプロセスは死ぬ。
step5 admitted 2, process ended, a new process still reads waiting 1サーバーのログにも残る。
course step5: up, state in desk-state.json (waiting=3)
course step5: up, state in desk-state.json (waiting=1)二度目の起動が 1 から始まっている。
自分で動かす
dart run bin/step5.dart
bash verify.sh持ち帰るもの
_load/_saveの二つ — ファイルが無いときの既定値も含めて- 全体の書き直し — 書きかけの記録を作らない
- 再起動の往復検査 — 保存ログではなく、新しいプロセスの読み取りで確かめる
次の編
値が変わったことを画面に知らせる。問わせずに。
サンプルを実行する
makemind-academy/course_server/git clone https://github.com/makemind-academy/course_server cd course_server dart pub get dart run bin/step5.dart