sycommon-python-lib 0.2.0b3__tar.gz → 0.2.0b4__tar.gz
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.
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/PKG-INFO +1 -1
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/pyproject.toml +1 -1
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_client.py +125 -71
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_pool.py +46 -81
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_service_client_manager.py +2 -2
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +1 -1
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +2 -2
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_service_producer_manager.py +8 -8
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/README.md +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/setup.cfg +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/command/cli.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/LangfuseConfig.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/SentryConfig.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/database/async_base_db_service.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/database/async_database_service.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/database/base_db_service.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/database/database_service.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/health/health_check.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/health/metrics.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/embedding.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/get_llm.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/llm_logger.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/llm_tokens.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/struct_token.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/sy_langfuse.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/usage_token.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/async_sql_logger.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/kafka_log.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/logger_levels.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/sql_logger.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/cors.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/docs.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/middleware.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/traceid.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/base_http.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/mqlistener_config.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/notice/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/notice/uvicorn_monitor.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_service.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_service_core.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/sentry/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/sentry/sy_sentry.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/services.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/sse/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/sse/event.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/sse/sse.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/example.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/example2.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/feign.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/feign_client.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/nacos_client_base.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/nacos_config_manager.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/nacos_heartbeat_manager.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/nacos_service.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/nacos_service_discovery.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/nacos_service_registration.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/param.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tests/test_email.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/__init__.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/docs.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/env.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/merge_headers.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/snowflake.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/syemail.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
- {sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_client.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import functools
|
|
2
3
|
import json
|
|
3
4
|
from typing import Optional, Callable, Coroutine, Dict, Any, Union
|
|
4
5
|
from aio_pika import Channel, Message, DeliveryMode, ExchangeType
|
|
@@ -67,6 +68,7 @@ class RabbitMQClient:
|
|
|
67
68
|
# 并发控制
|
|
68
69
|
self._consume_lock = asyncio.Lock()
|
|
69
70
|
self._connect_lock = asyncio.Lock()
|
|
71
|
+
self._reconnect_lock = asyncio.Lock()
|
|
70
72
|
|
|
71
73
|
# 防止并发重连覆盖
|
|
72
74
|
self._connecting = False
|
|
@@ -78,14 +80,24 @@ class RabbitMQClient:
|
|
|
78
80
|
self._RECONNECT_INTERVAL = 15
|
|
79
81
|
|
|
80
82
|
@property
|
|
81
|
-
|
|
83
|
+
def is_connected(self) -> bool:
|
|
84
|
+
"""
|
|
85
|
+
同步检查连接状态
|
|
86
|
+
【修复】改为同步属性,避免异步调用错误和布尔上下文误判
|
|
87
|
+
"""
|
|
82
88
|
if self._closed:
|
|
83
89
|
return False
|
|
84
90
|
try:
|
|
91
|
+
# 检查通道是否有效
|
|
92
|
+
if not self._channel or self._channel.is_closed:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
# 检查连接是否有效
|
|
96
|
+
if self._channel_conn and self._channel_conn.is_closed:
|
|
97
|
+
return False
|
|
98
|
+
|
|
85
99
|
return (
|
|
86
|
-
self.
|
|
87
|
-
and self._channel_conn and not self._channel_conn.is_closed
|
|
88
|
-
and self._exchange is not None
|
|
100
|
+
self._exchange is not None
|
|
89
101
|
and (not self.queue_name or self._queue is not None)
|
|
90
102
|
)
|
|
91
103
|
except Exception:
|
|
@@ -96,7 +108,7 @@ class RabbitMQClient:
|
|
|
96
108
|
if not self._channel or self._channel.is_closed:
|
|
97
109
|
raise RuntimeError("无有效通道,无法重建资源")
|
|
98
110
|
|
|
99
|
-
# 无条件声明交换机
|
|
111
|
+
# 1. 无条件声明交换机
|
|
100
112
|
exchange_inst = await self._channel.declare_exchange(
|
|
101
113
|
name=self.exchange_name,
|
|
102
114
|
type=self.exchange_type,
|
|
@@ -107,7 +119,7 @@ class RabbitMQClient:
|
|
|
107
119
|
self._exchange = exchange_inst
|
|
108
120
|
logger.info(f"交换机重建成功: {self.exchange_name}")
|
|
109
121
|
|
|
110
|
-
# 仅在有队列名且符合条件时声明队列
|
|
122
|
+
# 2. 仅在有队列名且符合条件时声明队列
|
|
111
123
|
if self.queue_name and self.queue_name.endswith(f".{self.app_name}"):
|
|
112
124
|
self._queue = await self._channel.declare_queue(
|
|
113
125
|
name=self.queue_name,
|
|
@@ -118,41 +130,61 @@ class RabbitMQClient:
|
|
|
118
130
|
await self._queue.bind(exchange=exchange_inst, routing_key=self.routing_key)
|
|
119
131
|
logger.info(f"队列重建成功: {self.queue_name}")
|
|
120
132
|
|
|
133
|
+
async def _ensure_connection_alive(self) -> AbstractRobustConnection:
|
|
134
|
+
"""
|
|
135
|
+
【新增安全辅助方法】
|
|
136
|
+
动态获取当前有效的连接对象,避免引用池中已过期的连接。
|
|
137
|
+
如果连接无效,会抛出异常,交由上层逻辑处理重连。
|
|
138
|
+
"""
|
|
139
|
+
if not self.connection_pool or not self.connection_pool._initialized:
|
|
140
|
+
raise RuntimeError("连接池未初始化")
|
|
141
|
+
|
|
142
|
+
conn = self.connection_pool._connection
|
|
143
|
+
if not conn or conn.is_closed:
|
|
144
|
+
raise RuntimeError("连接池底层连接已关闭")
|
|
145
|
+
return conn
|
|
146
|
+
|
|
121
147
|
async def connect(self) -> None:
|
|
122
|
-
"""
|
|
148
|
+
"""连接方法(终极防御版:防并发风暴、防死锁、防资源泄漏)"""
|
|
123
149
|
if self._closed:
|
|
124
150
|
raise RuntimeError("客户端已关闭,无法重新连接")
|
|
125
151
|
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if self._connect_condition.locked():
|
|
133
|
-
self._connect_condition.release()
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
if self._connecting:
|
|
137
|
-
try:
|
|
138
|
-
logger.debug("连接正在进行中,等待现有连接完成...")
|
|
139
|
-
await asyncio.wait_for(self._connect_condition.wait(), timeout=60.0)
|
|
140
|
-
except asyncio.TimeoutError:
|
|
141
|
-
logger.warning("等待前序连接超时,强制接管...")
|
|
142
|
-
|
|
143
|
-
if await self.is_connected:
|
|
152
|
+
# === 阶段 A: 并发控制循环 ===
|
|
153
|
+
while True:
|
|
154
|
+
await self._connect_condition.acquire()
|
|
155
|
+
try:
|
|
156
|
+
# 1. 检查是否已连接
|
|
157
|
+
if self.is_connected:
|
|
144
158
|
if self._connect_condition.locked():
|
|
145
159
|
self._connect_condition.release()
|
|
146
160
|
return
|
|
147
161
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
162
|
+
# 2. 检查是否已有协程在连接
|
|
163
|
+
if self._connecting:
|
|
164
|
+
try:
|
|
165
|
+
logger.debug("连接正在进行中,等待现有连接完成...")
|
|
166
|
+
# 等待其他协程完成
|
|
167
|
+
await asyncio.wait_for(self._connect_condition.wait(), timeout=60.0)
|
|
168
|
+
except asyncio.TimeoutError:
|
|
169
|
+
logger.warning("⚠️ 等待前序连接超时,重新竞争连接权...")
|
|
170
|
+
|
|
171
|
+
# 【核心修复】
|
|
172
|
+
# 无论是因为超时还是被唤醒,都 continue 重新循环
|
|
173
|
+
# 这样确保不会出现“所有协程同时醒来”的羊群效应
|
|
174
|
+
# 只有抢到锁的那个协程会进入连接逻辑,其他继续 wait
|
|
175
|
+
if self._connect_condition.locked():
|
|
176
|
+
self._connect_condition.release()
|
|
177
|
+
continue
|
|
151
178
|
|
|
152
|
-
|
|
153
|
-
|
|
179
|
+
# 3. 抢到连接权,标记开始
|
|
180
|
+
self._connecting = True
|
|
154
181
|
self._connect_condition.release()
|
|
155
|
-
|
|
182
|
+
break # <--- 跳出循环,去执行连接逻辑
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
if self._connect_condition.locked():
|
|
186
|
+
self._connect_condition.release()
|
|
187
|
+
raise
|
|
156
188
|
|
|
157
189
|
# === 阶段 C: 执行耗时的连接逻辑 (无锁状态) ===
|
|
158
190
|
connection_failed = False
|
|
@@ -164,7 +196,7 @@ class RabbitMQClient:
|
|
|
164
196
|
# --- 步骤 1: 清理旧资源 ---
|
|
165
197
|
was_consuming = self._consumer_tag is not None
|
|
166
198
|
|
|
167
|
-
#
|
|
199
|
+
# 清理旧连接的回调
|
|
168
200
|
if self._channel_conn:
|
|
169
201
|
try:
|
|
170
202
|
if self._channel_conn.close_callbacks:
|
|
@@ -189,33 +221,38 @@ class RabbitMQClient:
|
|
|
189
221
|
if is_consumer:
|
|
190
222
|
logger.debug("获取消费者独立通道...")
|
|
191
223
|
self._channel = await self.connection_pool.acquire_consumer_channel()
|
|
192
|
-
# 【修正】回退到直接引用连接池的连接对象
|
|
193
|
-
# 虽然在连接池切换时这个引用会变,但因为我们每次 connect 都会重新获取,
|
|
194
|
-
# 且在 connect 开始时清理了旧回调,所以是安全的。
|
|
195
|
-
self._channel_conn = self.connection_pool._connection
|
|
196
224
|
else:
|
|
197
225
|
logger.debug("获取生产者主通道...")
|
|
198
226
|
self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
|
|
199
227
|
|
|
200
|
-
# --- 步骤 3:
|
|
228
|
+
# --- 步骤 3: 统一获取并注册连接回调 ---
|
|
229
|
+
if not self._channel_conn:
|
|
230
|
+
# 消费者路径需要手动获取连接引用
|
|
231
|
+
# 直接引用连接池的连接对象,确保引用时效性
|
|
232
|
+
self._channel_conn = self.connection_pool._connection
|
|
233
|
+
|
|
201
234
|
loop = asyncio.get_running_loop()
|
|
202
235
|
|
|
203
236
|
def on_conn_closed(conn, exc):
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
237
|
+
# 【核心修复】不再判断 _connecting,直接委托给 _safe_reconnect
|
|
238
|
+
# _safe_reconnect 内部有锁,会自动处理并发问题
|
|
239
|
+
if not self._closed:
|
|
240
|
+
logger.warning(f"⚠️ 检测到底层连接关闭: {exc}")
|
|
241
|
+
asyncio.run_coroutine_threadsafe(
|
|
242
|
+
self._safe_reconnect(), loop)
|
|
243
|
+
|
|
244
|
+
# 注册回调前,再次防御性清理
|
|
245
|
+
if self._channel_conn.close_callbacks:
|
|
246
|
+
self._channel_conn.close_callbacks.clear()
|
|
209
247
|
|
|
210
|
-
|
|
211
|
-
self._channel_conn.close_callbacks.add(on_conn_closed)
|
|
248
|
+
self._channel_conn.close_callbacks.add(on_conn_closed)
|
|
212
249
|
|
|
213
250
|
# --- 步骤 4: 重建基础资源 ---
|
|
214
251
|
await self._rebuild_resources()
|
|
215
252
|
|
|
216
253
|
except Exception as e:
|
|
217
254
|
connection_failed = True
|
|
218
|
-
logger.error(f"客户端连接失败: {str(e)}", exc_info=True)
|
|
255
|
+
logger.error(f"❌ 客户端连接失败: {str(e)}", exc_info=True)
|
|
219
256
|
|
|
220
257
|
# 异常时清理
|
|
221
258
|
if self._channel_conn and self._channel_conn.close_callbacks:
|
|
@@ -233,7 +270,7 @@ class RabbitMQClient:
|
|
|
233
270
|
try:
|
|
234
271
|
await self._connect_condition.acquire()
|
|
235
272
|
except Exception:
|
|
236
|
-
# 如果 acquire
|
|
273
|
+
# 如果 acquire 本身失败,确保状态复位,防止死锁
|
|
237
274
|
self._connecting = False
|
|
238
275
|
self._connect_condition.notify_all()
|
|
239
276
|
raise
|
|
@@ -286,29 +323,27 @@ class RabbitMQClient:
|
|
|
286
323
|
self._connect_condition.release()
|
|
287
324
|
|
|
288
325
|
async def _safe_reconnect(self):
|
|
289
|
-
"""
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
logger.info(f"将在{self._RECONNECT_INTERVAL}秒后尝试重连...")
|
|
299
|
-
await asyncio.sleep(self._RECONNECT_INTERVAL)
|
|
300
|
-
|
|
301
|
-
if self._closed or await self.is_connected:
|
|
302
|
-
return
|
|
326
|
+
"""
|
|
327
|
+
安全重连入口
|
|
328
|
+
【核心修复】使用锁防止并发风暴,确保同一时间只有一个重连任务在执行。
|
|
329
|
+
"""
|
|
330
|
+
# 如果锁已经被占用,说明已经有重连任务在进行中,直接忽略,防止无限递归
|
|
331
|
+
if self._reconnect_lock.locked():
|
|
332
|
+
logger.debug("⏳ 重连任务已在执行中,忽略本次触发,避免并发风暴")
|
|
333
|
+
return
|
|
303
334
|
|
|
335
|
+
async with self._reconnect_lock:
|
|
304
336
|
try:
|
|
305
|
-
self.
|
|
306
|
-
|
|
307
|
-
|
|
337
|
+
if self._closed:
|
|
338
|
+
logger.info("客户端已手动关闭,取消重连")
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
logger.warning("🔄 触发底层连接重连...")
|
|
342
|
+
# 调用实际的 connect 方法
|
|
343
|
+
await self.connect()
|
|
308
344
|
except Exception as e:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
self._current_reconnect_task = None
|
|
345
|
+
# 即使重连失败,也不要在这里递归调用自己,而是依赖外部的定时任务或下一次网络事件
|
|
346
|
+
logger.error(f"❌ 安全重连执行失败: {str(e)}", exc_info=True)
|
|
312
347
|
|
|
313
348
|
async def set_message_handler(self, handler: Callable[..., Coroutine]) -> None:
|
|
314
349
|
if not asyncio.iscoroutinefunction(handler):
|
|
@@ -317,23 +352,42 @@ class RabbitMQClient:
|
|
|
317
352
|
self._message_handler = handler
|
|
318
353
|
|
|
319
354
|
async def _process_message_callback(self, message: AbstractIncomingMessage):
|
|
355
|
+
"""
|
|
356
|
+
消息处理回调
|
|
357
|
+
"""
|
|
320
358
|
try:
|
|
359
|
+
# === 阶段 1: 消息解析与上下文设置 (在主线程执行) ===
|
|
321
360
|
body_dict = json.loads(message.body.decode("utf-8"))
|
|
322
361
|
msg_obj: MQMsgModel = MQMsgModel(**body_dict)
|
|
362
|
+
|
|
323
363
|
if not msg_obj.traceId:
|
|
324
364
|
msg_obj.traceId = message.headers.get(
|
|
325
365
|
"trace-id") if message.headers else SYLogger.get_trace_id()
|
|
326
366
|
|
|
327
367
|
SYLogger.set_trace_id(msg_obj.traceId)
|
|
328
368
|
|
|
369
|
+
# === 阶段 2: 业务逻辑执行 ===
|
|
329
370
|
if self._message_handler:
|
|
330
|
-
|
|
371
|
+
def run_job_with_context():
|
|
372
|
+
# 【关键修复】在子线程内重新设置 TraceId
|
|
373
|
+
SYLogger.set_trace_id(msg_obj.traceId)
|
|
374
|
+
|
|
375
|
+
return asyncio.run(
|
|
376
|
+
functools.partial(
|
|
377
|
+
self._message_handler, msg_obj, message)()
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
await asyncio.to_thread(run_job_with_context)
|
|
331
381
|
|
|
382
|
+
# === 阶段 3: 消息确认 ===
|
|
332
383
|
await message.ack()
|
|
333
384
|
|
|
334
385
|
except Exception as e:
|
|
335
|
-
logger.error(f"消息处理异常: {e}", exc_info=True)
|
|
336
|
-
|
|
386
|
+
logger.error(f"❌ 消息处理异常: {str(e)}", exc_info=True)
|
|
387
|
+
try:
|
|
388
|
+
await message.ack()
|
|
389
|
+
except Exception:
|
|
390
|
+
pass
|
|
337
391
|
|
|
338
392
|
async def start_consuming(self) -> Optional[ConsumerTag]:
|
|
339
393
|
if self._closed:
|
|
@@ -343,7 +397,7 @@ class RabbitMQClient:
|
|
|
343
397
|
if not self._message_handler:
|
|
344
398
|
raise RuntimeError("未设置消息处理器")
|
|
345
399
|
|
|
346
|
-
if not
|
|
400
|
+
if not self.is_connected:
|
|
347
401
|
await self.connect()
|
|
348
402
|
|
|
349
403
|
if not self._queue:
|
|
@@ -421,7 +475,7 @@ class RabbitMQClient:
|
|
|
421
475
|
|
|
422
476
|
for retry in range(retry_count):
|
|
423
477
|
try:
|
|
424
|
-
if not
|
|
478
|
+
if not self.is_connected:
|
|
425
479
|
await self.connect()
|
|
426
480
|
|
|
427
481
|
result = await self._exchange.publish(
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/rabbitmq/rabbitmq_pool.py
RENAMED
|
@@ -23,7 +23,7 @@ class AsyncProperty:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class RabbitMQConnectionPool:
|
|
26
|
-
"""单连接单通道RabbitMQ客户端
|
|
26
|
+
"""单连接单通道RabbitMQ客户端"""
|
|
27
27
|
|
|
28
28
|
def __init__(
|
|
29
29
|
self,
|
|
@@ -73,6 +73,7 @@ class RabbitMQConnectionPool:
|
|
|
73
73
|
return False
|
|
74
74
|
if not self._initialized:
|
|
75
75
|
return False
|
|
76
|
+
# 修复:简化检查,移除耗时的 connect() 调用,直接检查状态
|
|
76
77
|
if self._connection is None or self._connection.is_closed:
|
|
77
78
|
return False
|
|
78
79
|
if self._channel is None or self._channel.is_closed:
|
|
@@ -112,7 +113,6 @@ class RabbitMQConnectionPool:
|
|
|
112
113
|
if self._connection:
|
|
113
114
|
try:
|
|
114
115
|
if not self._connection.is_closed:
|
|
115
|
-
# close() 可能是同步的,也可能是异步的,aio_pika 中通常是异步的
|
|
116
116
|
await self._connection.close()
|
|
117
117
|
except Exception as e:
|
|
118
118
|
logger.warning(f"⚠️ [CLEANUP_CONN] 关闭连接失败: {e}")
|
|
@@ -121,67 +121,29 @@ class RabbitMQConnectionPool:
|
|
|
121
121
|
|
|
122
122
|
logger.info("✅ [CLEANUP] 资源清理完成")
|
|
123
123
|
|
|
124
|
-
async def _create_connection_impl(self, host: str) -> AbstractRobustConnection:
|
|
125
|
-
conn_url = (
|
|
126
|
-
f"amqp://{self.username}:{self.password}@{host}:{self.port}/"
|
|
127
|
-
f"{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}"
|
|
128
|
-
f"&reconnect_interval={self.reconnect_interval}&fail_fast=1"
|
|
129
|
-
)
|
|
130
|
-
logger.info(f"🔌 [CONNECT] 尝试连接节点: {host}")
|
|
131
|
-
try:
|
|
132
|
-
conn = await asyncio.wait_for(
|
|
133
|
-
connect_robust(conn_url),
|
|
134
|
-
timeout=self.connection_timeout + 5
|
|
135
|
-
)
|
|
136
|
-
logger.info(f"✅ [CONNECT_OK] 节点连接成功: {host}")
|
|
137
|
-
return conn
|
|
138
|
-
except Exception as e:
|
|
139
|
-
logger.error(f"❌ [CONNECT_FAIL] 节点 {host} 连接失败: {str(e)}")
|
|
140
|
-
raise ConnectionError(f"无法连接RabbitMQ {host}") from e
|
|
141
|
-
|
|
142
124
|
async def _ensure_main_channel(self) -> RobustChannel:
|
|
143
125
|
"""
|
|
144
|
-
确保主通道有效 (
|
|
126
|
+
确保主通道有效 (修复版:移除会导致死锁的代码)
|
|
145
127
|
"""
|
|
146
128
|
async with self._lock: # 持有锁贯穿整个方法
|
|
147
129
|
if self._is_shutdown:
|
|
148
130
|
raise RuntimeError("客户端已关闭")
|
|
149
131
|
|
|
150
132
|
# --- 阶段 A: 连接检查与重建 ---
|
|
133
|
+
# 修复:直接检查 is_closed,不调用 connect(timeout=1) 防止阻塞
|
|
151
134
|
connection_is_dead = False
|
|
152
|
-
if self._connection is None:
|
|
135
|
+
if self._connection is None or self._connection.is_closed:
|
|
153
136
|
connection_is_dead = True
|
|
154
|
-
else:
|
|
155
|
-
try:
|
|
156
|
-
if self._connection.is_closed:
|
|
157
|
-
connection_is_dead = True
|
|
158
|
-
else:
|
|
159
|
-
# 【修复】显式探活,保持在锁内
|
|
160
|
-
# 这样如果探活失败,可以直接在锁内进入重建流程,无需重新竞争锁
|
|
161
|
-
try:
|
|
162
|
-
await asyncio.wait_for(self._connection.connect(timeout=1), timeout=15)
|
|
163
|
-
except Exception:
|
|
164
|
-
logger.warning("⚠️ 连接探活失败,判定为死连接,强制重建...")
|
|
165
|
-
connection_is_dead = True
|
|
166
|
-
except Exception:
|
|
167
|
-
connection_is_dead = True
|
|
168
137
|
|
|
169
|
-
# 如果连接死掉,执行清理和重建
|
|
170
138
|
if connection_is_dead:
|
|
171
139
|
await self._cleanup_resources()
|
|
172
|
-
# ... (重建连接逻辑保持不变: 遍历 hosts -> connect_robust -> 赋值 self._connection) ...
|
|
173
|
-
# 确保重建成功,否则抛出异常
|
|
174
140
|
|
|
175
|
-
#
|
|
141
|
+
# 重建连接逻辑
|
|
176
142
|
retry_hosts = self.hosts.copy()
|
|
177
143
|
random.shuffle(retry_hosts)
|
|
178
144
|
last_error = None
|
|
179
|
-
max_attempts = min(len(retry_hosts), 3)
|
|
180
145
|
|
|
181
|
-
for
|
|
182
|
-
if not retry_hosts:
|
|
183
|
-
break
|
|
184
|
-
host = retry_hosts.pop()
|
|
146
|
+
for host in retry_hosts:
|
|
185
147
|
self._current_host = host
|
|
186
148
|
temp_conn = None
|
|
187
149
|
try:
|
|
@@ -192,8 +154,13 @@ class RabbitMQConnectionPool:
|
|
|
192
154
|
)
|
|
193
155
|
temp_conn = await asyncio.wait_for(
|
|
194
156
|
connect_robust(conn_url),
|
|
195
|
-
timeout=self.connection_timeout
|
|
157
|
+
timeout=self.connection_timeout
|
|
196
158
|
)
|
|
159
|
+
|
|
160
|
+
if self._is_shutdown:
|
|
161
|
+
await temp_conn.close()
|
|
162
|
+
raise RuntimeError("客户端已关闭")
|
|
163
|
+
|
|
197
164
|
self._connection = temp_conn
|
|
198
165
|
self._initialized = True
|
|
199
166
|
last_error = None
|
|
@@ -211,10 +178,11 @@ class RabbitMQConnectionPool:
|
|
|
211
178
|
await asyncio.sleep(self.reconnect_interval)
|
|
212
179
|
|
|
213
180
|
if last_error:
|
|
214
|
-
|
|
181
|
+
self._initialized = False
|
|
182
|
+
raise ConnectionError(
|
|
183
|
+
f"所有 RabbitMQ 节点连接失败: {last_error}") from last_error
|
|
215
184
|
|
|
216
185
|
# --- 阶段 B: 通道恢复逻辑 ---
|
|
217
|
-
# 此时 self._connection 必须是有效的
|
|
218
186
|
if self._channel is None or self._channel.is_closed:
|
|
219
187
|
max_channel_attempts = 2
|
|
220
188
|
for attempt in range(max_channel_attempts):
|
|
@@ -227,18 +195,19 @@ class RabbitMQConnectionPool:
|
|
|
227
195
|
logger.warning(
|
|
228
196
|
f"⚠️ [CHANNEL_RETRY] 第 {attempt + 1} 次尝试创建通道失败: {e}")
|
|
229
197
|
if attempt < max_channel_attempts - 1:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
198
|
+
try:
|
|
199
|
+
await self._connection.close()
|
|
200
|
+
except:
|
|
201
|
+
pass
|
|
202
|
+
self._connection = None
|
|
234
203
|
else:
|
|
235
|
-
raise e
|
|
204
|
+
raise RuntimeError(f"创建通道失败: {e}")
|
|
236
205
|
else:
|
|
237
|
-
#
|
|
206
|
+
# 通道存在,进行一次轻量级 QOS 刷新
|
|
238
207
|
try:
|
|
239
208
|
await self._channel.set_qos(prefetch_count=self.prefetch_count)
|
|
240
209
|
except Exception:
|
|
241
|
-
logger.warning("⚠️
|
|
210
|
+
logger.warning("⚠️ 通道 QOS 刷新失败,重建通道...")
|
|
242
211
|
self._channel = await self._connection.channel()
|
|
243
212
|
await self._channel.set_qos(prefetch_count=self.prefetch_count)
|
|
244
213
|
|
|
@@ -246,14 +215,12 @@ class RabbitMQConnectionPool:
|
|
|
246
215
|
|
|
247
216
|
async def init_pools(self):
|
|
248
217
|
"""初始化入口"""
|
|
249
|
-
# 快速检查
|
|
250
218
|
if self._initialized:
|
|
251
219
|
return
|
|
252
220
|
|
|
253
221
|
conn_created_in_this_try = None
|
|
254
222
|
|
|
255
223
|
try:
|
|
256
|
-
# 锁外创建连接,减少锁持有时间
|
|
257
224
|
init_host = random.choice(self.hosts)
|
|
258
225
|
conn = await self._create_connection_impl(init_host)
|
|
259
226
|
conn_created_in_this_try = conn
|
|
@@ -262,14 +229,12 @@ class RabbitMQConnectionPool:
|
|
|
262
229
|
if self._is_shutdown:
|
|
263
230
|
raise RuntimeError("客户端已关闭")
|
|
264
231
|
|
|
265
|
-
# 双重检查:防止在锁外等待时,状态已改变或被其他协程初始化
|
|
266
232
|
if self._initialized:
|
|
267
233
|
logger.info("🚀 [INIT_SKIP] 其他协程已完成初始化")
|
|
268
234
|
if conn_created_in_this_try:
|
|
269
235
|
await conn_created_in_this_try.close()
|
|
270
236
|
return
|
|
271
237
|
|
|
272
|
-
# 提交新资源
|
|
273
238
|
self._connection = conn
|
|
274
239
|
self._channel = await self._connection.channel()
|
|
275
240
|
await self._channel.set_qos(prefetch_count=self.prefetch_count)
|
|
@@ -286,29 +251,38 @@ class RabbitMQConnectionPool:
|
|
|
286
251
|
except Exception:
|
|
287
252
|
pass
|
|
288
253
|
if not self._is_shutdown:
|
|
289
|
-
|
|
254
|
+
async with self._lock:
|
|
255
|
+
self._initialized = False
|
|
290
256
|
raise
|
|
291
257
|
|
|
258
|
+
async def _create_connection_impl(self, host: str) -> AbstractRobustConnection:
|
|
259
|
+
conn_url = (
|
|
260
|
+
f"amqp://{self.username}:{self.password}@{host}:{self.port}/"
|
|
261
|
+
f"{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}"
|
|
262
|
+
f"&reconnect_interval={self.reconnect_interval}&fail_fast=1"
|
|
263
|
+
)
|
|
264
|
+
logger.info(f"🔌 [CONNECT] 尝试连接节点: {host}")
|
|
265
|
+
try:
|
|
266
|
+
conn = await asyncio.wait_for(
|
|
267
|
+
connect_robust(conn_url),
|
|
268
|
+
timeout=self.connection_timeout + 5
|
|
269
|
+
)
|
|
270
|
+
logger.info(f"✅ [CONNECT_OK] 节点连接成功: {host}")
|
|
271
|
+
return conn
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error(f"❌ [CONNECT_FAIL] 节点 {host} 连接失败: {str(e)}")
|
|
274
|
+
raise ConnectionError(f"无法连接RabbitMQ {host}") from e
|
|
275
|
+
|
|
292
276
|
async def force_reconnect(self):
|
|
293
|
-
"""
|
|
294
|
-
强制重连
|
|
295
|
-
严格执行:清理所有资源 -> 尝试建立新资源
|
|
296
|
-
"""
|
|
277
|
+
"""强制重连"""
|
|
297
278
|
async with self._lock:
|
|
298
279
|
if self._is_shutdown:
|
|
299
280
|
return
|
|
300
281
|
|
|
301
282
|
logger.warning("🔄 [FORCE_RECONNECT] 开始强制重连...")
|
|
302
|
-
|
|
303
|
-
# 1. 【关键】标记未初始化,迫使 _ensure_main_channel 走清理流程
|
|
304
283
|
self._initialized = False
|
|
305
|
-
|
|
306
|
-
# 2. 【关键】立即清理旧资源 (在锁内)
|
|
307
284
|
await self._cleanup_resources()
|
|
308
285
|
|
|
309
|
-
# 此时 self._connection 和 self._channel 均为 None
|
|
310
|
-
|
|
311
|
-
# 3. 锁外触发恢复 (避免阻塞锁太久)
|
|
312
286
|
try:
|
|
313
287
|
await self.acquire_channel()
|
|
314
288
|
logger.info("✅ [FORCE_RECONNECT_OK] 强制重连成功")
|
|
@@ -317,20 +291,13 @@ class RabbitMQConnectionPool:
|
|
|
317
291
|
raise
|
|
318
292
|
|
|
319
293
|
async def acquire_consumer_channel(self) -> RobustChannel:
|
|
320
|
-
"""
|
|
321
|
-
专门为消费者获取独立的通道。
|
|
322
|
-
遵循 aio_pika 最佳实践:消费者不应与发布者或其他消费者共享同一个 Channel 对象。
|
|
323
|
-
"""
|
|
324
|
-
# 确保连接池已初始化且连接是活的
|
|
294
|
+
"""获取消费者独立通道"""
|
|
325
295
|
if not self._initialized:
|
|
326
296
|
await self.init_pools()
|
|
327
297
|
|
|
328
|
-
# 确保 self._connection 是有效的(复用 _ensure_main_channel 的连接恢复逻辑)
|
|
329
298
|
await self._ensure_main_channel()
|
|
330
299
|
|
|
331
|
-
# 基于有效连接创建一个新的独立通道
|
|
332
300
|
try:
|
|
333
|
-
# 注意:这里直接使用 self._connection,而不是返回缓存的 self._channel
|
|
334
301
|
consumer_ch = await self._connection.channel()
|
|
335
302
|
await consumer_ch.set_qos(prefetch_count=self.prefetch_count)
|
|
336
303
|
logger.debug("✅ [CONSUMER_CH] 消费者独立通道已创建")
|
|
@@ -424,11 +391,9 @@ class RabbitMQConnectionPool:
|
|
|
424
391
|
async def close(self):
|
|
425
392
|
"""资源销毁"""
|
|
426
393
|
try:
|
|
427
|
-
# 设置超时,防止因锁异常导致无法关闭
|
|
428
394
|
await asyncio.wait_for(self._lock.acquire(), timeout=5.0)
|
|
429
395
|
except asyncio.TimeoutError:
|
|
430
396
|
logger.error("⚠️ [CLOSE_TIMEOUT] 获取锁超时,强制标记关闭")
|
|
431
|
-
# 如果拿不到锁,我们只能标记状态,无法安全清理连接
|
|
432
397
|
self._is_shutdown = True
|
|
433
398
|
self._initialized = False
|
|
434
399
|
return
|
|
@@ -85,7 +85,7 @@ class RabbitMQClientManager(RabbitMQCoreService):
|
|
|
85
85
|
await client.connect()
|
|
86
86
|
|
|
87
87
|
# 验证重连结果
|
|
88
|
-
if
|
|
88
|
+
if client.is_connected:
|
|
89
89
|
logger.info(f"✅ 客户端 '{client_name}' 重连成功")
|
|
90
90
|
return True
|
|
91
91
|
else:
|
|
@@ -199,7 +199,7 @@ class RabbitMQClientManager(RabbitMQCoreService):
|
|
|
199
199
|
if not client.queue_name and kwargs.get("queue_name"):
|
|
200
200
|
client.queue_name = kwargs.get("queue_name")
|
|
201
201
|
|
|
202
|
-
if
|
|
202
|
+
if client.is_connected:
|
|
203
203
|
return client
|
|
204
204
|
else:
|
|
205
205
|
logger.info(f"客户端 '{client_name}' 连接已断开,重新创建")
|
|
@@ -33,7 +33,7 @@ class RabbitMQConnectionMonitor(RabbitMQClientManager):
|
|
|
33
33
|
# 检查所有客户端连接
|
|
34
34
|
for client_name, client in list(cls._clients.items()):
|
|
35
35
|
try:
|
|
36
|
-
client_connected =
|
|
36
|
+
client_connected = client.is_connected
|
|
37
37
|
if not client_connected:
|
|
38
38
|
logger.warning(
|
|
39
39
|
f"客户端 '{client_name}' 连接异常,触发重连")
|
|
@@ -160,7 +160,7 @@ class RabbitMQConsumerManager(RabbitMQClientManager):
|
|
|
160
160
|
|
|
161
161
|
# 确保客户端已连接
|
|
162
162
|
start_time = asyncio.get_event_loop().time()
|
|
163
|
-
while not
|
|
163
|
+
while not client.is_connected and not cls._is_shutdown:
|
|
164
164
|
if asyncio.get_event_loop().time() - start_time > cls.CONSUMER_START_TIMEOUT:
|
|
165
165
|
raise TimeoutError(f"等待客户端 '{client_name}' 连接超时")
|
|
166
166
|
|
|
@@ -184,7 +184,7 @@ class RabbitMQConsumerManager(RabbitMQClientManager):
|
|
|
184
184
|
while attempt < max_attempts and not stop_event.is_set() and not cls._is_shutdown:
|
|
185
185
|
try:
|
|
186
186
|
# 启动消费前再次校验
|
|
187
|
-
if not
|
|
187
|
+
if not client.is_connected:
|
|
188
188
|
logger.info(f"消费者 '{client_name}' 连接断开,尝试重连")
|
|
189
189
|
await client.connect()
|
|
190
190
|
|
|
@@ -56,7 +56,7 @@ class RabbitMQProducerManager(RabbitMQClientManager):
|
|
|
56
56
|
# ===== 处理已有客户端重连 =====
|
|
57
57
|
if normalized_name in cls._clients:
|
|
58
58
|
client = cls._clients[normalized_name]
|
|
59
|
-
if not
|
|
59
|
+
if not client.is_connected:
|
|
60
60
|
client.queue_name = normalized_name
|
|
61
61
|
client.create_if_not_exists = False
|
|
62
62
|
await client.connect()
|
|
@@ -104,14 +104,14 @@ class RabbitMQProducerManager(RabbitMQClientManager):
|
|
|
104
104
|
# 检查是否在已注册的发送器中
|
|
105
105
|
if queue_name in cls._sender_client_names and queue_name in cls._clients:
|
|
106
106
|
client = cls._clients[queue_name]
|
|
107
|
-
if
|
|
107
|
+
if client.is_connected:
|
|
108
108
|
return client
|
|
109
109
|
else:
|
|
110
110
|
logger.info(f"发送器 '{queue_name}' 连接已断开,尝试重连")
|
|
111
111
|
try:
|
|
112
112
|
client.create_if_not_exists = False
|
|
113
113
|
await client.connect()
|
|
114
|
-
if
|
|
114
|
+
if client.is_connected:
|
|
115
115
|
return client
|
|
116
116
|
except Exception as e:
|
|
117
117
|
logger.error(f"发送器 '{queue_name}' 重连失败: {str(e)}")
|
|
@@ -123,14 +123,14 @@ class RabbitMQProducerManager(RabbitMQClientManager):
|
|
|
123
123
|
suffixed_name = f"{queue_name}.{app_name}"
|
|
124
124
|
if suffixed_name in cls._sender_client_names and suffixed_name in cls._clients:
|
|
125
125
|
client = cls._clients[suffixed_name]
|
|
126
|
-
if
|
|
126
|
+
if client.is_connected:
|
|
127
127
|
return client
|
|
128
128
|
else:
|
|
129
129
|
logger.info(f"发送器 '{suffixed_name}' 连接已断开,尝试重连")
|
|
130
130
|
try:
|
|
131
131
|
client.create_if_not_exists = False
|
|
132
132
|
await client.connect()
|
|
133
|
-
if
|
|
133
|
+
if client.is_connected:
|
|
134
134
|
return client
|
|
135
135
|
except Exception as e:
|
|
136
136
|
logger.error(f"发送器 '{suffixed_name}' 重连失败: {str(e)}")
|
|
@@ -156,7 +156,7 @@ class RabbitMQProducerManager(RabbitMQClientManager):
|
|
|
156
156
|
raise ValueError(error_msg)
|
|
157
157
|
|
|
158
158
|
# 确保连接有效
|
|
159
|
-
if not
|
|
159
|
+
if not sender.is_connected:
|
|
160
160
|
logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
|
|
161
161
|
max_retry = 3
|
|
162
162
|
retry_count = 0
|
|
@@ -166,7 +166,7 @@ class RabbitMQProducerManager(RabbitMQClientManager):
|
|
|
166
166
|
try:
|
|
167
167
|
sender.create_if_not_exists = False
|
|
168
168
|
await sender.connect()
|
|
169
|
-
if
|
|
169
|
+
if sender.is_connected:
|
|
170
170
|
logger.info(
|
|
171
171
|
f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
|
|
172
172
|
break
|
|
@@ -177,7 +177,7 @@ class RabbitMQProducerManager(RabbitMQClientManager):
|
|
|
177
177
|
f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}")
|
|
178
178
|
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
179
179
|
|
|
180
|
-
if retry_count >= max_retry and not
|
|
180
|
+
if retry_count >= max_retry and not sender.is_connected:
|
|
181
181
|
error_msg = f"发送器 '{queue_name}' 经过 {max_retry} 次重连仍失败"
|
|
182
182
|
logger.error(f"{error_msg}: {str(last_exception)}")
|
|
183
183
|
raise Exception(error_msg) from last_exception
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/DatabaseConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/EmbeddingConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/LLMConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/LangfuseConfig.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/RerankerConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/config/SentryConfig.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/database/base_db_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/health/health_check.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/llm/struct_token.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/async_sql_logger.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/kafka_log.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/logger_levels.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/logger_wrapper.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/logging/sql_logger.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/exception.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/timeout.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/middleware/traceid.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/base_http.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/mqlistener_config.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/mqmsg_model.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/models/mqsend_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/notice/uvicorn_monitor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/sentry/sy_sentry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/example2.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/feign_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/synacos/nacos_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tests/test_email.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.2.0b3 → sycommon_python_lib-0.2.0b4}/src/sycommon/tools/merge_headers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|