From 439241dd83964adc37c6549ef9e3c9275f9864f4 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 30 Oct 2026 12:18:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=B2=E8=A1=8C=E6=89=A7=E8=A1=8C=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=8D=87=E7=BA=A7=E9=81=BF=E5=85=8D=E5=A4=9A?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E5=90=AF=E5=8A=A8=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logistics_chat/db.py | 2 ++ tests/test_storage.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/logistics_chat/db.py b/logistics_chat/db.py index 1937715..fbe7d08 100644 --- a/logistics_chat/db.py +++ b/logistics_chat/db.py @@ -37,6 +37,8 @@ def init_db(): Path(get_setting('DATABASE')).parent.mkdir(parents=True, exist_ok=True) conn = connect() cur = conn.cursor() + # Serialize schema inspection and migration when multiple workers start together. + cur.execute('BEGIN IMMEDIATE') cur.execute(''' CREATE TABLE IF NOT EXISTS chat_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/tests/test_storage.py b/tests/test_storage.py index 230616a..7483502 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -6,6 +6,8 @@ from pathlib import Path import tempfile import time import unittest +from concurrent.futures import ThreadPoolExecutor +from threading import Barrier from flask import Flask from werkzeug.datastructures import FileStorage @@ -29,6 +31,27 @@ class StorageTests(unittest.TestCase): self.addCleanup(self.context.pop) db.init_db() + def test_concurrent_startup_migrates_legacy_history_once_without_claiming_it(self): + connection = db.connect() + connection.executescript('DROP TABLE chat_history; CREATE TABLE chat_history ' + '(id INTEGER PRIMARY KEY, title TEXT NOT NULL, ctime TEXT NOT NULL);' + "INSERT INTO chat_history VALUES(1,'旧记录','now');") + connection.close() + gate = Barrier(4) + + def initialize(): + with self.app.app_context(): + gate.wait(timeout=5) + db.init_db() + + with ThreadPoolExecutor(max_workers=4) as workers: + for result in workers.map(lambda _: initialize(), range(4)): + self.assertIsNone(result) + connection = db.connect() + self.assertEqual(connection.execute('SELECT title, visitor_id FROM chat_history').fetchall(), [('旧记录', None)]) + self.assertEqual(connection.execute('SELECT COUNT(*) FROM messages WHERE conv_id=1').fetchone()[0], 1) + connection.close() + def test_old_schema_is_migrated_and_legacy_title_backfilled(self): connection = db.connect() connection.executescript('DROP TABLE chat_history; CREATE TABLE chat_history '