进程死了也留得下 — 用最不聪明的办法

5 分钟
目标MCP 服务端课程第五篇。等待人数放在变量里,会随进程一起消失。把它写进文件,而且每次都整份重写。比局部更新慢,但不会留下写了一半的记录。

到第四篇为止,等待人数是这么活着的:

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);

为什么整份重写

只变一个值,却把整个文件重写一遍。低效。可这个做法有一个性质。

不会产生写了一半的记录。 写要么发生了,要么没发生。中途死掉,旧文件原样还在。

局部更新不是这样。就地改一条记录时死掉,留下的是 两边都不是的文件,下次启动解析就崩。从那时起,恢复就变成一件工作。

记录到了几万条,这个办法就不能用了。那是该上数据库的位置。但在那之前,这是最安全的。

放在哪一层

状态可能待的地方有三层。

层 多块画面看的是同一个值吗 进程死了
画面里 不是 —— 每块画面各有各的值 没了
服务端进程 是 没了
服务端之外(文件、数据库) 是 还在

到第四篇为止是第二层。这一篇把它挪到第三层。

不定,就会落在第二层。 而它出事的那天,多半在晚上。

校验 —— 真的把进程杀掉

持久化不能靠看到「已保存」来确认。得 断开再接上去读。

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

可以带走的

  1. _load 与 _save 两个函数 — 包括文件不存在时的默认值
  2. 整份重写 — 不制造写了一半的记录
  3. 重启往返检查 — 用新进程读到的结果确认,而不是保存日志

下一篇

让画面知道值变了。不用它来问。

运行示例

makemind-academy/course_server/
git clone https://github.com/makemind-academy/course_server
cd course_server
dart pub get
dart run bin/step5.dart
在 GitHub 上打开 →