fix: 自动存档几乎永不触发的真 bug——把 dirty 和 unsaved 拆开

顺着验收网页修复时发现:default 牧场根本没写进库,内存里每次
重启都是新档。根因是 session.dirty 一个标志位伺二主:

* Publisher 推送循环每 ~250ms 就把 dirty 清掉(无人在线时直接清,
  "没人在看就不做序列化,省 CPU");
* autosave() 每 60s 才看一眼 dirty。

dirty=True 的窗口只有约 250ms,自动存档撞上它的概率约 0.4%——
等于永不存盘。进度只在手动存档/优雅停机时落盘,进程被强杀全丢。

修法:新增 session.unsaved 专管「有变化没落盘」,_on_change / load /
touch / advance_day 置位,只有成功的 save() 清位;autosave() 改看
unsaved;dirty 继续只管推送节流,hub.py 的推送循环一字未动。

两只旧测试直接操纵 dirty 驱动 autosave,固化了被修掉的耦合,改成
unsaved 表达意图;新增回归测试:推送循环清了 dirty 也要照常落盘、
没有新变化不重复写库。

真机验证:0 客户端在线纯 HTTP 种一块地,70 秒内库行更新(金币
120→112,种子钱都对得上)。515 项测试 OK。
This commit is contained in:
snowy-yang committed 2026-09-23 20:54:12 +08:00
1 parent 305ef0dcbe
commit ea346be484
2 files changed
+38 -7

No files matched your search

+16 -2
View File
@@ -85,6 +85,11 @@ class FarmSession:
self.engine.next_tick_at = time.time() + self.engine.tick_seconds
# 由 FarmClock 维护,Publisher 读取;用普通属性即可,都是原子读写
self.dirty = True
# unsaved 与 dirty 分开:dirty 管推送节流(Publisher 会频繁清它),
# unsaved 管「有变化还没落盘」,只有成功的 save() 才清。
# 之前两个概念共用 dirty,而推送循环 ~250ms 就清一次,自动存档
# 60s 才看一眼——窗口太小,autosave 几乎永远不触发。
self.unsaved = True
self.published_day = 0
self.last_events: list[str] = []
self.created_at = time.time()
@@ -100,6 +105,7 @@ class FarmSession:
def _on_change(self) -> None:
"""引擎的任何状态变化都会走到这里(可能来自任意线程)。"""
self.dirty = True
self.unsaved = True
def load(self) -> str:
state, message = load_state(self.db_path, self.farm_id)
@@ -112,17 +118,23 @@ class FarmSession:
self.load_message = f"{message},已开新档"
self.published_day = self.engine.state.day
self.dirty = True
self.unsaved = True
return self.load_message
def save(self) -> str:
with self._lock:
path = save_state(self.engine.state, self.db_path, self.farm_id)
self.saved_at = time.time()
self.unsaved = False
return path
def autosave(self) -> bool:
"""只在有变化时落盘,返回是否真的写了。"""
if not self.dirty or self.save_dir == "":
"""只在有未落盘的变化时写,返回是否真的写了。
看的是 ``unsaved`` 而不是 ``dirty``:dirty 是推送节流用的,
Publisher 每 ~250ms 就清一次,拿来当存盘依据等于永不存盘。
"""
if not self.unsaved or self.save_dir == "":
return False
if time.time() - self.saved_at < 5:
return False
@@ -142,12 +154,14 @@ class FarmSession:
if count_action:
self.actions_taken += 1
self.dirty = True
self.unsaved = True
def advance_day(self, reason: str = "时间流逝") -> list[str]:
with self._lock:
events = self.engine.advance_day(reason=reason)
self.last_events = events
self.dirty = True
self.unsaved = True
return events
def reset(self) -> None: