推送真发送:webhook/slack 内置真实发送端 + worker 终态自动通知

此前订阅与分发框架都在,但分发端点传 sender=None,
webhook/slack 永远报无 sender、从未真发过。本轮:
- push:send_webhook/send_slack(标准库 urllib POST,
  单次 10 秒超时,不重试,失败记 errors);sender=None
  时用内置端,显式 sender 优先;webpush/apns/fcm 仍报
  缺凭证(原因写明:webpush 需浏览器端三字段订阅与标准库
  没有的 P-256 ECDH + AES-GCM;apns 需 HTTP/2;fcm 需
  Firebase 服务账号,三者均无现成接收方,不造假发送)
- worker:run_one 终态(completed/failed,缺 key 与仓库
  失败两条早退路径也在内)后 best-effort 调分发,通知
  失败只记日志、绝不影响任务状态;取消(interrupted)
  不在事件集合里,不通知
- 测试:真实发送端 6 个(本地回环服务器)、worker 终态
  通知 2 个,旧无 sender 断言按新语义改写
This commit is contained in:
host committed 2026-10-26 17:31:30 +08:00
1 parent b7dace55b9
commit fb3a311da8
7 files changed
+429 -29

No files matched your search

+35
View File
@@ -640,6 +640,34 @@ class Worker:
except Exception:
pass
def _notify_task_finished(self, task_id, project_id, event_type,
error=None):
"""任务终态推送:best-effort,绝不因通知失败影响任务状态。
只发 completed/failed(push.ALLOWED_PUSH_EVENTS 的子集;
interrupted 不在事件集合里,取消不通知)。无订阅时
dispatch 直接返回空 sent,不联网。同步发送、单次 10 秒
超时:慢订阅会拖住 worker 循环最多几秒,换取投递结果
可观测(失败记日志,不静默丢);后台队列是后续优化,
不在本轮。
"""
try:
from cloud.push import dispatch_event
result = dispatch_event(
self.db_path, event_type, project_id,
{"task_id": task_id, "status": event_type,
"executor": self._executor_name(), "error": error})
if result.get("errors"):
log_event(self.logger, "worker_notify_failed",
task_id=task_id, project_id=project_id,
status=event_type,
message="notify errors: %s" % (result["errors"],))
except Exception as exc: # noqa: BLE001 -- 通知失败绝不炸任务
log_event(self.logger, "worker_notify_failed",
task_id=task_id, project_id=project_id,
status=event_type,
message="notify exception: %s" % exc)
# -- 认领 ----------------------------------------------------------
def _budget_exceeded(self):
return budget_exceeded(self.usage_turns, self.max_turns,
@@ -1346,6 +1374,8 @@ class Worker:
self._audit(task["project_id"], "worker_missing_key", error)
log_event(self.logger, "worker_missing_key", task_id=task_id,
project_id=task["project_id"], status="failed")
self._notify_task_finished(
task_id, task["project_id"], "failed", error=error)
return {"dispatched": True, "task_id": task_id,
"host_name": self.executor._host_name(task)
if hasattr(self.executor, "_host_name") else task_id,
@@ -1365,6 +1395,8 @@ class Worker:
self._audit(task["project_id"], "worker_repo_failed", repo_error)
log_event(self.logger, "worker_repo_failed", task_id=task_id,
project_id=task["project_id"], status="failed")
self._notify_task_finished(
task_id, task["project_id"], "failed", error=repo_error)
return {"dispatched": True, "task_id": task_id,
"host_name": self.executor._host_name(task)
if hasattr(self.executor, "_host_name") else task_id,
@@ -1537,6 +1569,9 @@ class Worker:
else "worker_failed",
task_id=task_id, project_id=task["project_id"],
status=final)
if final in ("completed", "failed"):
self._notify_task_finished(
task_id, task["project_id"], final, error=error)
else:
# 已被取消/回收:DB 状态以 interrupted 为准,不再
# 补一条 worker_completed/failed 审计误导排查。