modify: 协程调度器
This commit is contained in:
@@ -461,6 +461,31 @@ impl QueueOperation {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_expired_messages(&self, days: i64) -> PyResult<()> {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
let expire_time = now - Duration::days(days);
|
||||
let iso_format = expire_time.to_rfc3339();
|
||||
|
||||
let conn = self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
UPDATE messages
|
||||
SET is_deleted = 1
|
||||
WHERE is_deleted = 0
|
||||
AND expire_time IS NOT NULL
|
||||
AND expire_time < ?1
|
||||
",
|
||||
params![iso_format],
|
||||
)
|
||||
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clean_expired_messages(&self) -> PyResult<()> {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
let iso_format = now.to_rfc3339();
|
||||
|
||||
@@ -2,4 +2,4 @@ from .message_priority import MessagePriority
|
||||
from .message_status import MessageStatus
|
||||
from .message_type import MessageType
|
||||
|
||||
__all__ = ["MessagePriority", "MessageStatus", "MessageType"]
|
||||
__all__ = ["MessagePriority", "MessageStatus", "MessageType"]
|
||||
|
||||
@@ -7,4 +7,3 @@ class MessageStatus(IntEnum):
|
||||
COMPLETED = 2
|
||||
FAILED = 3
|
||||
RETRYING = 4
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@ from enum import Enum
|
||||
|
||||
|
||||
class MessageType(Enum):
|
||||
TASK = "task" # 任务
|
||||
LISTEN = "listen" # 监听
|
||||
TASK = "task" # 任务
|
||||
LISTEN = "listen" # 监听
|
||||
|
||||
@@ -44,6 +44,7 @@ class core:
|
||||
def delete_message(self, message_id: str): ...
|
||||
def clean_old_messages(self, days: int): ...
|
||||
def clean_expired_messages(self): ...
|
||||
def remove_expired_messages(self, days: int): ...
|
||||
|
||||
class TaskMounter:
|
||||
def __init__(self, task_class: type[TaskMounter]): ...
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,97 @@
|
||||
from ..model import MessageItem
|
||||
from ..constant import MessageStatus
|
||||
import json
|
||||
import asyncio
|
||||
import functools
|
||||
|
||||
|
||||
def retry_async(max_retries=3, delay=1):
|
||||
"""
|
||||
异步重试装饰器
|
||||
|
||||
Args:
|
||||
max_retries: 最大重试次数
|
||||
delay: 重试间隔(秒)
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
last_exception = None
|
||||
# 使用message_item中的retry_count作为最大重试次数
|
||||
retries = getattr(self.message_item, "retry_count", max_retries)
|
||||
|
||||
# 至少尝试一次(retries+1),最多尝试max_retries+1次
|
||||
actual_retries = min(retries, max_retries) if max_retries > 0 else retries
|
||||
|
||||
for attempt in range(actual_retries + 1):
|
||||
try:
|
||||
return await func(self, *args, **kwargs)
|
||||
except Exception as e:
|
||||
last_exception = e
|
||||
if attempt < actual_retries:
|
||||
await asyncio.sleep(delay)
|
||||
else:
|
||||
break
|
||||
if last_exception:
|
||||
raise last_exception
|
||||
else:
|
||||
raise Exception("Unknown error occurred during async task execution")
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class AsyncTaskCycle:
|
||||
def __init__(self, message_item: MessageItem, callback):
|
||||
self.message_item = message_item
|
||||
self.callback = callback
|
||||
self.task_result = None
|
||||
self.task_status = None
|
||||
self.task_error = None
|
||||
|
||||
@retry_async(max_retries=3, delay=1)
|
||||
async def run(self):
|
||||
try:
|
||||
task_result = await self.callback(self.message_item) # type: ignore
|
||||
except Exception as e:
|
||||
self.task_result = None
|
||||
self.task_status = MessageStatus.FAILED
|
||||
self.task_error = str(e)
|
||||
else:
|
||||
self.task_result = task_result
|
||||
self.task_status = MessageStatus.COMPLETED
|
||||
self.task_error = None
|
||||
|
||||
def get_task_result(self):
|
||||
if isinstance(self.task_result, (dict, list)):
|
||||
try:
|
||||
return json.dumps(self.task_result)
|
||||
except:
|
||||
return json.dumps({"result": str(self.task_result)})
|
||||
|
||||
elif isinstance(self.task_result, str):
|
||||
try:
|
||||
json.loads(self.task_result)
|
||||
return self.task_result
|
||||
except:
|
||||
return json.dumps({"result": self.task_result})
|
||||
elif isinstance(self.task_result, (int, float, bool)):
|
||||
return json.dumps({"result": self.task_result})
|
||||
elif self.task_result is None:
|
||||
return "null"
|
||||
else:
|
||||
return json.dumps({"result": str(self.task_result)})
|
||||
|
||||
def get_task_status(self):
|
||||
return self.task_status
|
||||
|
||||
def get_task_error(self):
|
||||
return self.task_error
|
||||
|
||||
def get_task_message_item(self):
|
||||
return self.message_item
|
||||
|
||||
def get_task_callback(self):
|
||||
return self.callback
|
||||
@@ -3,6 +3,50 @@ from typing import Callable, Optional
|
||||
from ..constant import MessageStatus
|
||||
import json
|
||||
|
||||
import functools
|
||||
import time
|
||||
|
||||
|
||||
def retry_sync(max_retries=3, delay=1):
|
||||
"""
|
||||
同步重试装饰器
|
||||
|
||||
Args:
|
||||
max_retries: 最大重试次数
|
||||
delay: 重试间隔(秒)
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
last_exception = None
|
||||
# 使用message_item中的retry_count作为最大重试次数
|
||||
retries = getattr(self.message_item, "retry_count", max_retries)
|
||||
|
||||
# 至少尝试一次(retries+1),最多尝试max_retries+1次
|
||||
actual_retries = min(retries, max_retries) if max_retries > 0 else retries
|
||||
|
||||
for attempt in range(actual_retries + 1):
|
||||
try:
|
||||
return func(self, *args, **kwargs)
|
||||
except Exception as e:
|
||||
last_exception = e
|
||||
if attempt < actual_retries:
|
||||
time.sleep(delay)
|
||||
else:
|
||||
break
|
||||
|
||||
# 如果没有捕获到异常,则创建一个新的异常
|
||||
if last_exception is not None:
|
||||
raise last_exception
|
||||
else:
|
||||
raise Exception("Unknown error occurred during sync task execution")
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class TaskCycle:
|
||||
def __init__(self, message_item: MessageItem, callback: Optional[Callable]):
|
||||
self.message_item = message_item
|
||||
@@ -11,15 +55,16 @@ class TaskCycle:
|
||||
self.task_status = None
|
||||
self.task_error = None
|
||||
|
||||
@retry_sync(max_retries=3, delay=1)
|
||||
def run(self):
|
||||
try:
|
||||
task_result = self.callback(self.message_item) # type: ignore
|
||||
task_result = self.callback(self.message_item) # type: ignore
|
||||
except Exception as e:
|
||||
self.task_result = None
|
||||
self.task_status = MessageStatus.FAILED
|
||||
self.task_error = str(e)
|
||||
else:
|
||||
self.task_result = task_result # type: ignore
|
||||
self.task_result = task_result # type: ignore
|
||||
self.task_status = MessageStatus.COMPLETED
|
||||
self.task_error = None
|
||||
|
||||
@@ -28,31 +73,29 @@ class TaskCycle:
|
||||
try:
|
||||
return json.dumps(self.task_result)
|
||||
except:
|
||||
return json.dumps({'result': str(self.task_result)})
|
||||
return json.dumps({"result": str(self.task_result)})
|
||||
|
||||
elif isinstance(self.task_result, str):
|
||||
try:
|
||||
json.loads(self.task_result)
|
||||
return self.task_result
|
||||
except:
|
||||
return json.dumps({'result': self.task_result})
|
||||
return json.dumps({"result": self.task_result})
|
||||
elif isinstance(self.task_result, (int, float, bool)):
|
||||
return json.dumps({'result': self.task_result})
|
||||
return json.dumps({"result": self.task_result})
|
||||
elif self.task_result is None:
|
||||
return 'null'
|
||||
return "null"
|
||||
else:
|
||||
return json.dumps({'result': str(self.task_result)})
|
||||
return json.dumps({"result": str(self.task_result)})
|
||||
|
||||
def get_task_status(self):
|
||||
return self.task_status
|
||||
|
||||
def get_task_error(self):
|
||||
return self.task_error
|
||||
|
||||
|
||||
def get_task_message_item(self):
|
||||
return self.message_item
|
||||
|
||||
|
||||
def get_task_callback(self):
|
||||
return self.callback
|
||||
|
||||
|
||||
|
||||
@@ -19,12 +19,21 @@ class ListenOperation:
|
||||
def create_table(self):
|
||||
if len(self.listen_fields) == 0:
|
||||
return
|
||||
sql = f"""
|
||||
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,
|
||||
@@ -34,18 +43,28 @@ class ListenOperation:
|
||||
new_value TEXT,
|
||||
is_delete integer DEFAULT 0,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
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;
|
||||
)
|
||||
"""
|
||||
conn = self._get_connection()
|
||||
conn.executescript(sql)
|
||||
)
|
||||
|
||||
# 检查触发器是否已存在,如果不存在则创建
|
||||
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.execute("PRAGMA journal_mode=WAL;")
|
||||
conn.execute("PRAGMA synchronous=NORMAL;")
|
||||
conn.execute("PRAGMA cache_size=-20000;")
|
||||
@@ -54,15 +73,8 @@ class ListenOperation:
|
||||
conn.execute("PRAGMA busy_timeout=5000;")
|
||||
conn.commit()
|
||||
|
||||
# 删除原有数据
|
||||
# sql = f"""
|
||||
# DELETE FROM listen_table
|
||||
# """
|
||||
# conn.execute(sql)
|
||||
# conn.commit()
|
||||
|
||||
for listen_field in self.listen_fields:
|
||||
sql = f"""
|
||||
sql = """
|
||||
INSERT INTO
|
||||
listen_table (key, value)
|
||||
VALUES
|
||||
|
||||
@@ -120,3 +120,8 @@ class QueueOperation:
|
||||
def clean_expired_messages(self, shard_index: int):
|
||||
conn = self._get_shard_conn(shard_index)
|
||||
conn.clean_expired_messages()
|
||||
|
||||
# 彻底删除消息
|
||||
def remove_expired_messages(self, shard_index: int, remove_days: int = 30):
|
||||
conn = self._get_shard_conn(shard_index)
|
||||
conn.remove_expired_messages(remove_days)
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
from .standard import StandardQueueScheduler
|
||||
from ._async import AsyncQueueScheduler
|
||||
from .base import BaseScheduler
|
||||
from ..model import MessageItem
|
||||
from typing import Callable
|
||||
import multiprocessing
|
||||
|
||||
|
||||
SCHEDULER_TYPES = {"standard": StandardQueueScheduler, "async": AsyncQueueScheduler}
|
||||
|
||||
|
||||
class QueueScheduler(BaseScheduler):
|
||||
def __init__(
|
||||
self,
|
||||
receive_thread_num=1,
|
||||
task_thread_num=1,
|
||||
task_thread_num=multiprocessing.cpu_count() * 2,
|
||||
shard_num=4,
|
||||
scheduler_type="standard",
|
||||
):
|
||||
self.scheduler = None
|
||||
if scheduler_type == "standard":
|
||||
self.scheduler = StandardQueueScheduler(
|
||||
receive_thread_num, task_thread_num, shard_num
|
||||
)
|
||||
else:
|
||||
pass
|
||||
scheduler_class = SCHEDULER_TYPES.get(scheduler_type, None)
|
||||
if not scheduler_class:
|
||||
raise ValueError(f"Invalid scheduler type: {scheduler_type}")
|
||||
self.scheduler = scheduler_class(receive_thread_num, task_thread_num, shard_num)
|
||||
|
||||
if self.scheduler:
|
||||
self.queue_operation = self.scheduler.queue_operation
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
from .async_receive_scheduler import AsyncReceiveScheduler
|
||||
from .async_task_scheduler import AsyncTaskScheduler
|
||||
from .async_listen_data_scheduler import AsyncListenDataScheduler
|
||||
from ...model import MessageItem
|
||||
from ...queue_operation.listen_operation import ListenOperation
|
||||
from typing import Callable
|
||||
from ...queue_operation import QueueOperation
|
||||
import os
|
||||
from ..base import BaseScheduler
|
||||
from ..cleanup_scheduler import CleanupScheduler
|
||||
|
||||
|
||||
class AsyncQueueScheduler(BaseScheduler):
|
||||
|
||||
def __init__(self, receive_thread_num=1, task_thread_num=1, shard_num=4):
|
||||
self.queue_operation = QueueOperation(shard_num=shard_num)
|
||||
self.receive_scheduler = AsyncReceiveScheduler(
|
||||
self.queue_operation, receive_thread_num
|
||||
)
|
||||
self.listen_operation = ListenOperation(
|
||||
os.path.join(self.queue_operation.db_dir, "listen.db")
|
||||
)
|
||||
self.listen_operation.create_table()
|
||||
self.listen_scheduler = AsyncListenDataScheduler(self.listen_operation)
|
||||
self.task_scheduler = AsyncTaskScheduler(self.queue_operation, task_thread_num)
|
||||
self.cleanup_scheduler = CleanupScheduler(self.queue_operation)
|
||||
|
||||
def send_message(self, message: MessageItem, callback: Callable):
|
||||
self.receive_scheduler.send_message(message, callback)
|
||||
|
||||
def update_listen_data(self, key, value):
|
||||
self.listen_operation.update_listen_data(key, value)
|
||||
|
||||
def get_listen_datas(self):
|
||||
return self.listen_operation.get_values()
|
||||
|
||||
def get_listen_data(self, key):
|
||||
return self.listen_operation.get_value(key)
|
||||
|
||||
def start(self):
|
||||
self.receive_scheduler.start_receive_thread()
|
||||
self.task_scheduler.start_task_thread()
|
||||
|
||||
def stop(self):
|
||||
self.receive_scheduler.stop_receive_thread()
|
||||
self.task_scheduler.stop_task_thread()
|
||||
|
||||
|
||||
__all__ = ["AsyncQueueScheduler"]
|
||||
@@ -0,0 +1,67 @@
|
||||
from ...queue_operation.listen_operation import ListenOperation
|
||||
import asyncio
|
||||
from ...mounter.listen_mounter import ListenMounter
|
||||
import threading
|
||||
import multiprocessing
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
class AsyncListenDataScheduler:
|
||||
def __init__(self, listen_operation: ListenOperation):
|
||||
self.listen_operation = listen_operation
|
||||
self.is_running = False
|
||||
self.thread_num = multiprocessing.cpu_count()
|
||||
self.listen_thread = None
|
||||
|
||||
async def _process_listen_data(self, key, value, delete_id):
|
||||
listen_function = ListenMounter.get_Listener_function(key)
|
||||
if listen_function:
|
||||
if asyncio.iscoroutinefunction(listen_function):
|
||||
await listen_function(value)
|
||||
else:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
with concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=self.thread_num
|
||||
) as executor:
|
||||
await loop.run_in_executor(executor, listen_function, value)
|
||||
listen_function(value)
|
||||
except Exception as e:
|
||||
ValueError(f"Error in {key} listener function: {e}")
|
||||
finally:
|
||||
self.listen_operation.delete_change_log(delete_id=delete_id)
|
||||
|
||||
async def listen(self):
|
||||
async with asyncio.Semaphore(self.thread_num):
|
||||
while self.is_running:
|
||||
status, change_data_items = self.listen_operation.listen_data()
|
||||
tasks = []
|
||||
if status:
|
||||
for data in change_data_items:
|
||||
key = data[6]
|
||||
new_value = data[7]
|
||||
delete_id = data[0]
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
self._process_listen_data(key, new_value, delete_id)
|
||||
)
|
||||
)
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
else:
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
def start_listen_data(self):
|
||||
if self.is_running:
|
||||
return
|
||||
self.is_running = True
|
||||
self.listen_thread = threading.Thread(target=self.listen)
|
||||
self.listen_thread.start()
|
||||
|
||||
def stop_listen_data(self):
|
||||
if not self.is_running:
|
||||
return
|
||||
self.is_running = False
|
||||
if self.listen_thread and self.listen_thread.is_alive():
|
||||
self.listen_thread.join(timeout=2)
|
||||
@@ -0,0 +1,102 @@
|
||||
from ...queue_operation import QueueOperation
|
||||
from ...model import MessageItem
|
||||
from typing import Callable
|
||||
import asyncio
|
||||
import threading
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
class AsyncReceiveScheduler:
|
||||
def __init__(self, queue_operation: QueueOperation, receive_thread_num: int = 1):
|
||||
self.callbacks = dict()
|
||||
self.receive_thread_num = receive_thread_num
|
||||
self.is_running = False
|
||||
self.lock = threading.Lock()
|
||||
self.queue_operation = queue_operation
|
||||
self.receive_thread = None # 单一轮询线程
|
||||
|
||||
def send_message(self, message: MessageItem, callback: Callable = None): # type: ignore
|
||||
if callback is None:
|
||||
callback = lambda message: print(message)
|
||||
# self.queue_operation.enqueue(message.to_dict())
|
||||
self.queue_operation.enqueue(message.to_dict_by_core())
|
||||
with self.lock:
|
||||
self.callbacks[message.id] = callback
|
||||
|
||||
async def receive_message(self):
|
||||
"""单一轮询线程,并行执行回调"""
|
||||
|
||||
def callback_default(message):
|
||||
return message
|
||||
|
||||
async with asyncio.Semaphore(self.receive_thread_num):
|
||||
while self.is_running:
|
||||
message_list = self.queue_operation.get_completed_messages()
|
||||
tasks = []
|
||||
if message_list:
|
||||
for message in message_list:
|
||||
message = MessageItem.from_dict(message)
|
||||
with self.lock:
|
||||
callback = self.callbacks.pop(message.id, None)
|
||||
if callback is None:
|
||||
callback = callback_default
|
||||
|
||||
tasks.append(
|
||||
asyncio.create_task(self._safe_callback(callback, message))
|
||||
)
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
else:
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
async def _safe_callback(self, callback, message):
|
||||
"""安全执行回调函数"""
|
||||
try:
|
||||
# 检查回调函数是否是异步函数
|
||||
if asyncio.iscoroutinefunction(callback):
|
||||
await callback(message)
|
||||
else:
|
||||
# 如果是同步函数,则在执行器中运行以避免阻塞事件循环
|
||||
loop = asyncio.get_event_loop()
|
||||
with concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=self.receive_thread_num
|
||||
) as executor:
|
||||
await loop.run_in_executor(executor, callback, message)
|
||||
except Exception as e:
|
||||
print(f"回调执行错误: {str(e)}")
|
||||
finally:
|
||||
self.queue_operation.delete_message(message.id)
|
||||
|
||||
def _run_receive_loop(self):
|
||||
"""在新事件循环中运行接收消息循环"""
|
||||
# 创建新的事件循环
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
try:
|
||||
# 运行异步接收函数直到停止
|
||||
loop.run_until_complete(self.receive_message())
|
||||
except Exception as e:
|
||||
print(f"任务调度出错: {str(e)}")
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
def start_receive_thread(self):
|
||||
if self.is_running:
|
||||
return
|
||||
|
||||
self.is_running = True
|
||||
# 创建单一轮询线程
|
||||
self.receive_thread = threading.Thread(
|
||||
target=self._run_receive_loop, daemon=True
|
||||
)
|
||||
self.receive_thread.start()
|
||||
|
||||
def stop_receive_thread(self):
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
self.is_running = False
|
||||
# 等待轮询线程结束
|
||||
if self.receive_thread and self.receive_thread.is_alive():
|
||||
self.receive_thread.join(timeout=5.0)
|
||||
@@ -0,0 +1,137 @@
|
||||
from ...queue_operation import QueueOperation
|
||||
from ...model import MessageItem
|
||||
from ...constant import MessageStatus
|
||||
from ...cycle.async_task_cycle import AsyncTaskCycle
|
||||
from ...mounter.task_mounter import TaskMounter
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
|
||||
class AsyncTaskScheduler:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
queue_operation: QueueOperation,
|
||||
task_thread_num: int = multiprocessing.cpu_count() * 2,
|
||||
):
|
||||
self.task_thread_num = task_thread_num
|
||||
self.is_running = False
|
||||
self.queue_operation = queue_operation
|
||||
self.task_thread = None # 单一轮询线程
|
||||
|
||||
async def _process_message(self, message: MessageItem):
|
||||
"""处理单个消息"""
|
||||
try:
|
||||
if message.destination == "client":
|
||||
message.status = MessageStatus.COMPLETED
|
||||
message.result = {"result": "success"}
|
||||
message.updatetime = datetime.now()
|
||||
return message
|
||||
|
||||
task_function = TaskMounter.get_task_function(message.destination)
|
||||
if task_function is None:
|
||||
raise ValueError(
|
||||
f"Task function not found for destination: {message.destination}"
|
||||
)
|
||||
|
||||
task_cycle = AsyncTaskCycle(message, task_function)
|
||||
await task_cycle.run()
|
||||
|
||||
message.status = task_cycle.get_task_status() # type: ignore
|
||||
if message.status == MessageStatus.FAILED:
|
||||
message.result = {"error": task_cycle.get_task_error()}
|
||||
else:
|
||||
message.result = task_cycle.get_task_result() # type: ignore
|
||||
message.updatetime = datetime.now()
|
||||
except Exception as e:
|
||||
message.status = MessageStatus.FAILED
|
||||
message.result = {"error": str(e)}
|
||||
message.updatetime = datetime.now()
|
||||
|
||||
return message
|
||||
|
||||
def _process_messages(self, message):
|
||||
"""更新任务结果到数据库"""
|
||||
try:
|
||||
self.queue_operation.update_result(message.id, message.result)
|
||||
self.queue_operation.update_status(message.id, message.status)
|
||||
except Exception as e:
|
||||
print(f"任务结果更新失败: {str(e)}")
|
||||
|
||||
async def task_callback(self):
|
||||
"""单一轮询线程,并行执行任务"""
|
||||
# 使用信号量控制并发任务数
|
||||
semaphore = asyncio.Semaphore(self.task_thread_num)
|
||||
|
||||
async def limited_process_message(message):
|
||||
async with semaphore:
|
||||
return await self._process_message(MessageItem.from_dict(message))
|
||||
|
||||
while self.is_running:
|
||||
try:
|
||||
message_list = self.queue_operation.dequeue(
|
||||
size=self.task_thread_num * 2
|
||||
)
|
||||
if not message_list:
|
||||
# 使用指数退避策略减少轮询开销
|
||||
await asyncio.sleep(0.05) # 增加延迟减少CPU使用
|
||||
continue
|
||||
|
||||
# 并发处理消息
|
||||
tasks = [
|
||||
asyncio.create_task(limited_process_message(message))
|
||||
for message in message_list
|
||||
]
|
||||
|
||||
# 等待所有任务完成
|
||||
completed, _ = await asyncio.wait(
|
||||
tasks,
|
||||
return_when=asyncio.ALL_COMPLETED,
|
||||
timeout=30.0, # 添加超时防止无限等待
|
||||
)
|
||||
|
||||
# 批量处理结果
|
||||
for task in completed:
|
||||
try:
|
||||
message = task.result()
|
||||
self._process_messages(message)
|
||||
except Exception as e:
|
||||
print(f"处理任务结果时出错: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"任务调度出错: {str(e)}")
|
||||
await asyncio.sleep(0.1) # 出错时短暂延迟
|
||||
|
||||
def _run_task_loop(self):
|
||||
"""在新事件循环中运行任务处理循环"""
|
||||
# 创建新的事件循环
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
try:
|
||||
# 运行异步任务处理函数直到停止
|
||||
loop.run_until_complete(self.task_callback())
|
||||
except Exception as e:
|
||||
print(f"任务调度出错: {str(e)}")
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
def start_task_thread(self):
|
||||
if self.is_running:
|
||||
return
|
||||
|
||||
self.is_running = True
|
||||
self.task_thread = threading.Thread(target=self._run_task_loop)
|
||||
self.task_thread.daemon = True
|
||||
self.task_thread.start()
|
||||
|
||||
def stop_task_thread(self):
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
self.is_running = False
|
||||
if self.task_thread:
|
||||
self.task_thread.join(timeout=5.0)
|
||||
self.task_thread = None
|
||||
+12
-7
@@ -1,26 +1,31 @@
|
||||
# 新建清理调度器
|
||||
import threading
|
||||
import time
|
||||
from ...queue_operation import QueueOperation
|
||||
from ..queue_operation import QueueOperation
|
||||
|
||||
|
||||
class CleanupScheduler:
|
||||
def __init__(self, queue_operation: QueueOperation, interval_minutes=60):
|
||||
def __init__(
|
||||
self, queue_operation: QueueOperation, interval_minutes=60, remove_days=30
|
||||
):
|
||||
self.queue_operation = queue_operation
|
||||
self.interval = interval_minutes * 60 # 转换为秒
|
||||
self.is_running = False
|
||||
self.cleanup_thread = None
|
||||
self.remove_days = remove_days
|
||||
|
||||
for i in range(self.queue_operation.shard_num):
|
||||
# 清理过期但未处理的消息
|
||||
self.queue_operation.clean_expired_messages(i)
|
||||
# 彻底删除30天前的消息
|
||||
self.queue_operation.remove_expired_messages(i, self.remove_days)
|
||||
|
||||
def cleanup_expired_messages(self):
|
||||
"""清理过期消息"""
|
||||
while self.is_running:
|
||||
try:
|
||||
# 清理7天前的已完成/失败消息
|
||||
for i in range(self.queue_operation.shard_num):
|
||||
self.queue_operation.clean_old_messages(i, 7)
|
||||
|
||||
# 清理过期但未处理的消息
|
||||
for i in range(self.queue_operation.shard_num):
|
||||
# 清理过期但未处理的消息
|
||||
self.queue_operation.clean_expired_messages(i)
|
||||
|
||||
except Exception as e:
|
||||
@@ -5,7 +5,7 @@ from ...model import MessageItem
|
||||
from typing import Callable
|
||||
from ...queue_operation import QueueOperation
|
||||
from ...queue_operation.listen_operation import ListenOperation
|
||||
from .cleanup_scheduler import CleanupScheduler
|
||||
from ..cleanup_scheduler import CleanupScheduler
|
||||
import os
|
||||
from ..base import BaseScheduler
|
||||
|
||||
|
||||
@@ -2,13 +2,14 @@ from ...queue_operation.listen_operation import ListenOperation
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from ...mounter.listen_mounter import ListenMounter
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
|
||||
class ListenDataScheduler:
|
||||
def __init__(self, listen_operation: ListenOperation):
|
||||
self.listen_operation = listen_operation
|
||||
self.is_running = False
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
self.executor = ThreadPoolExecutor(max_workers=multiprocessing.cpu_count())
|
||||
self.listen_thread = None
|
||||
|
||||
def _process_listen_data(self, key, value, delete_id):
|
||||
|
||||
@@ -7,10 +7,15 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
import time
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
|
||||
class TaskScheduler:
|
||||
def __init__(self, queue_operation: QueueOperation, task_thread_num: int = 2):
|
||||
def __init__(
|
||||
self,
|
||||
queue_operation: QueueOperation,
|
||||
task_thread_num: int = multiprocessing.cpu_count() * 2,
|
||||
):
|
||||
self.task_thread_num = task_thread_num
|
||||
self.is_running = False
|
||||
self.executor = ThreadPoolExecutor(max_workers=task_thread_num) # 并行执行任务
|
||||
|
||||
Reference in New Issue
Block a user