mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
fix(runtime): 隔离线程任务上下文 (#6420)
* fix(runtime): isolate thread task contexts * test: use canonical message schema import
This commit is contained in:
@@ -3,6 +3,7 @@ import threading
|
||||
import time
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from contextvars import Context, copy_context
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Optional, Protocol
|
||||
from urllib.parse import urlparse
|
||||
@@ -288,7 +289,11 @@ class BrowserSessionHelper:
|
||||
|
||||
executor = cls._get_existing_session_executor(session_key)
|
||||
if executor:
|
||||
future = executor.submit(
|
||||
context = copy_context()
|
||||
# 会话线程保持空底层上下文,每次操作只使用当前调用快照。
|
||||
future = Context().run(
|
||||
executor.submit,
|
||||
context.run,
|
||||
cls._run_session_task,
|
||||
session_key,
|
||||
cls._close_session_in_thread,
|
||||
@@ -387,7 +392,11 @@ class BrowserSessionHelper:
|
||||
for _ in range(2):
|
||||
executor = cls._get_session_executor(session_key)
|
||||
try:
|
||||
future = executor.submit(
|
||||
context = copy_context()
|
||||
# 会话线程保持空底层上下文,每次操作只使用当前调用快照。
|
||||
future = Context().run(
|
||||
executor.submit,
|
||||
context.run,
|
||||
cls._run_session_task,
|
||||
session_key,
|
||||
callback,
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
import threading
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from contextvars import Context, copy_context
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, Protocol
|
||||
@@ -225,7 +226,13 @@ async def run_agent_blocking(
|
||||
|
||||
await semaphore.acquire()
|
||||
try:
|
||||
future = _get_blocking_executor(bucket_name).submit(bound_call)
|
||||
context = copy_context()
|
||||
# 长期 worker 保持空底层上下文,每个任务只在自己的调用快照内运行。
|
||||
future = Context().run(
|
||||
_get_blocking_executor(bucket_name).submit,
|
||||
context.run,
|
||||
bound_call,
|
||||
)
|
||||
except Exception:
|
||||
semaphore.release()
|
||||
raise
|
||||
|
||||
@@ -7,7 +7,9 @@ import queue
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from contextvars import Context, copy_context
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from typing import Any, Literal, Optional, List, Dict, Protocol, Union
|
||||
from typing import Callable
|
||||
|
||||
@@ -959,8 +961,16 @@ class MessageQueueManager(metaclass=SingletonClass):
|
||||
if immediately or self._is_in_scheduled_time(datetime.now()):
|
||||
# _send 会执行具体渠道回调,可能包含网络 IO;放到 executor
|
||||
# 避免 async 调用方所在事件循环被同步发送阻塞。
|
||||
context = copy_context()
|
||||
call = partial(self._send, *args, **kwargs)
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, lambda: self._send(*args, **kwargs))
|
||||
# 默认执行器保持空底层上下文,渠道调用只使用当前消息快照。
|
||||
await Context().run(
|
||||
loop.run_in_executor,
|
||||
None,
|
||||
context.run,
|
||||
call,
|
||||
)
|
||||
return
|
||||
self.queue.put({
|
||||
"args": args,
|
||||
|
||||
@@ -6,7 +6,9 @@ import pickle
|
||||
import threading
|
||||
from collections import defaultdict, deque
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from contextvars import Context, copy_context
|
||||
from datetime import date, datetime
|
||||
from functools import partial
|
||||
from time import sleep
|
||||
from typing import Any, Callable, List, Optional, Tuple
|
||||
|
||||
@@ -371,14 +373,17 @@ class WorkflowExecutor:
|
||||
if not node_id:
|
||||
continue
|
||||
|
||||
# 提交任务到线程池,每个节点使用上下文快照,避免并行节点互相修改同一个对象。
|
||||
future = self.executor.submit(
|
||||
# 节点分别复制业务上下文和调用上下文,避免共享可变状态或丢失触发链路。
|
||||
context = copy_context()
|
||||
future = Context().run(
|
||||
self.executor.submit,
|
||||
context.run,
|
||||
self.execute_node,
|
||||
self.workflow.id,
|
||||
node_id,
|
||||
copy.deepcopy(self.context)
|
||||
copy.deepcopy(self.context),
|
||||
)
|
||||
future.add_done_callback(self.on_node_complete)
|
||||
future.add_done_callback(partial(context.run, self.on_node_complete))
|
||||
finally:
|
||||
self.executor.shutdown(wait=True, cancel_futures=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user