fix(atest): 去除 manage.py 硬编码密钥口令 + SIO 初次连接失败兜底重连
- manage.py 强制环境变量注入,删除默认 SECRET_KEY/MSYNC_SERVICE_SECRET/DB_PASSWORD - sio_client 初次连接失败启动兜底重连线程,消除永久退化态 - 补充 RetryStep 手动重试与状态守卫回归用例(17/17 通过)
This commit is contained in:
1 parent
409664d546
commit
ddaadd8e57
3 files changed
+90
-6
No files matched your search
+10
-3
@@ -7,9 +7,16 @@ def main():
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'ATestCServer.settings'
|
||||
os.environ['DB_NAME'] = 'atestc'
|
||||
os.environ.setdefault('DJANGO_SECRET_KEY', 'gvsds-django-prod-k8x2m9v4n7p1q6w3j5r0t8y1u4i7o0a2s6d9f3g5h7j2k4l6z8x1c3v5b')
|
||||
os.environ.setdefault('MSYNC_SERVICE_SECRET', 'atestc-msync-secret')
|
||||
os.environ.setdefault('DB_PASSWORD', '1Qq@1Qq@190*')
|
||||
# 安全:禁止硬编码默认密钥/口令,缺失时显式报错(settings.py 亦有
|
||||
# ImproperlyConfigured 兜底)。MSYNC_SERVICE_SECRET 与 DB_PASSWORD
|
||||
# 由部署环境(环境变量)提供。
|
||||
required_envs = ['DJANGO_SECRET_KEY', 'MSYNC_SERVICE_SECRET', 'DB_PASSWORD']
|
||||
missing = [name for name in required_envs if not os.environ.get(name)]
|
||||
if missing:
|
||||
raise SystemExit(
|
||||
'缺少必需的环境变量: ' + ', '.join(missing)
|
||||
+ '(禁止使用默认值,请通过环境变量注入)'
|
||||
)
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
|
||||
@@ -103,10 +103,40 @@ class MSyncSocketClient:
|
||||
logger.info(f'[SIO] → connect url={url} ns=/msync')
|
||||
self._sio.connect(url, namespaces=['/msync'], wait=True, wait_timeout=15)
|
||||
except Exception as e:
|
||||
logger.warning(f'Socket.IO 连接失败: {e},将自动重连')
|
||||
self._StartHeartbeat()
|
||||
logger.warning(f'Socket.IO 连接失败: {e},启动兜底重连线程')
|
||||
self._StartBootstrap()
|
||||
return
|
||||
|
||||
self._OnReady()
|
||||
|
||||
def _StartBootstrap(self):
|
||||
"""初次连接失败的兜底:循环重连,成功后走完整注册/认证流程
|
||||
|
||||
python-socketio 的 reconnection 仅在"已连接后断开"时生效,
|
||||
初次 connect 失败不会自动重连,需自行兜底。
|
||||
"""
|
||||
|
||||
def bootstrap():
|
||||
while self._running and not self._sio.connected:
|
||||
try:
|
||||
logger.info(f'[SIO] 兜底重连尝试: {self.MasterURL}')
|
||||
self._sio.connect(
|
||||
self.MasterURL,
|
||||
namespaces=['/msync'],
|
||||
wait=True,
|
||||
wait_timeout=10,
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f'[SIO] 兜底重连失败: {e}')
|
||||
time.sleep(3)
|
||||
if self._sio.connected:
|
||||
self._OnReady()
|
||||
|
||||
threading.Thread(target=bootstrap, daemon=True, name='msync-bootstrap').start()
|
||||
|
||||
def _OnReady(self):
|
||||
"""连接建立后的统一就绪流程:注册/认证/心跳/ORM 中转"""
|
||||
logger.info(f'[SIO] Socket 已连接, 开始注册/认证...')
|
||||
for attempt in range(1, 4):
|
||||
reg_ok = self._DoRegister()
|
||||
|
||||
@@ -53,7 +53,7 @@ def _Run():
|
||||
|
||||
from unittest import mock
|
||||
import uuid
|
||||
from gvsdsdk.msync.transaction import TransactionManager, StepExecutionError
|
||||
from gvsdsdk.msync.transaction import TransactionManager, StepExecutionError, TransactionError
|
||||
from gvsdsdk.msync.local_tx import local_tx
|
||||
from gvsdsdk.msync.models import ServiceNode
|
||||
|
||||
@@ -164,6 +164,53 @@ def _Run():
|
||||
r3 = local_tx.ExecuteStep('no_such', {}, 'K:1:1')
|
||||
check('不同键不命中', r1 is not r3)
|
||||
|
||||
# ─── 6. RetryStep 手动重试(自动重试耗尽后仍可重试,计数分离)───
|
||||
print('[6] RetryStep 手动重试')
|
||||
dtx = TransactionManager.Create(
|
||||
name='t6',
|
||||
steps_data=[
|
||||
{'name': 's1', 'ParticipantService': 'svc'},
|
||||
{'name': 's2', 'ParticipantService': 'svc', 'MaxRetries': 0},
|
||||
],
|
||||
)
|
||||
with mock.patch.object(TransactionManager, '_call_service', side_effect=[
|
||||
{'ok': 1},
|
||||
StepExecutionError('s2 永久失败'), # s2 自动重试耗尽
|
||||
StepExecutionError('补偿 s1 也失败'), # 补偿失败 → 事务 FAILED
|
||||
]):
|
||||
TransactionManager.Execute(dtx.TransactionCode)
|
||||
dtx.refresh_from_db()
|
||||
check('自动重试耗尽后事务 Failed', dtx.TransactionStatus == 5)
|
||||
# s2 MaxRetries=0 已耗尽,原逻辑(CanRetry)拒绝;现手动重试不受配额限制
|
||||
with mock.patch.object(TransactionManager, '_call_service', return_value={'ok': True}):
|
||||
step = TransactionManager.RetryStep(dtx.TransactionCode, 1)
|
||||
check('手动重试成功 Completed', step.StepStatus == 2)
|
||||
check('手动重试计数独立', step.RetryCount == 1)
|
||||
|
||||
# ─── 7. RetryStep 拒绝已补偿事务(事务状态守卫)───
|
||||
print('[7] RetryStep 已补偿事务拒绝')
|
||||
dtx = TransactionManager.Create(
|
||||
name='t7',
|
||||
steps_data=[
|
||||
{'name': 's1', 'ParticipantService': 'svc'},
|
||||
{'name': 's2', 'ParticipantService': 'svc', 'MaxRetries': 0},
|
||||
],
|
||||
)
|
||||
with mock.patch.object(TransactionManager, '_call_service', side_effect=[
|
||||
{'ok': 1},
|
||||
StepExecutionError('s2 失败'),
|
||||
{'ok': True}, # s1 补偿成功 → 事务 COMPENSATED
|
||||
]):
|
||||
TransactionManager.Execute(dtx.TransactionCode)
|
||||
dtx.refresh_from_db()
|
||||
check('事务已补偿', dtx.TransactionStatus == 4)
|
||||
rejected = False
|
||||
try:
|
||||
TransactionManager.RetryStep(dtx.TransactionCode, 1) # s2 正向失败步骤
|
||||
except TransactionError:
|
||||
rejected = True
|
||||
check('已补偿事务 RetryStep 被拒绝', rejected)
|
||||
|
||||
# ─── 汇总 ───
|
||||
passed = sum(1 for _, ok in results if ok)
|
||||
print(f'\n结果: {passed}/{len(results)} 通过')
|
||||
|
||||
Reference in new issue
Block a user