进程死了也留得下 — 用最不聪明的办法
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可以带走的
_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