refactor(database): isolate configuration transactions

This commit is contained in:
InfinityPacer
2026-08-23 02:29:22 +08:00
parent e99289a07b
commit 3d7fb44dc7
23 changed files with 1071 additions and 153 deletions
+119 -6
View File
@@ -1,6 +1,7 @@
import threading
import uuid
from concurrent.futures import ThreadPoolExecutor
from types import SimpleNamespace
import pytest
@@ -16,15 +17,105 @@ def _unique_key() -> str:
def _fresh_oper() -> SystemConfigOper:
"""重置单例并从数据库重新加载配置缓存"""
"""重置单例并显式加载数据库配置快照"""
Singleton._instances.pop((SystemConfigOper, (), frozenset()), None)
return SystemConfigOper()
oper = SystemConfigOper()
oper.load_snapshot()
return oper
def test_constructor_does_not_query_database(monkeypatch):
"""构造配置对象时不打开数据库会话。"""
Singleton._instances.pop((SystemConfigOper, (), frozenset()), None)
monkeypatch.setattr(
SystemConfig,
"list",
lambda _db: pytest.fail("构造阶段不应查询数据库"),
)
oper = SystemConfigOper()
with pytest.raises(RuntimeError, match="快照尚未加载"):
oper.get("key")
def test_load_snapshot_publishes_complete_dictionary(monkeypatch):
"""重新加载期间读取方只会看到完整旧快照或完整新快照。"""
Singleton._instances.pop((SystemConfigOper, (), frozenset()), None)
oper = SystemConfigOper()
values = [SimpleNamespace(key="key", value="old")]
entered = threading.Event()
release = threading.Event()
monkeypatch.setattr(SystemConfig, "list", lambda _db: values)
oper.load_snapshot()
def load_new_snapshot(_db):
entered.set()
release.wait(1)
return [SimpleNamespace(key="key", value="new")]
monkeypatch.setattr(SystemConfig, "list", load_new_snapshot)
thread = threading.Thread(target=oper.load_snapshot)
thread.start()
assert entered.wait(1)
assert oper.get("key") == "old"
release.set()
thread.join(1)
assert thread.is_alive() is False
assert oper.get("key") == "new"
def test_read_does_not_wait_for_slow_write_transaction(monkeypatch):
"""数据库写入期间,内存读取仍返回最近一次已提交值。"""
oper = _fresh_oper()
key = _unique_key()
oper.set(key, "old")
entered = threading.Event()
release = threading.Event()
def slow_write(_operation):
entered.set()
release.wait(1)
return True
monkeypatch.setattr(oper, "_execute_sync_write", slow_write)
thread = threading.Thread(target=lambda: oper.set(key, "new"))
thread.start()
assert entered.wait(1)
assert oper.get(key) == "old"
release.set()
thread.join(1)
assert thread.is_alive() is False
assert oper.get(key) == "new"
def test_failed_write_keeps_committed_snapshot(monkeypatch):
"""事务失败时内存快照保持最近一次已提交值。"""
oper = _fresh_oper()
key = _unique_key()
oper.set(key, "old")
def fail_write(_operation):
raise RuntimeError("write failed")
monkeypatch.setattr(oper, "_execute_sync_write", fail_write)
with pytest.raises(RuntimeError, match="write failed"):
oper.set(key, "new")
assert oper.get(key) == "old"
def test_increment_serializes_concurrent_counter_updates(monkeypatch):
"""并发递增系统计数时不应丢失更新。"""
oper = object.__new__(SystemConfigOper)
oper._rlock = threading.RLock()
oper._snapshot_lock = threading.RLock()
oper._write_lock = threading.RLock()
oper._loaded = True
stored_value = {"value": 0}
monkeypatch.setattr(oper, "get", lambda _key: stored_value["value"])
@@ -51,7 +142,9 @@ def test_increment_serializes_concurrent_counter_updates(monkeypatch):
def test_increment_supports_custom_step(monkeypatch):
"""整数系统计数应支持指定递增步长。"""
oper = object.__new__(SystemConfigOper)
oper._rlock = threading.RLock()
oper._snapshot_lock = threading.RLock()
oper._write_lock = threading.RLock()
oper._loaded = True
stored_value = {"value": 4}
monkeypatch.setattr(oper, "get", lambda _key: stored_value["value"])
@@ -95,7 +188,17 @@ async def test_async_set_persists_falsy_value_on_existing_record():
oper = _fresh_oper()
oper.set(key, True)
assert await oper.async_set(key, False) is True
from app.application.configuration import SystemConfigService
class _InlineDatabaseExecutor:
async def run(self, operation):
return operation()
service = SystemConfigService(
repository=oper,
async_executor=_InlineDatabaseExecutor(),
)
assert await service.async_set(key, False) is True
assert oper.get(key) is False
assert SystemConfig.get_by_key(oper._db, key).value is False
@@ -106,7 +209,17 @@ async def test_async_set_creates_record_for_falsy_value():
key = _unique_key()
oper = _fresh_oper()
assert await oper.async_set(key, 0) is True
from app.application.configuration import SystemConfigService
class _InlineDatabaseExecutor:
async def run(self, operation):
return operation()
service = SystemConfigService(
repository=oper,
async_executor=_InlineDatabaseExecutor(),
)
assert await service.async_set(key, 0) is True
assert oper.get(key) == 0
assert SystemConfig.get_by_key(oper._db, key).value == 0