service-forge 0.1.18__py3-none-any.whl → 0.1.39__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.
Potentially problematic release.
This version of service-forge might be problematic. Click here for more details.
- service_forge/__init__.py +0 -0
- service_forge/api/deprecated_websocket_api.py +91 -33
- service_forge/api/deprecated_websocket_manager.py +70 -53
- service_forge/api/http_api.py +205 -55
- service_forge/api/kafka_api.py +113 -25
- service_forge/api/routers/meta_api/meta_api_router.py +57 -0
- service_forge/api/routers/service/service_router.py +42 -6
- service_forge/api/routers/trace/trace_router.py +326 -0
- service_forge/api/routers/websocket/websocket_router.py +69 -1
- service_forge/api/service_studio.py +9 -0
- service_forge/db/database.py +17 -0
- service_forge/execution_context.py +106 -0
- service_forge/frontend/static/assets/CreateNewNodeDialog-DkrEMxSH.js +1 -0
- service_forge/frontend/static/assets/CreateNewNodeDialog-DwFcBiGp.css +1 -0
- service_forge/frontend/static/assets/EditorSidePanel-BNVms9Fq.css +1 -0
- service_forge/frontend/static/assets/EditorSidePanel-DZbB3ILL.js +1 -0
- service_forge/frontend/static/assets/FeedbackPanel-CC8HX7Yo.js +1 -0
- service_forge/frontend/static/assets/FeedbackPanel-ClgniIVk.css +1 -0
- service_forge/frontend/static/assets/FormattedCodeViewer.vue_vue_type_script_setup_true_lang-BNuI1NCs.js +1 -0
- service_forge/frontend/static/assets/NodeDetailWrapper-BqFFM7-r.js +1 -0
- service_forge/frontend/static/assets/NodeDetailWrapper-pZBxv3J0.css +1 -0
- service_forge/frontend/static/assets/TestRunningDialog-D0GrCoYs.js +1 -0
- service_forge/frontend/static/assets/TestRunningDialog-dhXOsPgH.css +1 -0
- service_forge/frontend/static/assets/TracePanelWrapper-B9zvDSc_.js +1 -0
- service_forge/frontend/static/assets/TracePanelWrapper-BiednCrq.css +1 -0
- service_forge/frontend/static/assets/WorkflowEditor-CcaGGbko.js +3 -0
- service_forge/frontend/static/assets/WorkflowEditor-CmasOOYK.css +1 -0
- service_forge/frontend/static/assets/WorkflowList-Copuwi-a.css +1 -0
- service_forge/frontend/static/assets/WorkflowList-LrRJ7B7h.js +1 -0
- service_forge/frontend/static/assets/WorkflowStudio-CthjgII2.css +1 -0
- service_forge/frontend/static/assets/WorkflowStudio-FCyhGD4y.js +2 -0
- service_forge/frontend/static/assets/api-BDer3rj7.css +1 -0
- service_forge/frontend/static/assets/api-DyiqpKJK.js +1 -0
- service_forge/frontend/static/assets/code-editor-DBSql_sc.js +12 -0
- service_forge/frontend/static/assets/el-collapse-item-D4LG0FJ0.css +1 -0
- service_forge/frontend/static/assets/el-empty-D4ZqTl4F.css +1 -0
- service_forge/frontend/static/assets/el-form-item-BWkJzdQ_.css +1 -0
- service_forge/frontend/static/assets/el-input-D6B3r8CH.css +1 -0
- service_forge/frontend/static/assets/el-select-B0XIb2QK.css +1 -0
- service_forge/frontend/static/assets/el-tag-DljBBxJR.css +1 -0
- service_forge/frontend/static/assets/element-ui-D3x2y3TA.js +12 -0
- service_forge/frontend/static/assets/elkjs-Dm5QV7uy.js +24 -0
- service_forge/frontend/static/assets/highlightjs-D4ATuRwX.js +3 -0
- service_forge/frontend/static/assets/index-BMvodlwc.js +2 -0
- service_forge/frontend/static/assets/index-CjSe8i2q.css +1 -0
- service_forge/frontend/static/assets/js-yaml-yTPt38rv.js +32 -0
- service_forge/frontend/static/assets/time-DKCKV6Ug.js +1 -0
- service_forge/frontend/static/assets/ui-components-DQ7-U3pr.js +1 -0
- service_forge/frontend/static/assets/vue-core-DL-LgTX0.js +1 -0
- service_forge/frontend/static/assets/vue-flow-Dn7R8GPr.js +39 -0
- service_forge/frontend/static/index.html +16 -0
- service_forge/frontend/static/vite.svg +1 -0
- service_forge/model/meta_api/__init__.py +0 -0
- service_forge/model/meta_api/schema.py +29 -0
- service_forge/model/trace.py +82 -0
- service_forge/service.py +39 -11
- service_forge/service_config.py +14 -0
- service_forge/sft/cli.py +39 -0
- service_forge/sft/cmd/remote_deploy.py +160 -0
- service_forge/sft/cmd/remote_list_tars.py +111 -0
- service_forge/sft/config/injector.py +54 -7
- service_forge/sft/config/injector_default_files.py +13 -1
- service_forge/sft/config/sf_metadata.py +31 -27
- service_forge/sft/config/sft_config.py +18 -0
- service_forge/sft/util/assert_util.py +0 -1
- service_forge/telemetry.py +66 -0
- service_forge/utils/default_type_converter.py +1 -1
- service_forge/utils/type_converter.py +5 -0
- service_forge/utils/workflow_clone.py +1 -0
- service_forge/workflow/node.py +274 -27
- service_forge/workflow/triggers/fast_api_trigger.py +64 -28
- service_forge/workflow/triggers/websocket_api_trigger.py +66 -38
- service_forge/workflow/workflow.py +140 -37
- service_forge/workflow/workflow_callback.py +27 -4
- service_forge/workflow/workflow_factory.py +14 -0
- {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/METADATA +4 -1
- service_forge-0.1.39.dist-info/RECORD +134 -0
- service_forge-0.1.18.dist-info/RECORD +0 -83
- {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/WHEEL +0 -0
- {service_forge-0.1.18.dist-info → service_forge-0.1.39.dist-info}/entry_points.txt +0 -0
|
File without changes
|
|
@@ -1,37 +1,95 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
|
-
|
|
5
|
+
import uuid
|
|
6
|
+
from dataclasses import replace
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
from fastapi import APIRouter, Depends, Query, Request, WebSocket, WebSocketDisconnect
|
|
6
10
|
from fastapi.responses import JSONResponse
|
|
7
11
|
from loguru import logger
|
|
8
|
-
from
|
|
12
|
+
from opentelemetry import context as otel_context_api
|
|
13
|
+
from opentelemetry import propagate, trace
|
|
14
|
+
|
|
15
|
+
from service_forge.execution_context import (
|
|
16
|
+
ExecutionContext,
|
|
17
|
+
reset_current_context,
|
|
18
|
+
set_current_context,
|
|
19
|
+
)
|
|
20
|
+
|
|
9
21
|
from .websocket_manager import websocket_manager
|
|
10
22
|
|
|
11
23
|
router = APIRouter(prefix="/ws", tags=["websocket"])
|
|
24
|
+
tracer = trace.get_tracer("service_forge.api.websocket")
|
|
25
|
+
|
|
12
26
|
|
|
13
27
|
@router.websocket("/connect")
|
|
14
|
-
async def websocket_endpoint(
|
|
28
|
+
async def websocket_endpoint(
|
|
29
|
+
websocket: WebSocket, client_id: Optional[str] = Query(None)
|
|
30
|
+
):
|
|
15
31
|
"""WebSocket连接端点,支持指定客户端ID"""
|
|
16
|
-
client_id = await websocket_manager.connect(websocket, client_id)
|
|
17
32
|
try:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
parent_context = propagate.extract(dict(websocket.headers))
|
|
34
|
+
except Exception:
|
|
35
|
+
parent_context = otel_context_api.get_current()
|
|
36
|
+
if parent_context is None:
|
|
37
|
+
parent_context = otel_context_api.get_current()
|
|
38
|
+
|
|
39
|
+
span_name = f"WS {websocket.url.path}"
|
|
40
|
+
with tracer.start_as_current_span(
|
|
41
|
+
span_name,
|
|
42
|
+
context=parent_context,
|
|
43
|
+
kind=trace.SpanKind.SERVER,
|
|
44
|
+
) as span:
|
|
45
|
+
if websocket.client:
|
|
46
|
+
span.set_attribute("net.peer.ip", websocket.client.host)
|
|
47
|
+
span.set_attribute("net.protocol.name", "websocket")
|
|
48
|
+
span.set_attribute("net.host.port", websocket.url.port or 80)
|
|
49
|
+
|
|
50
|
+
base_context = ExecutionContext(
|
|
51
|
+
trace_context=otel_context_api.get_current(),
|
|
52
|
+
span=span,
|
|
53
|
+
logger=logger.bind(entrypoint="websocket"),
|
|
54
|
+
metadata={
|
|
55
|
+
"entrypoint": "websocket",
|
|
56
|
+
"path": websocket.url.path,
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
token = set_current_context(base_context)
|
|
60
|
+
|
|
61
|
+
client_id = await websocket_manager.connect(websocket, client_id)
|
|
62
|
+
span.set_attribute("client.id", client_id)
|
|
63
|
+
|
|
64
|
+
execution_context = replace(
|
|
65
|
+
base_context,
|
|
66
|
+
metadata={**base_context.metadata, "client_id": client_id},
|
|
67
|
+
)
|
|
68
|
+
websocket_manager.set_context(client_id, execution_context)
|
|
69
|
+
|
|
70
|
+
reset_current_context(token)
|
|
71
|
+
token = set_current_context(execution_context)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
while True:
|
|
75
|
+
data = await websocket.receive_text()
|
|
76
|
+
try:
|
|
77
|
+
message = json.loads(data)
|
|
78
|
+
await handle_client_message(client_id, message)
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
logger.error(f"从客户端 {client_id} 收到无效JSON消息: {data}")
|
|
81
|
+
await websocket_manager.send_personal_message(
|
|
82
|
+
json.dumps({"error": "Invalid JSON format"}), client_id
|
|
83
|
+
)
|
|
84
|
+
except WebSocketDisconnect:
|
|
85
|
+
websocket_manager.disconnect(client_id)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"WebSocket连接处理异常: {e}")
|
|
88
|
+
websocket_manager.disconnect(client_id)
|
|
89
|
+
finally:
|
|
90
|
+
reset_current_context(token)
|
|
91
|
+
websocket_manager.clear_context(client_id)
|
|
92
|
+
|
|
35
93
|
|
|
36
94
|
async def handle_client_message(client_id: str, message: Dict[str, Any]):
|
|
37
95
|
"""处理来自客户端的消息"""
|
|
@@ -42,8 +100,7 @@ async def handle_client_message(client_id: str, message: Dict[str, Any]):
|
|
|
42
100
|
task_id_str = message.get("task_id")
|
|
43
101
|
if not task_id_str:
|
|
44
102
|
await websocket_manager.send_personal_message(
|
|
45
|
-
json.dumps({"error": "Missing task_id in subscribe message"}),
|
|
46
|
-
client_id
|
|
103
|
+
json.dumps({"error": "Missing task_id in subscribe message"}), client_id
|
|
47
104
|
)
|
|
48
105
|
return
|
|
49
106
|
|
|
@@ -51,11 +108,12 @@ async def handle_client_message(client_id: str, message: Dict[str, Any]):
|
|
|
51
108
|
task_id = uuid.UUID(task_id_str)
|
|
52
109
|
success = await websocket_manager.subscribe_to_task(client_id, task_id)
|
|
53
110
|
response = {"success": success}
|
|
54
|
-
await websocket_manager.send_personal_message(
|
|
111
|
+
await websocket_manager.send_personal_message(
|
|
112
|
+
json.dumps(response), client_id
|
|
113
|
+
)
|
|
55
114
|
except ValueError:
|
|
56
115
|
await websocket_manager.send_personal_message(
|
|
57
|
-
json.dumps({"error": "Invalid task_id format"}),
|
|
58
|
-
client_id
|
|
116
|
+
json.dumps({"error": "Invalid task_id format"}), client_id
|
|
59
117
|
)
|
|
60
118
|
|
|
61
119
|
elif message_type == "unsubscribe":
|
|
@@ -64,7 +122,7 @@ async def handle_client_message(client_id: str, message: Dict[str, Any]):
|
|
|
64
122
|
if not task_id_str:
|
|
65
123
|
await websocket_manager.send_personal_message(
|
|
66
124
|
json.dumps({"error": "Missing task_id in unsubscribe message"}),
|
|
67
|
-
client_id
|
|
125
|
+
client_id,
|
|
68
126
|
)
|
|
69
127
|
return
|
|
70
128
|
|
|
@@ -72,15 +130,15 @@ async def handle_client_message(client_id: str, message: Dict[str, Any]):
|
|
|
72
130
|
task_id = uuid.UUID(task_id_str)
|
|
73
131
|
success = await websocket_manager.unsubscribe_from_task(client_id, task_id)
|
|
74
132
|
response = {"success": success}
|
|
75
|
-
await websocket_manager.send_personal_message(
|
|
133
|
+
await websocket_manager.send_personal_message(
|
|
134
|
+
json.dumps(response), client_id
|
|
135
|
+
)
|
|
76
136
|
except ValueError:
|
|
77
137
|
await websocket_manager.send_personal_message(
|
|
78
|
-
json.dumps({"error": "Invalid task_id format"}),
|
|
79
|
-
client_id
|
|
138
|
+
json.dumps({"error": "Invalid task_id format"}), client_id
|
|
80
139
|
)
|
|
81
140
|
|
|
82
141
|
else:
|
|
83
142
|
await websocket_manager.send_personal_message(
|
|
84
|
-
json.dumps({"error": f"Unknown message type: {message_type}"}),
|
|
85
|
-
client_id
|
|
143
|
+
json.dumps({"error": f"Unknown message type: {message_type}"}), client_id
|
|
86
144
|
)
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
|
-
import uuid
|
|
4
4
|
import json
|
|
5
|
-
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
6
8
|
from fastapi import WebSocket, WebSocketDisconnect
|
|
7
9
|
from loguru import logger
|
|
10
|
+
|
|
11
|
+
from ..execution_context import ExecutionContext
|
|
8
12
|
from .task_manager import TaskManager
|
|
9
13
|
|
|
14
|
+
|
|
10
15
|
class WebSocketManager:
|
|
11
16
|
def __init__(self):
|
|
12
17
|
# 存储活动连接: {client_id: websocket}
|
|
13
|
-
self.active_connections:
|
|
18
|
+
self.active_connections: dict[str, WebSocket] = {}
|
|
19
|
+
# 存储链接的 ExecutionContext
|
|
20
|
+
self.connection_contexts: dict[str, ExecutionContext] = {}
|
|
14
21
|
# 存储任务与客户端的映射: {task_id: client_id}
|
|
15
|
-
self.task_client_mapping:
|
|
22
|
+
self.task_client_mapping: dict[uuid.UUID, str] = {}
|
|
16
23
|
# 存储客户端订阅的任务: {client_id: set(task_id)}
|
|
17
|
-
self.client_task_subscriptions:
|
|
24
|
+
self.client_task_subscriptions: dict[str, set[uuid.UUID]] = {}
|
|
18
25
|
# 存储客户端历史记录,用于重连时恢复订阅: {client_id: last_active_time}
|
|
19
|
-
self.client_history:
|
|
26
|
+
self.client_history: dict[str, float] = {}
|
|
20
27
|
# 设置客户端记录过期时间(秒),默认0.5小时
|
|
21
28
|
self.client_history_expiry = 0.5 * 60 * 60
|
|
22
29
|
# 初始化任务管理器
|
|
@@ -51,11 +58,14 @@ class WebSocketManager:
|
|
|
51
58
|
"type": "connection established",
|
|
52
59
|
"client_id": client_id,
|
|
53
60
|
"timestamp": str(asyncio.get_event_loop().time()),
|
|
54
|
-
"restored_subscriptions": []
|
|
61
|
+
"restored_subscriptions": [],
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
# 如果有历史订阅,恢复它们
|
|
58
|
-
if
|
|
65
|
+
if (
|
|
66
|
+
client_id in self.client_task_subscriptions
|
|
67
|
+
and self.client_task_subscriptions[client_id]
|
|
68
|
+
):
|
|
59
69
|
restored_tasks = []
|
|
60
70
|
for task_id in self.client_task_subscriptions[client_id]:
|
|
61
71
|
restored_tasks.append(str(task_id))
|
|
@@ -74,6 +84,7 @@ class WebSocketManager:
|
|
|
74
84
|
# 更新客户端的最后活动时间
|
|
75
85
|
self.client_history[client_id] = asyncio.get_event_loop().time()
|
|
76
86
|
logger.info(f"客户端 {client_id} 已断开WebSocket连接,保留订阅信息")
|
|
87
|
+
self.clear_context(client_id)
|
|
77
88
|
|
|
78
89
|
async def subscribe_to_task(self, client_id: str, task_id: uuid.UUID) -> bool:
|
|
79
90
|
"""客户端订阅任务"""
|
|
@@ -98,7 +109,22 @@ class WebSocketManager:
|
|
|
98
109
|
|
|
99
110
|
return False
|
|
100
111
|
|
|
101
|
-
def
|
|
112
|
+
def set_context(self, client_id: str, context: ExecutionContext) -> None:
|
|
113
|
+
self.connection_contexts[client_id] = context
|
|
114
|
+
|
|
115
|
+
def get_context(self, client_id: str) -> ExecutionContext | None:
|
|
116
|
+
return self.connection_contexts.get(client_id)
|
|
117
|
+
|
|
118
|
+
def clear_context(self, client_id: str) -> None:
|
|
119
|
+
self.connection_contexts.pop(client_id, None)
|
|
120
|
+
|
|
121
|
+
def create_task_with_client(
|
|
122
|
+
self,
|
|
123
|
+
task_id: uuid.UUID,
|
|
124
|
+
client_id: str,
|
|
125
|
+
workflow_name: str = "Unknown",
|
|
126
|
+
steps: int = 1,
|
|
127
|
+
) -> bool:
|
|
102
128
|
"""创建任务与客户端的映射,并添加到任务管理器"""
|
|
103
129
|
# 建立任务与客户端的映射
|
|
104
130
|
self.task_client_mapping[task_id] = client_id
|
|
@@ -107,7 +133,7 @@ class WebSocketManager:
|
|
|
107
133
|
if client_id not in self.client_task_subscriptions:
|
|
108
134
|
self.client_task_subscriptions[client_id] = set()
|
|
109
135
|
self.client_task_subscriptions[client_id].add(task_id)
|
|
110
|
-
|
|
136
|
+
|
|
111
137
|
# 添加任务到任务管理器
|
|
112
138
|
self.task_manager.add_task(task_id, client_id, workflow_name, steps)
|
|
113
139
|
|
|
@@ -132,11 +158,11 @@ class WebSocketManager:
|
|
|
132
158
|
return # 没有关联的客户端
|
|
133
159
|
|
|
134
160
|
client_id = self.task_client_mapping[task_id]
|
|
135
|
-
|
|
161
|
+
|
|
136
162
|
# 确保task_id是字符串,避免JSON序列化问题
|
|
137
163
|
if "task_id" in message and isinstance(message["task_id"], uuid.UUID):
|
|
138
164
|
message["task_id"] = str(message["task_id"])
|
|
139
|
-
|
|
165
|
+
|
|
140
166
|
# 递归处理嵌套字典中的UUID
|
|
141
167
|
def convert_uuids(obj):
|
|
142
168
|
if isinstance(obj, dict):
|
|
@@ -147,18 +173,21 @@ class WebSocketManager:
|
|
|
147
173
|
return str(obj)
|
|
148
174
|
else:
|
|
149
175
|
return obj
|
|
150
|
-
|
|
176
|
+
|
|
151
177
|
message = convert_uuids(message)
|
|
152
178
|
message_str = json.dumps(message)
|
|
153
179
|
await self.send_personal_message(message_str, client_id)
|
|
154
180
|
|
|
155
|
-
async def send_task_status(
|
|
181
|
+
async def send_task_status(
|
|
182
|
+
self,
|
|
183
|
+
task_id: uuid.UUID,
|
|
184
|
+
status: str,
|
|
185
|
+
node: str = None,
|
|
186
|
+
progress: float = None,
|
|
187
|
+
error: str = None,
|
|
188
|
+
):
|
|
156
189
|
"""发送任务状态更新"""
|
|
157
|
-
message = {
|
|
158
|
-
"task_id": str(task_id),
|
|
159
|
-
"type": "status",
|
|
160
|
-
"status": status
|
|
161
|
-
}
|
|
190
|
+
message = {"task_id": str(task_id), "type": "status", "status": status}
|
|
162
191
|
|
|
163
192
|
if node is not None:
|
|
164
193
|
message["node"] = node
|
|
@@ -175,7 +204,7 @@ class WebSocketManager:
|
|
|
175
204
|
"""发送任务开始执行消息"""
|
|
176
205
|
# 获取客户端ID
|
|
177
206
|
client_id = self.task_client_mapping.get(task_id)
|
|
178
|
-
|
|
207
|
+
|
|
179
208
|
# 更新任务状态为运行中
|
|
180
209
|
self.task_manager.start_task(task_id)
|
|
181
210
|
|
|
@@ -186,19 +215,16 @@ class WebSocketManager:
|
|
|
186
215
|
|
|
187
216
|
# 获取全局任务队列信息
|
|
188
217
|
global_queue_info = self.task_manager.get_global_queue_info()
|
|
189
|
-
|
|
218
|
+
|
|
190
219
|
# 获取当前任务在队列中的位置
|
|
191
220
|
queue_position = self.task_manager.get_queue_position(task_id)
|
|
192
221
|
|
|
193
222
|
message = {
|
|
194
223
|
"task_id": str(task_id),
|
|
195
224
|
"type": "execution start",
|
|
196
|
-
"client_tasks": {
|
|
197
|
-
"total": len(client_tasks),
|
|
198
|
-
"tasks": client_tasks
|
|
199
|
-
},
|
|
225
|
+
"client_tasks": {"total": len(client_tasks), "tasks": client_tasks},
|
|
200
226
|
"global_queue": global_queue_info,
|
|
201
|
-
"queue_position": queue_position
|
|
227
|
+
"queue_position": queue_position,
|
|
202
228
|
}
|
|
203
229
|
await self.send_to_task_client(task_id, message)
|
|
204
230
|
|
|
@@ -214,7 +240,7 @@ class WebSocketManager:
|
|
|
214
240
|
|
|
215
241
|
# 获取全局任务队列信息
|
|
216
242
|
global_queue_info = self.task_manager.get_global_queue_info()
|
|
217
|
-
|
|
243
|
+
|
|
218
244
|
# 获取当前任务在队列中的位置
|
|
219
245
|
queue_position = self.task_manager.get_queue_position(task_id)
|
|
220
246
|
|
|
@@ -222,12 +248,9 @@ class WebSocketManager:
|
|
|
222
248
|
"task_id": str(task_id),
|
|
223
249
|
"type": "executing",
|
|
224
250
|
"node": node,
|
|
225
|
-
"client_tasks": {
|
|
226
|
-
"total": len(client_tasks),
|
|
227
|
-
"tasks": client_tasks
|
|
228
|
-
},
|
|
251
|
+
"client_tasks": {"total": len(client_tasks), "tasks": client_tasks},
|
|
229
252
|
"global_queue": global_queue_info,
|
|
230
|
-
"queue_position": queue_position
|
|
253
|
+
"queue_position": queue_position,
|
|
231
254
|
}
|
|
232
255
|
await self.send_to_task_client(task_id, message)
|
|
233
256
|
|
|
@@ -243,7 +266,7 @@ class WebSocketManager:
|
|
|
243
266
|
|
|
244
267
|
# 获取全局任务队列信息
|
|
245
268
|
global_queue_info = self.task_manager.get_global_queue_info()
|
|
246
|
-
|
|
269
|
+
|
|
247
270
|
# 获取当前任务在队列中的位置
|
|
248
271
|
queue_position = self.task_manager.get_queue_position(task_id)
|
|
249
272
|
|
|
@@ -252,12 +275,9 @@ class WebSocketManager:
|
|
|
252
275
|
"type": "progress",
|
|
253
276
|
"node": node,
|
|
254
277
|
"progress": progress,
|
|
255
|
-
"client_tasks": {
|
|
256
|
-
"total": len(client_tasks),
|
|
257
|
-
"tasks": client_tasks
|
|
258
|
-
},
|
|
278
|
+
"client_tasks": {"total": len(client_tasks), "tasks": client_tasks},
|
|
259
279
|
"global_queue": global_queue_info,
|
|
260
|
-
"queue_position": queue_position
|
|
280
|
+
"queue_position": queue_position,
|
|
261
281
|
}
|
|
262
282
|
await self.send_to_task_client(task_id, message)
|
|
263
283
|
|
|
@@ -273,7 +293,7 @@ class WebSocketManager:
|
|
|
273
293
|
|
|
274
294
|
# 获取全局任务队列信息
|
|
275
295
|
global_queue_info = self.task_manager.get_global_queue_info()
|
|
276
|
-
|
|
296
|
+
|
|
277
297
|
# 获取当前任务在队列中的位置
|
|
278
298
|
queue_position = self.task_manager.get_queue_position(task_id)
|
|
279
299
|
|
|
@@ -281,17 +301,15 @@ class WebSocketManager:
|
|
|
281
301
|
"task_id": str(task_id),
|
|
282
302
|
"type": "executed",
|
|
283
303
|
"node": node,
|
|
284
|
-
"client_tasks": {
|
|
285
|
-
"total": len(client_tasks),
|
|
286
|
-
"tasks": client_tasks
|
|
287
|
-
},
|
|
304
|
+
"client_tasks": {"total": len(client_tasks), "tasks": client_tasks},
|
|
288
305
|
"global_queue": global_queue_info,
|
|
289
|
-
"queue_position": queue_position
|
|
306
|
+
"queue_position": queue_position,
|
|
290
307
|
}
|
|
291
308
|
|
|
292
309
|
if result is not None:
|
|
293
310
|
# 检查是否为协程对象
|
|
294
311
|
import asyncio
|
|
312
|
+
|
|
295
313
|
if asyncio.iscoroutine(result):
|
|
296
314
|
message["result"] = "<coroutine object>"
|
|
297
315
|
else:
|
|
@@ -307,7 +325,7 @@ class WebSocketManager:
|
|
|
307
325
|
"""发送执行错误消息"""
|
|
308
326
|
# 获取客户端ID
|
|
309
327
|
client_id = self.task_client_mapping.get(task_id)
|
|
310
|
-
|
|
328
|
+
|
|
311
329
|
# 更新任务状态为失败
|
|
312
330
|
self.task_manager.fail_task(task_id, error)
|
|
313
331
|
|
|
@@ -318,7 +336,7 @@ class WebSocketManager:
|
|
|
318
336
|
|
|
319
337
|
# 获取全局任务队列信息
|
|
320
338
|
global_queue_info = self.task_manager.get_global_queue_info()
|
|
321
|
-
|
|
339
|
+
|
|
322
340
|
# 获取当前任务在队列中的位置
|
|
323
341
|
queue_position = self.task_manager.get_queue_position(task_id)
|
|
324
342
|
|
|
@@ -327,12 +345,9 @@ class WebSocketManager:
|
|
|
327
345
|
"type": "execution error",
|
|
328
346
|
"node": node,
|
|
329
347
|
"error": error,
|
|
330
|
-
"client_tasks": {
|
|
331
|
-
"total": len(client_tasks),
|
|
332
|
-
"tasks": client_tasks
|
|
333
|
-
},
|
|
348
|
+
"client_tasks": {"total": len(client_tasks), "tasks": client_tasks},
|
|
334
349
|
"global_queue": global_queue_info,
|
|
335
|
-
"queue_position": queue_position
|
|
350
|
+
"queue_position": queue_position,
|
|
336
351
|
}
|
|
337
352
|
await self.send_to_task_client(task_id, message)
|
|
338
353
|
|
|
@@ -360,8 +375,10 @@ class WebSocketManager:
|
|
|
360
375
|
# 查找过期的客户端记录
|
|
361
376
|
for client_id, last_active_time in self.client_history.items():
|
|
362
377
|
# 如果客户端不在活动连接中且超过过期时间,则标记为过期
|
|
363
|
-
if (
|
|
364
|
-
|
|
378
|
+
if (
|
|
379
|
+
client_id not in self.active_connections
|
|
380
|
+
and current_time - last_active_time > self.client_history_expiry
|
|
381
|
+
):
|
|
365
382
|
expired_clients.append(client_id)
|
|
366
383
|
|
|
367
384
|
# 清理过期客户端的订阅记录
|