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.
Files changed (315) hide show
  1. memoryos-2.0.3.dist-info/METADATA +418 -0
  2. memoryos-2.0.3.dist-info/RECORD +315 -0
  3. memoryos-2.0.3.dist-info/WHEEL +4 -0
  4. memoryos-2.0.3.dist-info/entry_points.txt +3 -0
  5. memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
  6. memos/__init__.py +20 -0
  7. memos/api/client.py +571 -0
  8. memos/api/config.py +1018 -0
  9. memos/api/context/dependencies.py +50 -0
  10. memos/api/exceptions.py +53 -0
  11. memos/api/handlers/__init__.py +62 -0
  12. memos/api/handlers/add_handler.py +158 -0
  13. memos/api/handlers/base_handler.py +194 -0
  14. memos/api/handlers/chat_handler.py +1401 -0
  15. memos/api/handlers/component_init.py +388 -0
  16. memos/api/handlers/config_builders.py +190 -0
  17. memos/api/handlers/feedback_handler.py +93 -0
  18. memos/api/handlers/formatters_handler.py +237 -0
  19. memos/api/handlers/memory_handler.py +316 -0
  20. memos/api/handlers/scheduler_handler.py +497 -0
  21. memos/api/handlers/search_handler.py +222 -0
  22. memos/api/handlers/suggestion_handler.py +117 -0
  23. memos/api/mcp_serve.py +614 -0
  24. memos/api/middleware/request_context.py +101 -0
  25. memos/api/product_api.py +38 -0
  26. memos/api/product_models.py +1206 -0
  27. memos/api/routers/__init__.py +1 -0
  28. memos/api/routers/product_router.py +477 -0
  29. memos/api/routers/server_router.py +394 -0
  30. memos/api/server_api.py +44 -0
  31. memos/api/start_api.py +433 -0
  32. memos/chunkers/__init__.py +4 -0
  33. memos/chunkers/base.py +24 -0
  34. memos/chunkers/charactertext_chunker.py +41 -0
  35. memos/chunkers/factory.py +24 -0
  36. memos/chunkers/markdown_chunker.py +62 -0
  37. memos/chunkers/sentence_chunker.py +54 -0
  38. memos/chunkers/simple_chunker.py +50 -0
  39. memos/cli.py +113 -0
  40. memos/configs/__init__.py +0 -0
  41. memos/configs/base.py +82 -0
  42. memos/configs/chunker.py +59 -0
  43. memos/configs/embedder.py +88 -0
  44. memos/configs/graph_db.py +236 -0
  45. memos/configs/internet_retriever.py +100 -0
  46. memos/configs/llm.py +151 -0
  47. memos/configs/mem_agent.py +54 -0
  48. memos/configs/mem_chat.py +81 -0
  49. memos/configs/mem_cube.py +105 -0
  50. memos/configs/mem_os.py +83 -0
  51. memos/configs/mem_reader.py +91 -0
  52. memos/configs/mem_scheduler.py +385 -0
  53. memos/configs/mem_user.py +70 -0
  54. memos/configs/memory.py +324 -0
  55. memos/configs/parser.py +38 -0
  56. memos/configs/reranker.py +18 -0
  57. memos/configs/utils.py +8 -0
  58. memos/configs/vec_db.py +80 -0
  59. memos/context/context.py +355 -0
  60. memos/dependency.py +52 -0
  61. memos/deprecation.py +262 -0
  62. memos/embedders/__init__.py +0 -0
  63. memos/embedders/ark.py +95 -0
  64. memos/embedders/base.py +106 -0
  65. memos/embedders/factory.py +29 -0
  66. memos/embedders/ollama.py +77 -0
  67. memos/embedders/sentence_transformer.py +49 -0
  68. memos/embedders/universal_api.py +51 -0
  69. memos/exceptions.py +30 -0
  70. memos/graph_dbs/__init__.py +0 -0
  71. memos/graph_dbs/base.py +274 -0
  72. memos/graph_dbs/factory.py +27 -0
  73. memos/graph_dbs/item.py +46 -0
  74. memos/graph_dbs/nebular.py +1794 -0
  75. memos/graph_dbs/neo4j.py +1942 -0
  76. memos/graph_dbs/neo4j_community.py +1058 -0
  77. memos/graph_dbs/polardb.py +5446 -0
  78. memos/hello_world.py +97 -0
  79. memos/llms/__init__.py +0 -0
  80. memos/llms/base.py +25 -0
  81. memos/llms/deepseek.py +13 -0
  82. memos/llms/factory.py +38 -0
  83. memos/llms/hf.py +443 -0
  84. memos/llms/hf_singleton.py +114 -0
  85. memos/llms/ollama.py +135 -0
  86. memos/llms/openai.py +222 -0
  87. memos/llms/openai_new.py +198 -0
  88. memos/llms/qwen.py +13 -0
  89. memos/llms/utils.py +14 -0
  90. memos/llms/vllm.py +218 -0
  91. memos/log.py +237 -0
  92. memos/mem_agent/base.py +19 -0
  93. memos/mem_agent/deepsearch_agent.py +391 -0
  94. memos/mem_agent/factory.py +36 -0
  95. memos/mem_chat/__init__.py +0 -0
  96. memos/mem_chat/base.py +30 -0
  97. memos/mem_chat/factory.py +21 -0
  98. memos/mem_chat/simple.py +200 -0
  99. memos/mem_cube/__init__.py +0 -0
  100. memos/mem_cube/base.py +30 -0
  101. memos/mem_cube/general.py +240 -0
  102. memos/mem_cube/navie.py +172 -0
  103. memos/mem_cube/utils.py +169 -0
  104. memos/mem_feedback/base.py +15 -0
  105. memos/mem_feedback/feedback.py +1192 -0
  106. memos/mem_feedback/simple_feedback.py +40 -0
  107. memos/mem_feedback/utils.py +230 -0
  108. memos/mem_os/client.py +5 -0
  109. memos/mem_os/core.py +1203 -0
  110. memos/mem_os/main.py +582 -0
  111. memos/mem_os/product.py +1608 -0
  112. memos/mem_os/product_server.py +455 -0
  113. memos/mem_os/utils/default_config.py +359 -0
  114. memos/mem_os/utils/format_utils.py +1403 -0
  115. memos/mem_os/utils/reference_utils.py +162 -0
  116. memos/mem_reader/__init__.py +0 -0
  117. memos/mem_reader/base.py +47 -0
  118. memos/mem_reader/factory.py +53 -0
  119. memos/mem_reader/memory.py +298 -0
  120. memos/mem_reader/multi_modal_struct.py +965 -0
  121. memos/mem_reader/read_multi_modal/__init__.py +43 -0
  122. memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
  123. memos/mem_reader/read_multi_modal/base.py +273 -0
  124. memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
  125. memos/mem_reader/read_multi_modal/image_parser.py +359 -0
  126. memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
  127. memos/mem_reader/read_multi_modal/string_parser.py +139 -0
  128. memos/mem_reader/read_multi_modal/system_parser.py +327 -0
  129. memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
  130. memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
  131. memos/mem_reader/read_multi_modal/user_parser.py +218 -0
  132. memos/mem_reader/read_multi_modal/utils.py +358 -0
  133. memos/mem_reader/simple_struct.py +912 -0
  134. memos/mem_reader/strategy_struct.py +163 -0
  135. memos/mem_reader/utils.py +157 -0
  136. memos/mem_scheduler/__init__.py +0 -0
  137. memos/mem_scheduler/analyzer/__init__.py +0 -0
  138. memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
  139. memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
  140. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
  141. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  142. memos/mem_scheduler/base_scheduler.py +1319 -0
  143. memos/mem_scheduler/general_modules/__init__.py +0 -0
  144. memos/mem_scheduler/general_modules/api_misc.py +137 -0
  145. memos/mem_scheduler/general_modules/base.py +80 -0
  146. memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
  147. memos/mem_scheduler/general_modules/misc.py +313 -0
  148. memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
  149. memos/mem_scheduler/general_modules/task_threads.py +315 -0
  150. memos/mem_scheduler/general_scheduler.py +1495 -0
  151. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  152. memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
  153. memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
  154. memos/mem_scheduler/monitors/__init__.py +0 -0
  155. memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
  156. memos/mem_scheduler/monitors/general_monitor.py +394 -0
  157. memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
  158. memos/mem_scheduler/optimized_scheduler.py +410 -0
  159. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  160. memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
  161. memos/mem_scheduler/orm_modules/base_model.py +729 -0
  162. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  163. memos/mem_scheduler/orm_modules/redis_model.py +699 -0
  164. memos/mem_scheduler/scheduler_factory.py +23 -0
  165. memos/mem_scheduler/schemas/__init__.py +0 -0
  166. memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
  167. memos/mem_scheduler/schemas/api_schemas.py +233 -0
  168. memos/mem_scheduler/schemas/general_schemas.py +55 -0
  169. memos/mem_scheduler/schemas/message_schemas.py +173 -0
  170. memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
  171. memos/mem_scheduler/schemas/task_schemas.py +132 -0
  172. memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
  173. memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
  174. memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
  175. memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
  176. memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
  177. memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
  178. memos/mem_scheduler/utils/__init__.py +0 -0
  179. memos/mem_scheduler/utils/api_utils.py +77 -0
  180. memos/mem_scheduler/utils/config_utils.py +100 -0
  181. memos/mem_scheduler/utils/db_utils.py +50 -0
  182. memos/mem_scheduler/utils/filter_utils.py +176 -0
  183. memos/mem_scheduler/utils/metrics.py +125 -0
  184. memos/mem_scheduler/utils/misc_utils.py +290 -0
  185. memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
  186. memos/mem_scheduler/utils/status_tracker.py +229 -0
  187. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  188. memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
  189. memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
  190. memos/mem_user/factory.py +94 -0
  191. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  192. memos/mem_user/mysql_user_manager.py +502 -0
  193. memos/mem_user/persistent_factory.py +98 -0
  194. memos/mem_user/persistent_user_manager.py +260 -0
  195. memos/mem_user/redis_persistent_user_manager.py +225 -0
  196. memos/mem_user/user_manager.py +488 -0
  197. memos/memories/__init__.py +0 -0
  198. memos/memories/activation/__init__.py +0 -0
  199. memos/memories/activation/base.py +42 -0
  200. memos/memories/activation/item.py +56 -0
  201. memos/memories/activation/kv.py +292 -0
  202. memos/memories/activation/vllmkv.py +219 -0
  203. memos/memories/base.py +19 -0
  204. memos/memories/factory.py +42 -0
  205. memos/memories/parametric/__init__.py +0 -0
  206. memos/memories/parametric/base.py +19 -0
  207. memos/memories/parametric/item.py +11 -0
  208. memos/memories/parametric/lora.py +41 -0
  209. memos/memories/textual/__init__.py +0 -0
  210. memos/memories/textual/base.py +92 -0
  211. memos/memories/textual/general.py +236 -0
  212. memos/memories/textual/item.py +304 -0
  213. memos/memories/textual/naive.py +187 -0
  214. memos/memories/textual/prefer_text_memory/__init__.py +0 -0
  215. memos/memories/textual/prefer_text_memory/adder.py +504 -0
  216. memos/memories/textual/prefer_text_memory/config.py +106 -0
  217. memos/memories/textual/prefer_text_memory/extractor.py +221 -0
  218. memos/memories/textual/prefer_text_memory/factory.py +85 -0
  219. memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
  220. memos/memories/textual/prefer_text_memory/spliter.py +132 -0
  221. memos/memories/textual/prefer_text_memory/utils.py +93 -0
  222. memos/memories/textual/preference.py +344 -0
  223. memos/memories/textual/simple_preference.py +161 -0
  224. memos/memories/textual/simple_tree.py +69 -0
  225. memos/memories/textual/tree.py +459 -0
  226. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  227. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  228. memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
  229. memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
  230. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
  231. memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
  232. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  233. memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
  234. memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
  235. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
  236. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
  237. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
  238. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
  239. memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
  240. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  241. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
  242. memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
  243. memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
  244. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
  245. memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
  246. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
  247. memos/memos_tools/dinding_report_bot.py +453 -0
  248. memos/memos_tools/lockfree_dict.py +120 -0
  249. memos/memos_tools/notification_service.py +44 -0
  250. memos/memos_tools/notification_utils.py +142 -0
  251. memos/memos_tools/singleton.py +174 -0
  252. memos/memos_tools/thread_safe_dict.py +310 -0
  253. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  254. memos/multi_mem_cube/__init__.py +0 -0
  255. memos/multi_mem_cube/composite_cube.py +86 -0
  256. memos/multi_mem_cube/single_cube.py +874 -0
  257. memos/multi_mem_cube/views.py +54 -0
  258. memos/parsers/__init__.py +0 -0
  259. memos/parsers/base.py +15 -0
  260. memos/parsers/factory.py +21 -0
  261. memos/parsers/markitdown.py +28 -0
  262. memos/reranker/__init__.py +4 -0
  263. memos/reranker/base.py +25 -0
  264. memos/reranker/concat.py +103 -0
  265. memos/reranker/cosine_local.py +102 -0
  266. memos/reranker/factory.py +72 -0
  267. memos/reranker/http_bge.py +324 -0
  268. memos/reranker/http_bge_strategy.py +327 -0
  269. memos/reranker/noop.py +19 -0
  270. memos/reranker/strategies/__init__.py +4 -0
  271. memos/reranker/strategies/base.py +61 -0
  272. memos/reranker/strategies/concat_background.py +94 -0
  273. memos/reranker/strategies/concat_docsource.py +110 -0
  274. memos/reranker/strategies/dialogue_common.py +109 -0
  275. memos/reranker/strategies/factory.py +31 -0
  276. memos/reranker/strategies/single_turn.py +107 -0
  277. memos/reranker/strategies/singleturn_outmem.py +98 -0
  278. memos/settings.py +10 -0
  279. memos/templates/__init__.py +0 -0
  280. memos/templates/advanced_search_prompts.py +211 -0
  281. memos/templates/cloud_service_prompt.py +107 -0
  282. memos/templates/instruction_completion.py +66 -0
  283. memos/templates/mem_agent_prompts.py +85 -0
  284. memos/templates/mem_feedback_prompts.py +822 -0
  285. memos/templates/mem_reader_prompts.py +1096 -0
  286. memos/templates/mem_reader_strategy_prompts.py +238 -0
  287. memos/templates/mem_scheduler_prompts.py +626 -0
  288. memos/templates/mem_search_prompts.py +93 -0
  289. memos/templates/mos_prompts.py +403 -0
  290. memos/templates/prefer_complete_prompt.py +735 -0
  291. memos/templates/tool_mem_prompts.py +139 -0
  292. memos/templates/tree_reorganize_prompts.py +230 -0
  293. memos/types/__init__.py +34 -0
  294. memos/types/general_types.py +151 -0
  295. memos/types/openai_chat_completion_types/__init__.py +15 -0
  296. memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
  297. memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
  298. memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
  299. memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
  300. memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
  301. memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
  302. memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
  303. memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
  304. memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
  305. memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
  306. memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
  307. memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
  308. memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
  309. memos/utils.py +123 -0
  310. memos/vec_dbs/__init__.py +0 -0
  311. memos/vec_dbs/base.py +117 -0
  312. memos/vec_dbs/factory.py +23 -0
  313. memos/vec_dbs/item.py +50 -0
  314. memos/vec_dbs/milvus.py +654 -0
  315. 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