modify: qt调度器

This commit is contained in:
2025-11-04 11:17:59 +08:00
parent ac67b60d14
commit fa5db8e573
7 changed files with 293 additions and 370 deletions
@@ -5,13 +5,14 @@
@Time : 2025-09-27 17:02:18
@Author : chakcy
@Email : 947105045@qq.com
@description : 监听操作模块
@description : 监听操作模块 - 改进版,优化数据库连接
"""
from ..mounter.listen_mounter import ListenMounter
import sqlite3
from typing import List, Tuple, Union
import logging
class ListenOperation:
@@ -21,118 +22,167 @@ class ListenOperation:
self.create_table()
def _get_connection(self):
"""获取数据库连接"""
return sqlite3.connect(
self.db_dir,
check_same_thread=False,
"""获取数据库连接 - 改进版"""
conn = sqlite3.connect(
self.db_dir, check_same_thread=False, timeout=30.0 # 增加超时时间
)
# 设置SQLite优化参数
conn.execute("PRAGMA journal_mode=WAL;") # 使用WAL模式提高并发
conn.execute("PRAGMA synchronous=NORMAL;")
conn.execute("PRAGMA cache_size=-20000;")
conn.execute("PRAGMA busy_timeout=5000;") # 设置忙时超时
conn.execute("PRAGMA mmap_size=268435456;") # 256MB内存映射
return conn
def create_table(self):
if len(self.listen_fields) == 0:
return
conn = self._get_connection()
# 分别执行每个SQL语句而不是使用executescript
conn.execute(
"""
CREATE TABLE IF NOT EXISTS listen_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key Text,
value JSON
)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS change_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT,
row_id INTEGER,
column_name TEXT,
old_value TEXT,
new_value TEXT,
is_delete integer DEFAULT 0,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
"""
)
# 检查触发器是否已存在,如果不存在则创建
try:
# 分别执行每个SQL语句而不是使用executescript
conn.execute(
"""
CREATE TRIGGER IF NOT EXISTS track_value_change
AFTER UPDATE OF value ON listen_table
FOR EACH ROW
WHEN OLD.value <> NEW.value
BEGIN
INSERT INTO change_log (table_name, row_id, column_name, old_value, new_value)
VALUES ('listen_table', NEW.id, 'key', OLD.key, NEW.key);
END
CREATE TABLE IF NOT EXISTS listen_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key Text,
value JSON
)
"""
)
except sqlite3.Error:
# 如果触发器创建失败,我们继续执行其他操作
pass
conn.execute("PRAGMA journal_mode=WAL;")
conn.execute("PRAGMA synchronous=NORMAL;")
conn.execute("PRAGMA cache_size=-20000;")
conn.execute("PRAGMA mmap_size=1073741824;")
conn.execute("PRAGMA temp_store=MEMORY;")
conn.execute("PRAGMA busy_timeout=5000;")
conn.commit()
for listen_field in self.listen_fields:
sql = """
INSERT INTO
listen_table (key, value)
VALUES
(?, ?)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS change_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT,
row_id INTEGER,
column_name TEXT,
old_value TEXT,
new_value TEXT,
is_delete integer DEFAULT 0,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
"""
conn.execute(sql, (listen_field, "null"))
)
# 检查触发器是否已存在,如果不存在则创建
try:
conn.execute(
"""
CREATE TRIGGER IF NOT EXISTS track_value_change
AFTER UPDATE OF value ON listen_table
FOR EACH ROW
WHEN OLD.value <> NEW.value
BEGIN
INSERT INTO change_log (table_name, row_id, column_name, old_value, new_value)
VALUES ('listen_table', NEW.id, 'key', OLD.key, NEW.key);
END
"""
)
except sqlite3.Error:
# 如果触发器创建失败,我们继续执行其他操作
pass
conn.commit()
for listen_field in self.listen_fields:
# 检查是否已存在该key
cursor = conn.execute(
"SELECT id FROM listen_table WHERE key = ?", (listen_field,)
)
if cursor.fetchone() is None:
sql = """
INSERT INTO
listen_table (key, value)
VALUES
(?, ?)
"""
conn.execute(sql, (listen_field, "null"))
conn.commit()
except Exception as e:
logging.error(f"创建监听表失败: {str(e)}")
finally:
conn.close()
def listen_data(self) -> Tuple[bool, Union[List[Tuple], str]]:
sql = f"""
SELECT * FROM change_log where is_delete = 0 ORDER BY id DESC LIMIT 100
"""
"""获取监听数据 - 改进版"""
conn = self._get_connection()
result = conn.execute(sql).fetchall()
if len(result) == 0:
return False, "No data found"
return True, result
try:
sql = """
SELECT * FROM change_log where is_delete = 0 ORDER BY id DESC LIMIT 100
"""
result = conn.execute(sql).fetchall()
if len(result) == 0:
return False, "No data found"
return True, result
except Exception as e:
logging.error(f"获取监听数据失败: {str(e)}")
return False, str(e)
finally:
conn.close()
def delete_change_log(self, delete_id):
sql = f"""
DELETE FROM change_log WHERE id = {delete_id}
"""
"""删除变更日志 - 改进版"""
conn = self._get_connection()
conn.execute(sql)
try:
sql = """
DELETE FROM change_log WHERE id = ?
"""
conn.execute(sql, (delete_id,))
conn.commit()
except Exception as e:
logging.error(f"删除变更日志失败 {delete_id}: {str(e)}")
raise e # 重新抛出异常以便上层处理
finally:
conn.close()
def update_listen_data(self, key, value):
sql = f"""
UPDATE listen_table SET value = '{value}' WHERE key = '{key}'
"""
"""更新监听数据 - 改进版"""
conn = self._get_connection()
conn.execute(sql)
conn.commit()
try:
sql = """
UPDATE listen_table SET value = ? WHERE key = ?
"""
conn.execute(sql, (value, key))
conn.commit()
except Exception as e:
logging.error(f"更新监听数据失败 {key}: {str(e)}")
raise e
finally:
conn.close()
def get_value(self, key):
sql = f"""
SELECT value FROM listen_table WHERE key = '{key}'
"""
"""获取值 - 改进版"""
conn = self._get_connection()
result = conn.execute(sql).fetchone()
if result is None:
try:
sql = """
SELECT value FROM listen_table WHERE key = ?
"""
result = conn.execute(sql, (key,)).fetchone()
if result is None:
return None
return result[0]
except Exception as e:
logging.error(f"获取值失败 {key}: {str(e)}")
return None
return result[0]
finally:
conn.close()
def get_values(self):
sql = f"""
SELECT key, value FROM listen_table
"""
"""获取所有值 - 改进版"""
conn = self._get_connection()
result = conn.execute(sql).fetchall()
return result
try:
sql = """
SELECT key, value FROM listen_table
"""
result = conn.execute(sql).fetchall()
return result
except Exception as e:
logging.error(f"获取所有值失败: {str(e)}")
return []
finally:
conn.close()
+4 -2
View File
@@ -12,7 +12,7 @@ from .standard import StandardQueueScheduler
from ._async import AsyncQueueScheduler
from .base import BaseScheduler
from ..model import MessageItem, SchedulerConfig
from typing import Callable
from typing import Callable, Literal
import logging
@@ -29,11 +29,13 @@ except ImportError:
"Qt is not available, if you want to use it, please install PyQt5/PyQt6/PySide6"
)
SchedulerType = Literal["standard", "async", "qt"]
class QueueScheduler(BaseScheduler):
def __init__(
self,
scheduler_type="standard",
scheduler_type: SchedulerType = "standard",
config: SchedulerConfig = SchedulerConfig(),
):
scheduler_class = SCHEDULER_TYPES.get(scheduler_type, None)
+116 -51
View File
@@ -5,24 +5,31 @@
@Time : 2025-10-28 14:22:38
@Author : chakcy
@Email : 947105045@qq.com
@description : 监听调度器
@description : 监听调度器 - 改进版,解决数据库锁定问题
"""
import logging
import time
import sqlite3
from .listen_task import QtListenTask
from ...queue_operation.listen_operation import ListenOperation
from qtpy.QtCore import QThreadPool
from qtpy.QtCore import QThreadPool, QMutex, QMutexLocker
import threading
import time
class QtListenScheduler:
"""Qt 监听调度器 - 修复版"""
"""Qt 监听调度器 - 改进版,解决数据库锁定问题"""
def __init__(self, listen_operation: ListenOperation):
self.listen_operation = listen_operation
self.is_running = False
self.listen_thread = None
self.thread_pool = QThreadPool.globalInstance()
self.db_mutex = QMutex() # 数据库操作互斥锁
# 监听配置
self.poll_interval = 0.1 # 轮询间隔(秒)
self.max_retries = 5 # 最大重试次数
self.retry_delay = 0.2 # 重试延迟(秒)
# 确保监听表存在
self._ensure_listen_tables()
@@ -30,68 +37,126 @@ class QtListenScheduler:
def _ensure_listen_tables(self):
"""确保监听相关的表存在"""
try:
# 检查表是否存在,如果不存在则创建
conn = self.listen_operation._get_connection()
# 使用互斥锁保护数据库操作
with QMutexLocker(self.db_mutex):
# 检查表是否存在,如果不存在则创建
conn = self.listen_operation._get_connection()
# 检查 change_log 表是否存在
cursor = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='change_log'"
)
if cursor.fetchone() is None:
logging.info("创建缺失的 change_log 表")
conn.execute(
"""
CREATE TABLE change_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT,
row_id INTEGER,
column_name TEXT,
old_value TEXT,
new_value TEXT,
is_delete integer DEFAULT 0,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
"""
# 检查 change_log 表是否存在
cursor = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='change_log'"
)
conn.commit()
if cursor.fetchone() is None:
logging.info("创建缺失的 change_log 表")
conn.execute(
"""
CREATE TABLE change_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT,
row_id INTEGER,
column_name TEXT,
old_value TEXT,
new_value TEXT,
is_delete integer DEFAULT 0,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
"""
)
conn.commit()
except Exception as e:
logging.error(f"确保监听表存在失败: {str(e)}")
def _safe_listen_data(self):
"""安全地获取监听数据,带重试机制"""
for attempt in range(self.max_retries):
try:
# 使用互斥锁保护数据库操作
with QMutexLocker(self.db_mutex):
status, change_data_items = self.listen_operation.listen_data()
return status, change_data_items
except sqlite3.OperationalError as e:
if "database is locked" in str(e) and attempt < self.max_retries - 1:
logging.warning(
f"数据库锁定,第 {attempt + 1} 次重试获取监听数据..."
)
time.sleep(self.retry_delay)
else:
logging.error(f"获取监听数据失败: {str(e)}")
return False, []
except Exception as e:
logging.error(f"获取监听数据时发生未知错误: {str(e)}")
return False, []
return False, []
def _process_listen_data(self, change_data_items):
"""处理监听数据"""
processed_count = 0
for data in change_data_items:
try:
# 修正字段索引
if len(data) >= 8:
delete_id = data[0] # id
table_name = data[1] # table_name
row_id = data[2] # row_id
column_name = data[3] # column_name
old_value = data[4] # old_value
new_value = data[5] # new_value
# 使用线程池执行监听任务
task = QtListenTask(
column_name,
new_value,
int(delete_id),
self.listen_operation,
self.db_mutex,
self.max_retries,
self.retry_delay,
)
self.thread_pool.start(task) # type: ignore
processed_count += 1
except Exception as e:
logging.error(f"处理监听数据失败: {str(e)}")
return processed_count
def _listen_loop(self):
"""监听数据变化循环"""
"""监听数据变化循环 - 改进版"""
consecutive_errors = 0
max_consecutive_errors = 10
while self.is_running:
try:
status, change_data_items = self.listen_operation.listen_data()
if status:
for data in change_data_items:
try:
# 修正字段索引
if len(data) >= 8:
delete_id = data[0] # id
table_name = data[1] # table_name
row_id = data[2] # row_id
column_name = data[3] # column_name
old_value = data[4] # old_value
new_value = data[5] # new_value
status, change_data_items = self._safe_listen_data()
# 使用线程池执行监听任务
task = QtListenTask(
column_name,
new_value,
int(delete_id),
self.listen_operation,
)
self.thread_pool.start(task) # type: ignore
if status and change_data_items:
processed_count = self._process_listen_data(change_data_items)
consecutive_errors = 0 # 重置连续错误计数
except Exception as e:
logging.error(f"处理监听数据失败: {str(e)}")
if processed_count > 0:
logging.debug(f"成功处理 {processed_count} 个监听数据项")
elif not status:
# 没有数据或获取失败,短暂休眠
time.sleep(self.poll_interval)
consecutive_errors += 1
# 如果连续错误太多,延长休眠时间
if consecutive_errors >= max_consecutive_errors:
# logging.warning("连续多次获取监听数据失败,延长休眠时间")
time.sleep(self.poll_interval * 5)
consecutive_errors = 0 # 重置计数
else:
time.sleep(0.05)
# 有状态但没有数据,正常休眠
time.sleep(self.poll_interval)
consecutive_errors = 0
except Exception as e:
logging.error(f"监听循环错误: {str(e)}")
time.sleep(0.1)
consecutive_errors += 1
time.sleep(self.poll_interval * 2) # 出错时延长休眠
def start(self):
"""启动监听调度器"""
+36 -6
View File
@@ -5,28 +5,40 @@
@Time : 2025-10-28 14:30:41
@Author : chakcy
@Email : 947105045@qq.com
@description : 监听任务
@description : 监听任务 - 改进版,解决数据库锁定问题
"""
from queue_sqlite.queue_operation.listen_operation import ListenOperation
from ...mounter.listen_mounter import ListenMounter
import logging
from qtpy.QtCore import QRunnable
from qtpy.QtCore import QRunnable, QMutexLocker
import asyncio
from typing import Callable
import time
import sqlite3
class QtListenTask(QRunnable):
"""Qt 监听任务 - 修复版"""
"""Qt 监听任务 - 改进版,解决数据库锁定问题"""
def __init__(
self, key: str, value: str, delete_id: int, listen_operation: ListenOperation
self,
key: str,
value: str,
delete_id: int,
listen_operation: ListenOperation,
db_mutex,
max_retries=5,
retry_delay=0.2,
):
super().__init__()
self.key = key
self.value = value
self.delete_id = delete_id
self.listen_operation = listen_operation
self.db_mutex = db_mutex
self.max_retries = max_retries
self.retry_delay = retry_delay
self.setAutoDelete(True)
def run(self):
@@ -43,11 +55,29 @@ class QtListenTask(QRunnable):
except Exception as e:
logging.error(f"监听函数执行错误 {self.key}: {str(e)}")
finally:
# 删除变更日志
# 安全删除变更日志
self._safe_delete_change_log()
def _safe_delete_change_log(self):
"""安全删除变更日志,带重试机制"""
for attempt in range(self.max_retries):
try:
self.listen_operation.delete_change_log(self.delete_id)
# 使用互斥锁保护数据库操作
with QMutexLocker(self.db_mutex):
self.listen_operation.delete_change_log(self.delete_id)
break # 成功则退出循环
except sqlite3.OperationalError as e:
if "database is locked" in str(e) and attempt < self.max_retries - 1:
logging.debug(
f"删除变更日志时数据库锁定,第 {attempt + 1} 次重试..."
)
time.sleep(self.retry_delay)
else:
logging.error(f"删除变更日志失败 {self.delete_id}: {str(e)}")
break
except Exception as e:
logging.error(f"删除变更日志失败 {self.delete_id}: {str(e)}")
break
async def _run_async_listener(self, listen_function: Callable):
"""执行异步监听函数"""