fix: implement async database startup lock and refactor db configuration
This commit is contained in:
+73
-8
@@ -1,6 +1,9 @@
|
|||||||
|
import asyncio
|
||||||
import glob
|
import glob
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
from tortoise import Tortoise
|
from tortoise import Tortoise
|
||||||
|
|
||||||
@@ -8,18 +11,80 @@ from core.logger import logger
|
|||||||
from core.settings import data_root
|
from core.settings import data_root
|
||||||
|
|
||||||
|
|
||||||
async def init_db():
|
_DB_FILE = os.path.join(data_root, "filecodebox.db")
|
||||||
try:
|
_STARTUP_LOCK_FILE = os.path.join(data_root, "filecodebox.startup.lock")
|
||||||
# 使用正确的Tortoise初始化配置格式
|
|
||||||
db_config = {
|
|
||||||
"db_url": f"sqlite://{data_root}/filecodebox.db",
|
def get_db_config() -> dict:
|
||||||
"modules": {"models": ["apps.base.models"]},
|
return {
|
||||||
|
"connections": {
|
||||||
|
"default": {
|
||||||
|
"engine": "tortoise.backends.sqlite",
|
||||||
|
"credentials": {
|
||||||
|
"file_path": _DB_FILE,
|
||||||
|
"journal_mode": "WAL",
|
||||||
|
"busy_timeout": 10000,
|
||||||
|
"foreign_keys": "ON",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"models": {
|
||||||
|
"models": ["apps.base.models"],
|
||||||
|
"default_connection": "default",
|
||||||
|
}
|
||||||
|
},
|
||||||
"use_tz": False,
|
"use_tz": False,
|
||||||
"timezone": "Asia/Shanghai"
|
"timezone": "Asia/Shanghai",
|
||||||
}
|
}
|
||||||
|
|
||||||
await Tortoise.init(**db_config)
|
|
||||||
|
|
||||||
|
def _lock_file(file_obj: IO[str]) -> None:
|
||||||
|
if os.name == "nt":
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
# Windows 需要锁定至少 1 字节
|
||||||
|
if os.fstat(file_obj.fileno()).st_size == 0:
|
||||||
|
file_obj.write("0")
|
||||||
|
file_obj.flush()
|
||||||
|
msvcrt.locking(file_obj.fileno(), msvcrt.LK_LOCK, 1)
|
||||||
|
else:
|
||||||
|
import fcntl
|
||||||
|
|
||||||
|
fcntl.flock(file_obj.fileno(), fcntl.LOCK_EX)
|
||||||
|
|
||||||
|
|
||||||
|
def _unlock_file(file_obj: IO[str]) -> None:
|
||||||
|
if os.name == "nt":
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
msvcrt.locking(file_obj.fileno(), msvcrt.LK_UNLCK, 1)
|
||||||
|
else:
|
||||||
|
import fcntl
|
||||||
|
|
||||||
|
fcntl.flock(file_obj.fileno(), fcntl.LOCK_UN)
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def db_startup_lock():
|
||||||
|
os.makedirs(data_root, exist_ok=True)
|
||||||
|
lock_file = open(_STARTUP_LOCK_FILE, "a+", encoding="utf-8")
|
||||||
|
try:
|
||||||
|
await asyncio.to_thread(_lock_file, lock_file)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
await asyncio.to_thread(_unlock_file, lock_file)
|
||||||
|
lock_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def init_db():
|
||||||
|
try:
|
||||||
|
db_config = get_db_config()
|
||||||
|
|
||||||
|
if not Tortoise._inited:
|
||||||
|
await Tortoise.init(config=db_config)
|
||||||
|
|
||||||
|
async with db_startup_lock():
|
||||||
# 创建migrations表
|
# 创建migrations表
|
||||||
await Tortoise.get_connection("default").execute_script("""
|
await Tortoise.get_connection("default").execute_script("""
|
||||||
CREATE TABLE IF NOT EXISTS migrates (
|
CREATE TABLE IF NOT EXISTS migrates (
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ from apps.admin.views import admin_api
|
|||||||
from apps.base.models import KeyValue
|
from apps.base.models import KeyValue
|
||||||
from apps.base.utils import ip_limit
|
from apps.base.utils import ip_limit
|
||||||
from apps.base.views import share_api, chunk_api, presign_api
|
from apps.base.views import share_api, chunk_api, presign_api
|
||||||
from core.database import init_db
|
from core.database import db_startup_lock, get_db_config, init_db
|
||||||
from core.logger import logger
|
from core.logger import logger
|
||||||
from core.response import APIResponse
|
from core.response import APIResponse
|
||||||
from core.settings import data_root, settings, BASE_DIR, DEFAULT_CONFIG
|
from core.settings import settings, BASE_DIR, DEFAULT_CONFIG
|
||||||
from core.tasks import delete_expire_files, clean_incomplete_uploads
|
from core.tasks import delete_expire_files, clean_incomplete_uploads
|
||||||
from core.utils import hash_password, is_password_hashed
|
from core.utils import hash_password, is_password_hashed
|
||||||
|
|
||||||
@@ -31,7 +31,8 @@ async def lifespan(app: FastAPI):
|
|||||||
# 初始化数据库
|
# 初始化数据库
|
||||||
await init_db()
|
await init_db()
|
||||||
|
|
||||||
# 加载配置
|
# 加载配置(多进程下串行化启动写操作)
|
||||||
|
async with db_startup_lock():
|
||||||
await load_config()
|
await load_config()
|
||||||
app.mount(
|
app.mount(
|
||||||
"/assets",
|
"/assets",
|
||||||
@@ -97,15 +98,7 @@ app.add_middleware(
|
|||||||
# 使用 register_tortoise 来添加异常处理器
|
# 使用 register_tortoise 来添加异常处理器
|
||||||
register_tortoise(
|
register_tortoise(
|
||||||
app,
|
app,
|
||||||
config={
|
config=get_db_config(),
|
||||||
"connections": {"default": f"sqlite://{data_root}/filecodebox.db"},
|
|
||||||
"apps": {
|
|
||||||
"models": {
|
|
||||||
"models": ["apps.base.models"],
|
|
||||||
"default_connection": "default",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
generate_schemas=False,
|
generate_schemas=False,
|
||||||
add_exception_handlers=True,
|
add_exception_handlers=True,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user