MemoryOS 2.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memoryos-2.0.3.dist-info/METADATA +418 -0
- memoryos-2.0.3.dist-info/RECORD +315 -0
- memoryos-2.0.3.dist-info/WHEEL +4 -0
- memoryos-2.0.3.dist-info/entry_points.txt +3 -0
- memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
- memos/__init__.py +20 -0
- memos/api/client.py +571 -0
- memos/api/config.py +1018 -0
- memos/api/context/dependencies.py +50 -0
- memos/api/exceptions.py +53 -0
- memos/api/handlers/__init__.py +62 -0
- memos/api/handlers/add_handler.py +158 -0
- memos/api/handlers/base_handler.py +194 -0
- memos/api/handlers/chat_handler.py +1401 -0
- memos/api/handlers/component_init.py +388 -0
- memos/api/handlers/config_builders.py +190 -0
- memos/api/handlers/feedback_handler.py +93 -0
- memos/api/handlers/formatters_handler.py +237 -0
- memos/api/handlers/memory_handler.py +316 -0
- memos/api/handlers/scheduler_handler.py +497 -0
- memos/api/handlers/search_handler.py +222 -0
- memos/api/handlers/suggestion_handler.py +117 -0
- memos/api/mcp_serve.py +614 -0
- memos/api/middleware/request_context.py +101 -0
- memos/api/product_api.py +38 -0
- memos/api/product_models.py +1206 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +477 -0
- memos/api/routers/server_router.py +394 -0
- memos/api/server_api.py +44 -0
- memos/api/start_api.py +433 -0
- memos/chunkers/__init__.py +4 -0
- memos/chunkers/base.py +24 -0
- memos/chunkers/charactertext_chunker.py +41 -0
- memos/chunkers/factory.py +24 -0
- memos/chunkers/markdown_chunker.py +62 -0
- memos/chunkers/sentence_chunker.py +54 -0
- memos/chunkers/simple_chunker.py +50 -0
- memos/cli.py +113 -0
- memos/configs/__init__.py +0 -0
- memos/configs/base.py +82 -0
- memos/configs/chunker.py +59 -0
- memos/configs/embedder.py +88 -0
- memos/configs/graph_db.py +236 -0
- memos/configs/internet_retriever.py +100 -0
- memos/configs/llm.py +151 -0
- memos/configs/mem_agent.py +54 -0
- memos/configs/mem_chat.py +81 -0
- memos/configs/mem_cube.py +105 -0
- memos/configs/mem_os.py +83 -0
- memos/configs/mem_reader.py +91 -0
- memos/configs/mem_scheduler.py +385 -0
- memos/configs/mem_user.py +70 -0
- memos/configs/memory.py +324 -0
- memos/configs/parser.py +38 -0
- memos/configs/reranker.py +18 -0
- memos/configs/utils.py +8 -0
- memos/configs/vec_db.py +80 -0
- memos/context/context.py +355 -0
- memos/dependency.py +52 -0
- memos/deprecation.py +262 -0
- memos/embedders/__init__.py +0 -0
- memos/embedders/ark.py +95 -0
- memos/embedders/base.py +106 -0
- memos/embedders/factory.py +29 -0
- memos/embedders/ollama.py +77 -0
- memos/embedders/sentence_transformer.py +49 -0
- memos/embedders/universal_api.py +51 -0
- memos/exceptions.py +30 -0
- memos/graph_dbs/__init__.py +0 -0
- memos/graph_dbs/base.py +274 -0
- memos/graph_dbs/factory.py +27 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/nebular.py +1794 -0
- memos/graph_dbs/neo4j.py +1942 -0
- memos/graph_dbs/neo4j_community.py +1058 -0
- memos/graph_dbs/polardb.py +5446 -0
- memos/hello_world.py +97 -0
- memos/llms/__init__.py +0 -0
- memos/llms/base.py +25 -0
- memos/llms/deepseek.py +13 -0
- memos/llms/factory.py +38 -0
- memos/llms/hf.py +443 -0
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +135 -0
- memos/llms/openai.py +222 -0
- memos/llms/openai_new.py +198 -0
- memos/llms/qwen.py +13 -0
- memos/llms/utils.py +14 -0
- memos/llms/vllm.py +218 -0
- memos/log.py +237 -0
- memos/mem_agent/base.py +19 -0
- memos/mem_agent/deepsearch_agent.py +391 -0
- memos/mem_agent/factory.py +36 -0
- memos/mem_chat/__init__.py +0 -0
- memos/mem_chat/base.py +30 -0
- memos/mem_chat/factory.py +21 -0
- memos/mem_chat/simple.py +200 -0
- memos/mem_cube/__init__.py +0 -0
- memos/mem_cube/base.py +30 -0
- memos/mem_cube/general.py +240 -0
- memos/mem_cube/navie.py +172 -0
- memos/mem_cube/utils.py +169 -0
- memos/mem_feedback/base.py +15 -0
- memos/mem_feedback/feedback.py +1192 -0
- memos/mem_feedback/simple_feedback.py +40 -0
- memos/mem_feedback/utils.py +230 -0
- memos/mem_os/client.py +5 -0
- memos/mem_os/core.py +1203 -0
- memos/mem_os/main.py +582 -0
- memos/mem_os/product.py +1608 -0
- memos/mem_os/product_server.py +455 -0
- memos/mem_os/utils/default_config.py +359 -0
- memos/mem_os/utils/format_utils.py +1403 -0
- memos/mem_os/utils/reference_utils.py +162 -0
- memos/mem_reader/__init__.py +0 -0
- memos/mem_reader/base.py +47 -0
- memos/mem_reader/factory.py +53 -0
- memos/mem_reader/memory.py +298 -0
- memos/mem_reader/multi_modal_struct.py +965 -0
- memos/mem_reader/read_multi_modal/__init__.py +43 -0
- memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
- memos/mem_reader/read_multi_modal/base.py +273 -0
- memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
- memos/mem_reader/read_multi_modal/image_parser.py +359 -0
- memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
- memos/mem_reader/read_multi_modal/string_parser.py +139 -0
- memos/mem_reader/read_multi_modal/system_parser.py +327 -0
- memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
- memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
- memos/mem_reader/read_multi_modal/user_parser.py +218 -0
- memos/mem_reader/read_multi_modal/utils.py +358 -0
- memos/mem_reader/simple_struct.py +912 -0
- memos/mem_reader/strategy_struct.py +163 -0
- memos/mem_reader/utils.py +157 -0
- memos/mem_scheduler/__init__.py +0 -0
- memos/mem_scheduler/analyzer/__init__.py +0 -0
- memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
- memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
- memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
- memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
- memos/mem_scheduler/base_scheduler.py +1319 -0
- memos/mem_scheduler/general_modules/__init__.py +0 -0
- memos/mem_scheduler/general_modules/api_misc.py +137 -0
- memos/mem_scheduler/general_modules/base.py +80 -0
- memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
- memos/mem_scheduler/general_modules/misc.py +313 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
- memos/mem_scheduler/general_modules/task_threads.py +315 -0
- memos/mem_scheduler/general_scheduler.py +1495 -0
- memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
- memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
- memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
- memos/mem_scheduler/monitors/general_monitor.py +394 -0
- memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
- memos/mem_scheduler/optimized_scheduler.py +410 -0
- memos/mem_scheduler/orm_modules/__init__.py +0 -0
- memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
- memos/mem_scheduler/orm_modules/base_model.py +729 -0
- memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
- memos/mem_scheduler/orm_modules/redis_model.py +699 -0
- memos/mem_scheduler/scheduler_factory.py +23 -0
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
- memos/mem_scheduler/schemas/api_schemas.py +233 -0
- memos/mem_scheduler/schemas/general_schemas.py +55 -0
- memos/mem_scheduler/schemas/message_schemas.py +173 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
- memos/mem_scheduler/schemas/task_schemas.py +132 -0
- memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
- memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
- memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
- memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
- memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
- memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/api_utils.py +77 -0
- memos/mem_scheduler/utils/config_utils.py +100 -0
- memos/mem_scheduler/utils/db_utils.py +50 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/metrics.py +125 -0
- memos/mem_scheduler/utils/misc_utils.py +290 -0
- memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
- memos/mem_scheduler/utils/status_tracker.py +229 -0
- memos/mem_scheduler/webservice_modules/__init__.py +0 -0
- memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
- memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +502 -0
- memos/mem_user/persistent_factory.py +98 -0
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/mem_user/redis_persistent_user_manager.py +225 -0
- memos/mem_user/user_manager.py +488 -0
- memos/memories/__init__.py +0 -0
- memos/memories/activation/__init__.py +0 -0
- memos/memories/activation/base.py +42 -0
- memos/memories/activation/item.py +56 -0
- memos/memories/activation/kv.py +292 -0
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/base.py +19 -0
- memos/memories/factory.py +42 -0
- memos/memories/parametric/__init__.py +0 -0
- memos/memories/parametric/base.py +19 -0
- memos/memories/parametric/item.py +11 -0
- memos/memories/parametric/lora.py +41 -0
- memos/memories/textual/__init__.py +0 -0
- memos/memories/textual/base.py +92 -0
- memos/memories/textual/general.py +236 -0
- memos/memories/textual/item.py +304 -0
- memos/memories/textual/naive.py +187 -0
- memos/memories/textual/prefer_text_memory/__init__.py +0 -0
- memos/memories/textual/prefer_text_memory/adder.py +504 -0
- memos/memories/textual/prefer_text_memory/config.py +106 -0
- memos/memories/textual/prefer_text_memory/extractor.py +221 -0
- memos/memories/textual/prefer_text_memory/factory.py +85 -0
- memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
- memos/memories/textual/prefer_text_memory/spliter.py +132 -0
- memos/memories/textual/prefer_text_memory/utils.py +93 -0
- memos/memories/textual/preference.py +344 -0
- memos/memories/textual/simple_preference.py +161 -0
- memos/memories/textual/simple_tree.py +69 -0
- memos/memories/textual/tree.py +459 -0
- memos/memories/textual/tree_text_memory/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
- memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
- memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
- memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
- memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
- memos/memos_tools/dinding_report_bot.py +453 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +142 -0
- memos/memos_tools/singleton.py +174 -0
- memos/memos_tools/thread_safe_dict.py +310 -0
- memos/memos_tools/thread_safe_dict_segment.py +382 -0
- memos/multi_mem_cube/__init__.py +0 -0
- memos/multi_mem_cube/composite_cube.py +86 -0
- memos/multi_mem_cube/single_cube.py +874 -0
- memos/multi_mem_cube/views.py +54 -0
- memos/parsers/__init__.py +0 -0
- memos/parsers/base.py +15 -0
- memos/parsers/factory.py +21 -0
- memos/parsers/markitdown.py +28 -0
- memos/reranker/__init__.py +4 -0
- memos/reranker/base.py +25 -0
- memos/reranker/concat.py +103 -0
- memos/reranker/cosine_local.py +102 -0
- memos/reranker/factory.py +72 -0
- memos/reranker/http_bge.py +324 -0
- memos/reranker/http_bge_strategy.py +327 -0
- memos/reranker/noop.py +19 -0
- memos/reranker/strategies/__init__.py +4 -0
- memos/reranker/strategies/base.py +61 -0
- memos/reranker/strategies/concat_background.py +94 -0
- memos/reranker/strategies/concat_docsource.py +110 -0
- memos/reranker/strategies/dialogue_common.py +109 -0
- memos/reranker/strategies/factory.py +31 -0
- memos/reranker/strategies/single_turn.py +107 -0
- memos/reranker/strategies/singleturn_outmem.py +98 -0
- memos/settings.py +10 -0
- memos/templates/__init__.py +0 -0
- memos/templates/advanced_search_prompts.py +211 -0
- memos/templates/cloud_service_prompt.py +107 -0
- memos/templates/instruction_completion.py +66 -0
- memos/templates/mem_agent_prompts.py +85 -0
- memos/templates/mem_feedback_prompts.py +822 -0
- memos/templates/mem_reader_prompts.py +1096 -0
- memos/templates/mem_reader_strategy_prompts.py +238 -0
- memos/templates/mem_scheduler_prompts.py +626 -0
- memos/templates/mem_search_prompts.py +93 -0
- memos/templates/mos_prompts.py +403 -0
- memos/templates/prefer_complete_prompt.py +735 -0
- memos/templates/tool_mem_prompts.py +139 -0
- memos/templates/tree_reorganize_prompts.py +230 -0
- memos/types/__init__.py +34 -0
- memos/types/general_types.py +151 -0
- memos/types/openai_chat_completion_types/__init__.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
- memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
- memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
- memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
- memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
- memos/utils.py +123 -0
- memos/vec_dbs/__init__.py +0 -0
- memos/vec_dbs/base.py +117 -0
- memos/vec_dbs/factory.py +23 -0
- memos/vec_dbs/item.py +50 -0
- memos/vec_dbs/milvus.py +654 -0
- memos/vec_dbs/qdrant.py +355 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from concurrent.futures import as_completed
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
|
+
|
|
8
|
+
from memos.context.context import ContextThread
|
|
9
|
+
from memos.log import get_logger
|
|
10
|
+
from memos.mem_scheduler.general_modules.base import BaseSchedulerModule
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ThreadManager(BaseSchedulerModule):
|
|
19
|
+
"""
|
|
20
|
+
Thread race implementation that runs multiple tasks concurrently and returns
|
|
21
|
+
the result of the first task to complete successfully.
|
|
22
|
+
|
|
23
|
+
Features:
|
|
24
|
+
- Cooperative thread termination using stop flags
|
|
25
|
+
- Configurable timeout for tasks
|
|
26
|
+
- Automatic cleanup of slower threads
|
|
27
|
+
- Thread-safe result handling
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, thread_pool_executor=None):
|
|
31
|
+
super().__init__()
|
|
32
|
+
# Variable to store the result
|
|
33
|
+
self.result: tuple[str, Any] | None = None
|
|
34
|
+
# Event to mark if the race is finished
|
|
35
|
+
self.race_finished = threading.Event()
|
|
36
|
+
# Lock to protect the result variable
|
|
37
|
+
self.lock = threading.Lock()
|
|
38
|
+
# Store thread objects for termination
|
|
39
|
+
self.threads: dict[str, threading.Thread] = {}
|
|
40
|
+
# Stop flags for each thread
|
|
41
|
+
self.stop_flags: dict[str, threading.Event] = {}
|
|
42
|
+
# attributes
|
|
43
|
+
self.thread_pool_executor = thread_pool_executor
|
|
44
|
+
|
|
45
|
+
def worker(
|
|
46
|
+
self, task_func: Callable[[threading.Event], T], task_name: str
|
|
47
|
+
) -> tuple[str, T] | None:
|
|
48
|
+
"""
|
|
49
|
+
Worker thread function that executes a task and handles result reporting.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
task_func: Function to execute with a stop_flag parameter
|
|
53
|
+
task_name: Name identifier for this task/thread
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Tuple of (task_name, result) if this thread wins the race, None otherwise
|
|
57
|
+
"""
|
|
58
|
+
# Create a stop flag for this task
|
|
59
|
+
stop_flag = threading.Event()
|
|
60
|
+
self.stop_flags[task_name] = stop_flag
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Execute the task with stop flag
|
|
64
|
+
result = task_func(stop_flag)
|
|
65
|
+
|
|
66
|
+
# If the race is already finished or we were asked to stop, return immediately
|
|
67
|
+
if self.race_finished.is_set() or stop_flag.is_set():
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
# Try to set the result (if no other thread has set it yet)
|
|
71
|
+
with self.lock:
|
|
72
|
+
if not self.race_finished.is_set():
|
|
73
|
+
self.result = (task_name, result)
|
|
74
|
+
# Mark the race as finished
|
|
75
|
+
self.race_finished.set()
|
|
76
|
+
logger.info(f"Task '{task_name}' won the race")
|
|
77
|
+
|
|
78
|
+
# Signal other threads to stop
|
|
79
|
+
for name, flag in self.stop_flags.items():
|
|
80
|
+
if name != task_name:
|
|
81
|
+
logger.debug(f"Signaling task '{name}' to stop")
|
|
82
|
+
flag.set()
|
|
83
|
+
|
|
84
|
+
return self.result
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Task '{task_name}' encountered an error: {e}")
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def run_multiple_tasks(
|
|
92
|
+
self,
|
|
93
|
+
tasks: dict[str, tuple[Callable, tuple]],
|
|
94
|
+
use_thread_pool: bool = False,
|
|
95
|
+
timeout: float | None = None,
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
"""
|
|
98
|
+
Run multiple tasks concurrently and return all results.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
tasks: Dictionary mapping task names to (task_execution_function, task_execution_parameters) tuples
|
|
102
|
+
use_thread_pool: Whether to use ThreadPoolExecutor (True) or regular threads (False)
|
|
103
|
+
timeout: Maximum time to wait for all tasks to complete (in seconds). None for infinite timeout.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Dictionary mapping task names to their results
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
TimeoutError: If tasks don't complete within the specified timeout
|
|
110
|
+
"""
|
|
111
|
+
if not tasks:
|
|
112
|
+
logger.warning("No tasks provided to run_multiple_tasks")
|
|
113
|
+
return {}
|
|
114
|
+
|
|
115
|
+
results = {}
|
|
116
|
+
start_time = time.time()
|
|
117
|
+
|
|
118
|
+
if use_thread_pool:
|
|
119
|
+
# Convert tasks format for thread pool compatibility
|
|
120
|
+
thread_pool_tasks = {}
|
|
121
|
+
for task_name, (func, args) in tasks.items():
|
|
122
|
+
thread_pool_tasks[task_name] = (func, args, {})
|
|
123
|
+
return self.run_with_thread_pool(thread_pool_tasks, timeout)
|
|
124
|
+
else:
|
|
125
|
+
# Use regular threads
|
|
126
|
+
threads = {}
|
|
127
|
+
thread_results = {}
|
|
128
|
+
exceptions = {}
|
|
129
|
+
|
|
130
|
+
def worker(task_name: str, func: Callable, args: tuple):
|
|
131
|
+
"""Worker function for regular threads"""
|
|
132
|
+
try:
|
|
133
|
+
result = func(*args)
|
|
134
|
+
thread_results[task_name] = result
|
|
135
|
+
logger.debug(f"Task '{task_name}' completed successfully")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
exceptions[task_name] = e
|
|
138
|
+
logger.error(f"Task '{task_name}' failed with error: {e}")
|
|
139
|
+
|
|
140
|
+
# Start all threads
|
|
141
|
+
for task_name, (func, args) in tasks.items():
|
|
142
|
+
thread = ContextThread(
|
|
143
|
+
target=worker, args=(task_name, func, args), name=f"task-{task_name}"
|
|
144
|
+
)
|
|
145
|
+
threads[task_name] = thread
|
|
146
|
+
thread.start()
|
|
147
|
+
logger.debug(f"Started thread for task '{task_name}'")
|
|
148
|
+
|
|
149
|
+
# Wait for all threads to complete with timeout
|
|
150
|
+
for task_name, thread in threads.items():
|
|
151
|
+
if timeout is None:
|
|
152
|
+
# Infinite timeout - wait indefinitely
|
|
153
|
+
thread.join()
|
|
154
|
+
else:
|
|
155
|
+
# Finite timeout - calculate remaining time
|
|
156
|
+
remaining_time = timeout - (time.time() - start_time)
|
|
157
|
+
if remaining_time <= 0:
|
|
158
|
+
logger.error(f"Task '{task_name}' timed out after {timeout} seconds")
|
|
159
|
+
results[task_name] = None
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
thread.join(timeout=remaining_time)
|
|
163
|
+
if thread.is_alive():
|
|
164
|
+
logger.error(f"Task '{task_name}' timed out after {timeout} seconds")
|
|
165
|
+
results[task_name] = None
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
# Get result or exception (for both infinite and finite timeout cases)
|
|
169
|
+
if task_name in thread_results:
|
|
170
|
+
results[task_name] = thread_results[task_name]
|
|
171
|
+
elif task_name in exceptions:
|
|
172
|
+
results[task_name] = None
|
|
173
|
+
else:
|
|
174
|
+
results[task_name] = None
|
|
175
|
+
|
|
176
|
+
elapsed_time = time.time() - start_time
|
|
177
|
+
completed_tasks = sum(1 for result in results.values() if result is not None)
|
|
178
|
+
logger.info(f"Completed {completed_tasks}/{len(tasks)} tasks in {elapsed_time:.2f} seconds")
|
|
179
|
+
|
|
180
|
+
return results
|
|
181
|
+
|
|
182
|
+
def run_with_thread_pool(
|
|
183
|
+
self, tasks: dict[str, tuple[callable, tuple, dict]], timeout: float | None = None
|
|
184
|
+
) -> dict[str, Any]:
|
|
185
|
+
"""
|
|
186
|
+
Execute multiple tasks using ThreadPoolExecutor.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
tasks: Dictionary mapping task names to (function, args, kwargs) tuples
|
|
190
|
+
timeout: Maximum time to wait for all tasks to complete (None for infinite timeout)
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Dictionary mapping task names to their results
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
TimeoutError: If tasks don't complete within the specified timeout
|
|
197
|
+
"""
|
|
198
|
+
if self.thread_pool_executor is None:
|
|
199
|
+
logger.error("thread_pool_executor is None")
|
|
200
|
+
raise ValueError("ThreadPoolExecutor is not initialized")
|
|
201
|
+
|
|
202
|
+
results = {}
|
|
203
|
+
start_time = time.time()
|
|
204
|
+
|
|
205
|
+
# Check if executor is shutdown before using it
|
|
206
|
+
if self.thread_pool_executor._shutdown:
|
|
207
|
+
logger.error("ThreadPoolExecutor is already shutdown, cannot submit new tasks")
|
|
208
|
+
raise RuntimeError("ThreadPoolExecutor is already shutdown")
|
|
209
|
+
|
|
210
|
+
# Use ThreadPoolExecutor directly without context manager
|
|
211
|
+
# The executor lifecycle is managed by the parent SchedulerDispatcher
|
|
212
|
+
executor = self.thread_pool_executor
|
|
213
|
+
|
|
214
|
+
# Submit all tasks
|
|
215
|
+
future_to_name = {}
|
|
216
|
+
for task_name, (func, args, kwargs) in tasks.items():
|
|
217
|
+
try:
|
|
218
|
+
future = executor.submit(func, *args, **kwargs)
|
|
219
|
+
future_to_name[future] = task_name
|
|
220
|
+
logger.debug(f"Submitted task '{task_name}' to thread pool")
|
|
221
|
+
except RuntimeError as e:
|
|
222
|
+
if "cannot schedule new futures after shutdown" in str(e):
|
|
223
|
+
logger.error(
|
|
224
|
+
f"Cannot submit task '{task_name}': ThreadPoolExecutor is shutdown"
|
|
225
|
+
)
|
|
226
|
+
results[task_name] = None
|
|
227
|
+
else:
|
|
228
|
+
raise
|
|
229
|
+
|
|
230
|
+
# Collect results as they complete
|
|
231
|
+
try:
|
|
232
|
+
# Handle infinite timeout case
|
|
233
|
+
timeout_param = None if timeout is None else timeout
|
|
234
|
+
for future in as_completed(future_to_name, timeout=timeout_param):
|
|
235
|
+
task_name = future_to_name[future]
|
|
236
|
+
try:
|
|
237
|
+
result = future.result()
|
|
238
|
+
results[task_name] = result
|
|
239
|
+
logger.debug(f"Task '{task_name}' completed successfully")
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"Task '{task_name}' failed with error: {e}")
|
|
242
|
+
results[task_name] = None
|
|
243
|
+
|
|
244
|
+
except Exception:
|
|
245
|
+
elapsed_time = time.time() - start_time
|
|
246
|
+
timeout_msg = "infinite" if timeout is None else f"{timeout}s"
|
|
247
|
+
logger.error(
|
|
248
|
+
f"Tasks execution timed out after {elapsed_time:.2f} seconds (timeout: {timeout_msg})"
|
|
249
|
+
)
|
|
250
|
+
# Cancel remaining futures
|
|
251
|
+
for future in future_to_name:
|
|
252
|
+
if not future.done():
|
|
253
|
+
future.cancel()
|
|
254
|
+
task_name = future_to_name[future]
|
|
255
|
+
logger.warning(f"Cancelled task '{task_name}' due to timeout")
|
|
256
|
+
results[task_name] = None
|
|
257
|
+
timeout_seconds = "infinite" if timeout is None else timeout
|
|
258
|
+
logger.error(f"Tasks execution timed out after {timeout_seconds} seconds")
|
|
259
|
+
|
|
260
|
+
return results
|
|
261
|
+
|
|
262
|
+
def run_race(
|
|
263
|
+
self, tasks: dict[str, Callable[[threading.Event], T]], timeout: float = 10.0
|
|
264
|
+
) -> tuple[str, T] | None:
|
|
265
|
+
"""
|
|
266
|
+
Start a competition between multiple tasks and return the result of the fastest one.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
tasks: Dictionary mapping task names to task functions
|
|
270
|
+
timeout: Maximum time to wait for any task to complete (in seconds)
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Tuple of (task_name, result) from the winning task, or None if no task completes
|
|
274
|
+
"""
|
|
275
|
+
if not tasks:
|
|
276
|
+
logger.warning("No tasks provided for the race")
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
# Reset state
|
|
280
|
+
self.race_finished.clear()
|
|
281
|
+
self.result = None
|
|
282
|
+
self.threads.clear()
|
|
283
|
+
self.stop_flags.clear()
|
|
284
|
+
|
|
285
|
+
# Create and start threads for each task
|
|
286
|
+
for task_name, task_func in tasks.items():
|
|
287
|
+
thread = ContextThread(
|
|
288
|
+
target=self.worker, args=(task_func, task_name), name=f"race-{task_name}"
|
|
289
|
+
)
|
|
290
|
+
self.threads[task_name] = thread
|
|
291
|
+
thread.start()
|
|
292
|
+
logger.debug(f"Started task '{task_name}'")
|
|
293
|
+
|
|
294
|
+
# Wait for any thread to complete or timeout
|
|
295
|
+
race_completed = self.race_finished.wait(timeout=timeout)
|
|
296
|
+
|
|
297
|
+
if not race_completed:
|
|
298
|
+
logger.warning(f"Race timed out after {timeout} seconds")
|
|
299
|
+
# Signal all threads to stop
|
|
300
|
+
for _name, flag in self.stop_flags.items():
|
|
301
|
+
flag.set()
|
|
302
|
+
|
|
303
|
+
# Wait for all threads to end (with timeout to avoid infinite waiting)
|
|
304
|
+
for _name, thread in self.threads.items():
|
|
305
|
+
thread.join(timeout=1.0)
|
|
306
|
+
if thread.is_alive():
|
|
307
|
+
logger.warning(f"Thread '{_name}' did not terminate within the join timeout")
|
|
308
|
+
|
|
309
|
+
# Return the result
|
|
310
|
+
if self.result:
|
|
311
|
+
logger.info(f"Race completed. Winner: {self.result[0]}")
|
|
312
|
+
else:
|
|
313
|
+
logger.warning("Race completed with no winner")
|
|
314
|
+
|
|
315
|
+
return self.result
|