fix(chv-muse-r12fix-5): UsageLedger.record() 读-改-写原子化(P11)

R12 真机:4 线程 × 10 条记账 0 写错,但 verify_chain 返回
(False, first_bad=11),链 G、链 H 两次复现同一断点;根因是
record() 先 SELECT 链头、再算摘要、再 INSERT 提交,无锁无
事务,两线程读到同一 prev_hash 各写一条,链分叉。修法:
同一实例用 threading.Lock 串行化;SELECT 前 BEGIN IMMEDIATE
持库级写锁(WAL + busy_timeout 下等待而非立即失败),使另一
实例/进程读到的链头必是已提交最新值。record_warning 委托
record(),同款覆盖。新增并发测试:同款 4×10 复测两轮,
verify_chain 全真、40 条一条不丢(旧代码下该测试失败)。
This commit is contained in:
host committed 2026-11-08 04:32:35 +00:00
1 parent 0ebcdfb2a7
commit a18a530bfd
2 files changed
+66 -20

No files matched your search

+30
View File
@@ -288,6 +288,36 @@ class LedgerTestCase(unittest.TestCase):
self.assertNotIn("token", cols)
self.assertGreater(row_id, 0)
def test_concurrent_records_keep_chain_intact(self):
# round-12 修复轮(项 5,P11):R12 真机同款并发复测——
# 4 线程 × 10 条,0 写错,但旧 record() 读-改-写无锁,
# verify_chain 返回 (False, first_bad=11),链 G、链 H
# 两次复现同一断点。修后两轮都须全真、40 条一条不丢。
for _round in range(2):
with tempfile.TemporaryDirectory() as tmp:
db_path = _make_db(tmp)
ledger = UsageLedger(db_path)
errors = []
def _worker():
try:
for _ in range(10):
ledger.record("u1", "message", 1)
except Exception as e: # noqa: BLE001 - 汇总后断言
errors.append(e)
threads = [threading.Thread(target=_worker)
for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
self.assertEqual(errors, [])
self.assertEqual(ledger.sum_kind("u1", "message"), 40)
ok, bad = ledger.verify_chain("u1")
self.assertTrue(ok, bad)
self.assertIsNone(bad)
class WarningTestCase(unittest.TestCase):
def test_once_per_window_at_80_percent(self):