This repository has been archived on 2026-10-02. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
NeteaseUrl/tests/test_auth_db_concurrency.py
T
snowy-yang c77ef40fd5 feat(db): 同步栈读写并发(_q 去锁)+ core.db 并发/跨loop 修复
auth_system._q:单语句读不再持 _DB_LOCK —— WAL 下每连接读一致性快照,
读写互不阻塞;旧实现把全部读也串行化且被写挡住。写事务(_x 与 24 个
with _DB_LOCK 块,经扫描均为写事务)仍全程串行,无写-写竞争。

core.db 并发修复(由测试暴露的真实缺陷):
- WAL 切换竞态:并发连接同时执行 PRAGMA journal_mode=WAL 争排他锁
  → 专用锁内串行切换 + 二次复查(busy_timeout 前置)
- 跨事件循环锁:模块级 asyncio.Lock 绑定首个 loop,多 loop 场景报
  "bound to a different event loop" → 锁按 loop 隔离

测试:25/25 通过(新增「读不被写锁阻塞」红转绿、读+同步写者并发 WAL、
异步并发读);
lint: ruff All checks passed;pyright 0 errors。
验收:冒烟 10/10、奇偶校验 2/2、uvicorn 真实启动 Flask 兜底+FastAPI 双通。
2026-09-26 01:03:18 +08:00

88 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""auth_system 同步栈的读写并发验证:读不持锁、写仍串行(WAL)。"""
import sqlite3
import threading
import time
import pytest
import auth_system
@pytest.fixture()
def temp_site(tmp_path, monkeypatch):
monkeypatch.setattr(auth_system, 'DATA_DIR', tmp_path)
monkeypatch.setattr(auth_system, 'DB_PATH', tmp_path / 'site.db')
monkeypatch.setattr(auth_system, 'BACKUP_DIR', tmp_path / 'backups')
monkeypatch.setattr(auth_system, 'SECRET_KEY_PATH', tmp_path / 'secret.key')
auth_system.init_db()
return tmp_path / 'site.db'
def test_q_does_not_hold_lock(temp_site):
"""_q 读不应获取 _DB_LOCK(否则读会被写挡住)。"""
# 让另一个线程持有写锁,读仍应在短时间返回(不被阻塞)
held = threading.Event()
release = threading.Event()
def holder():
with auth_system._DB_LOCK:
held.set()
release.wait(5)
t = threading.Thread(target=holder, daemon=True)
t.start()
held.wait(2)
try:
start = time.monotonic()
rows = auth_system._q('SELECT COUNT(*) AS n FROM users')
elapsed = time.monotonic() - start
assert rows is not None
assert elapsed < 1.0, f'读被写锁阻塞了 {elapsed:.2f}s(WAL 下不应发生)'
finally:
release.set()
t.join(2)
def test_concurrent_reads_and_writes(temp_site):
"""多读线程 + 一个写线程并发:无异常、结果一致。"""
errors = []
stop = threading.Event()
def writer():
try:
for i in range(50):
auth_system.set_setting(f'k{i}', str(i))
except Exception as e:
errors.append(('w', e))
finally:
stop.set()
def reader():
while not stop.is_set():
try:
auth_system.get_setting('k0')
except Exception as e:
errors.append(('r', e))
ws = threading.Thread(target=writer, daemon=True)
rs = [threading.Thread(target=reader, daemon=True) for _ in range(4)]
ws.start()
for r in rs:
r.start()
ws.join(20)
for r in rs:
r.join(2)
assert not errors, f'并发读写异常: {errors[:3]}'
assert auth_system.get_setting('k49') == '49'
def test_wal_enabled(temp_site):
conn = sqlite3.connect(str(temp_site))
try:
mode = conn.execute('PRAGMA journal_mode').fetchone()[0]
finally:
conn.close()
assert str(mode).lower() == 'wal'