celestialflow 3.2.6__tar.gz → 3.2.8__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.
- {celestialflow-3.2.6/src/celestialflow.egg-info → celestialflow-3.2.8}/PKG-INFO +131 -21
- {celestialflow-3.2.6 → celestialflow-3.2.8}/README.md +130 -19
- {celestialflow-3.2.6 → celestialflow-3.2.8}/pyproject.toml +9 -2
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/__init__.py +4 -4
- celestialflow-3.2.8/src/celestialflow/benchmark/__init__.py +6 -0
- {celestialflow-3.2.6/src/celestialflow/utils → celestialflow-3.2.8/src/celestialflow/benchmark}/util_benchmark.py +20 -17
- {celestialflow-3.2.6/src/celestialflow/utils → celestialflow-3.2.8/src/celestialflow/benchmark}/util_clone.py +34 -12
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/funnel/core_spout.py +4 -1
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/graph/core_graph.py +306 -287
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/graph/core_structure.py +40 -21
- {celestialflow-3.2.6/src/celestialflow/runtime → celestialflow-3.2.8/src/celestialflow/graph}/util_estimators.py +15 -65
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/graph/util_graph.py +2 -3
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/graph/util_serialize.py +7 -7
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/observability/__init__.py +2 -3
- celestialflow-3.2.8/src/celestialflow/observability/core_observer.py +112 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/observability/core_report.py +42 -34
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/observability/util_types.py +14 -7
- celestialflow-3.2.8/src/celestialflow/persistence/__init__.py +31 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/persistence/core_fallback.py +23 -10
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/persistence/core_log.py +82 -57
- celestialflow-3.2.8/src/celestialflow/persistence/core_scope.py +41 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/persistence/util_payload.py +2 -1
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/persistence/util_sqlite.py +10 -8
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/__init__.py +1 -4
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/core_envelope.py +11 -11
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/core_metrics.py +77 -14
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/core_queue.py +26 -44
- celestialflow-3.2.8/src/celestialflow/runtime/util_config.py +40 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/util_errors.py +207 -285
- celestialflow-3.2.8/src/celestialflow/runtime/util_estimators.py +68 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/util_event.py +63 -63
- {celestialflow-3.2.6/src/celestialflow/utils → celestialflow-3.2.8/src/celestialflow/runtime}/util_format.py +10 -18
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/util_hash.py +3 -3
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/util_types.py +2 -6
- {celestialflow-3.2.6/src/celestialflow/runtime → celestialflow-3.2.8/src/celestialflow/stage}/core_dispatch.py +68 -55
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/stage/core_executor.py +692 -698
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/stage/core_stage.py +172 -236
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/stage/core_stages.py +23 -15
- {celestialflow-3.2.6 → celestialflow-3.2.8/src/celestialflow.egg-info}/PKG-INFO +131 -21
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow.egg-info/SOURCES.txt +9 -8
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow.egg-info/requires.txt +0 -1
- celestialflow-3.2.6/src/celestialflow/observability/core_observer.py +0 -51
- celestialflow-3.2.6/src/celestialflow/observability/core_progress.py +0 -61
- celestialflow-3.2.6/src/celestialflow/persistence/__init__.py +0 -15
- celestialflow-3.2.6/src/celestialflow/utils/__init__.py +0 -5
- celestialflow-3.2.6/src/celestialflow/utils/util_collections.py +0 -16
- {celestialflow-3.2.6 → celestialflow-3.2.8}/LICENSE +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/setup.cfg +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/funnel/__init__.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/funnel/core_inlet.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/funnel/util_count.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/graph/__init__.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/runtime/util_constant.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/stage/__init__.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/stage/util_callable.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow/stage/util_types.py +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow.egg-info/dependency_links.txt +0 -0
- {celestialflow-3.2.6 → celestialflow-3.2.8}/src/celestialflow.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: celestialflow
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.8
|
|
4
4
|
Summary: A flexible GRAPH-based task orchestration framework.
|
|
5
5
|
Author-email: Mr-xiaotian <mingxiaomingtian@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -14,7 +14,6 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
14
14
|
Requires-Python: >=3.12
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: tqdm
|
|
18
17
|
Requires-Dist: requests
|
|
19
18
|
Dynamic: license-file
|
|
20
19
|
|
|
@@ -134,16 +133,27 @@ uv sync --group dev
|
|
|
134
133
|
```python
|
|
135
134
|
from celestialflow import TaskStage, TaskGraph
|
|
136
135
|
|
|
137
|
-
|
|
136
|
+
|
|
137
|
+
def add(x, y):
|
|
138
138
|
return x + y
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
|
|
141
|
+
def square(x):
|
|
142
|
+
return x**2
|
|
143
|
+
|
|
142
144
|
|
|
143
145
|
if __name__ == "__main__":
|
|
144
146
|
# 定义两个任务节点
|
|
145
|
-
stage1 = TaskStage(
|
|
146
|
-
|
|
147
|
+
stage1 = TaskStage(
|
|
148
|
+
name="Adder",
|
|
149
|
+
func=add,
|
|
150
|
+
stage_mode="thread",
|
|
151
|
+
execution_mode="thread",
|
|
152
|
+
unpack_task_args=True,
|
|
153
|
+
)
|
|
154
|
+
stage2 = TaskStage(
|
|
155
|
+
name="Squarer", func=square, stage_mode="thread", execution_mode="thread"
|
|
156
|
+
)
|
|
147
157
|
|
|
148
158
|
# 构建任务图结构
|
|
149
159
|
graph = TaskGraph(name="DemoGraph")
|
|
@@ -165,12 +175,12 @@ if __name__ == "__main__":
|
|
|
165
175
|
- [TaskExecutor.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/stage/core_executor.md)
|
|
166
176
|
- [TaskStage.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/stage/core_stage.md)
|
|
167
177
|
- [TaskGraph.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/graph/core_graph.md)
|
|
168
|
-
- [TaskProgress.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/observability/core_progress.md)
|
|
169
178
|
- [TaskMetrics.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/runtime/core_metrics.md)
|
|
170
179
|
- [TaskQueue.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/runtime/core_queue.md)
|
|
171
180
|
- [TaskStages.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/stage/core_stages.md)
|
|
172
181
|
- [TaskReport.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/observability/core_report.md)
|
|
173
182
|
- [TaskStructure.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/graph/core_structure.md)
|
|
183
|
+
- [BaseObserver.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/observability/core_observer.md)
|
|
174
184
|
- [Go Worker.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/other/go_worker.md)
|
|
175
185
|
|
|
176
186
|
推荐阅读顺序:
|
|
@@ -183,7 +193,7 @@ flowchart TD
|
|
|
183
193
|
classDef execution fill:#f3e8ff,stroke:#a855f7,color:#581c87;
|
|
184
194
|
|
|
185
195
|
TM[TaskExecutor.md] --> TS[TaskStage.md] --> TG[TaskGraph.md]
|
|
186
|
-
TM -->
|
|
196
|
+
TM --> OB[BaseObserver.md]
|
|
187
197
|
TM --> TME[TaskMetrics.md]
|
|
188
198
|
|
|
189
199
|
TG --> TQ[TaskQueue.md]
|
|
@@ -247,26 +257,126 @@ flowchart TD
|
|
|
247
257
|
|
|
248
258
|
## 文件结构(File Structure)
|
|
249
259
|
|
|
260
|
+
```
|
|
261
|
+
📁 CelestialFlow (587MB 684KB 313B)
|
|
262
|
+
📁 bench (296KB 842B)
|
|
263
|
+
📁 [1项排除的目录] (194KB 222B)
|
|
264
|
+
🐍 bench_datastructures.py (6KB 690B)
|
|
265
|
+
🐍 bench_execution_mode.py (2KB 831B)
|
|
266
|
+
🐍 bench_futures_memory.py (2KB 269B)
|
|
267
|
+
🐍 bench_gil_vs_nogil.py (10KB 7B)
|
|
268
|
+
🐍 bench_graph_mode.py (7KB 450B)
|
|
269
|
+
🐍 bench_hash.py (7KB 67B)
|
|
270
|
+
🐍 bench_hash_container.py (3KB 1009B)
|
|
271
|
+
🐍 bench_hash_memory.py (3KB 642B)
|
|
272
|
+
🐍 bench_http_grpc.py (2KB 608B)
|
|
273
|
+
🐍 bench_ipc_queue.py (7KB 104B)
|
|
274
|
+
🐍 bench_lock_overhead.py (9KB 421B)
|
|
275
|
+
🐍 bench_mpqueue_vs_shared_memory.py (13KB 127B)
|
|
276
|
+
🐍 bench_observer.py (7KB 860B)
|
|
277
|
+
🐍 bench_persistence_spout.py (4KB 279B)
|
|
278
|
+
🐍 bench_queue.py (5KB 857B)
|
|
279
|
+
🐍 bench_requests.py (6KB 813B)
|
|
280
|
+
🐍 bench_tqdm.py (1KB 235B)
|
|
281
|
+
🐍 bench_utils.py (543B)
|
|
282
|
+
📁 demo (147KB 87B)
|
|
283
|
+
📁 [1项排除的目录] (100KB 9B)
|
|
284
|
+
🐍 demo_executor.py (1KB 495B)
|
|
285
|
+
🐍 demo_funnel.py (2KB 289B)
|
|
286
|
+
🐍 demo_graph.py (3KB 565B)
|
|
287
|
+
🐍 demo_network.py (3KB 879B)
|
|
288
|
+
🐍 demo_observer.py (4KB 270B)
|
|
289
|
+
🐍 demo_redis.py (9KB 144B)
|
|
290
|
+
🐍 demo_stages.py (4KB 336B)
|
|
291
|
+
🐍 demo_structure.py (12KB 24B)
|
|
292
|
+
🐍 demo_utils.py (6KB 148B)
|
|
293
|
+
📁 dist (147KB 48B)
|
|
294
|
+
❓ .gitignore (1B)
|
|
295
|
+
❓ celestialflow-3.2.7-py3-none-any.whl (79KB 235B)
|
|
296
|
+
📦 celestialflow-3.2.7.tar.gz (67KB 836B)
|
|
297
|
+
📁 docs (1MB 722KB 21B)
|
|
298
|
+
📁 en[已折叠] (568KB 991B)
|
|
299
|
+
📁 ja[已折叠] (642KB 383B)
|
|
300
|
+
📁 zh-CN[已折叠] (534KB 695B)
|
|
301
|
+
📁 experiments (2KB 1015B)
|
|
302
|
+
🐍 experiment_networkx.py (1KB 878B)
|
|
303
|
+
🐍 experiment_tqdm.py (1KB 137B)
|
|
304
|
+
📁 img (5MB 871KB 242B)
|
|
305
|
+
📷 file_structure.svg (4MB 918KB 1000B)
|
|
306
|
+
📷 logo(old).png (836KB 542B)
|
|
307
|
+
📷 logo.png (122KB 747B)
|
|
308
|
+
📷 scc_condensation.svg (17KB 1B)
|
|
309
|
+
📁 src (1MB 858KB 89B)
|
|
310
|
+
📁 celestialflow[已折叠] (1MB 839KB 629B)
|
|
311
|
+
📁 celestialflow.egg-info[已折叠] (18KB 484B)
|
|
312
|
+
📁 tests (4MB 167KB 552B)
|
|
313
|
+
📁 benchmark[已折叠] (38KB 197B)
|
|
314
|
+
📁 funnel[已折叠] (96KB 363B)
|
|
315
|
+
📁 graph[已折叠] (721KB 564B)
|
|
316
|
+
📁 observability[已折叠] (189KB 236B)
|
|
317
|
+
📁 persistence[已折叠] (388KB 426B)
|
|
318
|
+
📁 runtime[已折叠] (1MB 295KB 529B)
|
|
319
|
+
📁 stage[已折叠] (382KB 155B)
|
|
320
|
+
📁 utils[已折叠] (639KB 527B)
|
|
321
|
+
📁 [1项排除的目录] (487KB 589B)
|
|
322
|
+
🐍 conftest.py (1KB 38B)
|
|
323
|
+
🐍 __init__.py (0B)
|
|
324
|
+
📁 [12项排除的目录] (573MB 420KB 160B)
|
|
325
|
+
❓ .env (468B)
|
|
326
|
+
❓ .gitignore (1KB 314B)
|
|
327
|
+
📝 AGENTS.md (1KB 173B)
|
|
328
|
+
❓ LICENSE (1KB 65B)
|
|
329
|
+
❓ Makefile (155B)
|
|
330
|
+
⚙️ pyproject.toml (2KB 668B)
|
|
331
|
+
📝 README.md (19KB 24B)
|
|
332
|
+
🔒 uv.lock (97KB 510B)
|
|
333
|
+
```
|
|
250
334
|
<p align="center">
|
|
251
|
-
<
|
|
252
|
-
<br/>
|
|
253
|
-
<em>celestial-flow 3.2.6</em>
|
|
335
|
+
<em>celestial-flow 3.2.8</em>
|
|
254
336
|
</p>
|
|
255
337
|
|
|
256
338
|
(该视图由我的另一个项目[CelestialVault](https://github.com/Mr-xiaotian/CelestialVault)中inst_file.FileTree.print_tree()生成。转换为图片则借助[Carbon](https://carbon.now.sh)。)
|
|
257
339
|
|
|
258
340
|
## 版本日志(Version Log)
|
|
259
|
-
- 3.2.
|
|
341
|
+
- 3.2.8
|
|
260
342
|
- feat:
|
|
261
|
-
-
|
|
262
|
-
-
|
|
343
|
+
- [IMPORTANT] 添加 `TaskGraph.run_async`, 现在可以直接进行图级别的异步
|
|
344
|
+
- 根据 `bench_graph_mode.py` 最新测试, 在I/O密集型任务中, `serial`+`async`(6.05s),比 `serial`+`serial`(69.04s)快 **11.4x**
|
|
345
|
+
- 在 `core_structure` 中添加报错校验
|
|
346
|
+
- 不再允许设置 `execution_mode=async` 时调用 `TashExecutor.start`(如今的`TashExecutor.run`), 只能调用 `TashExecutor.run_async`
|
|
347
|
+
- 另外调用 `TashExecutor.run_async` 时也会进行模式检查, 不是 `execution_mode=async` 就会抛出异常
|
|
348
|
+
- 在 `BaseObserver` 中添加 `observer_error`, 用于处理 `BaseObserver.on_*` 函数的报错
|
|
349
|
+
- 在 `TaskGraph._finish_start_graph` `TaskExecutor._finish_start` 中对所有执行步骤进行 `try-except`, 以使收尾步骤尽量全部完成
|
|
350
|
+
- 添加 `core_scope`, 用于独立控制 `funnel` 的生命周期, 并用于 `TaskGraph.run/run_async` `TaskExecutor.run/run_async`
|
|
263
351
|
- refactor:
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
|
|
268
|
-
-
|
|
269
|
-
|
|
352
|
+
- [IMPORTANT] 原 `TaskGraph.start_graph/start_graph_async` `TaskExecutor.start/start_async` 已被重命名为 `TaskGraph.run/run_async` `TaskExecutor.run/run_async`
|
|
353
|
+
- 破坏性更新
|
|
354
|
+
- 其底层缘由是为了合并 `TaskStage.start_stage/start_stage_async` 与 `TaskExecutor.start/start_async` 而做出的一系列重构之一
|
|
355
|
+
- 现在的 `TaskGraph.start/start_async` `TaskExecutor.start/start_async` 不再接受任务, 而是专注于处理现有任务列表中的任务
|
|
356
|
+
- [IMPORTANT] 将funnel从 `TaskGraph` `TaskExecutor` 中的显性调用与显性传递, 改为所有使用端均从独立文件中import
|
|
357
|
+
- 一来是因为原先的传递链太丑了, 二来是为了简化 `TaskExecutor.start/start_async` 逻辑, 为其与 `TaskStage.start_stage/start_stage_async` 的合并做准备
|
|
358
|
+
- 移动部分文件以解决部分模块级的循环引用问题
|
|
359
|
+
- 原先并非文件级循环引用, 在使用上并无问题
|
|
360
|
+
- 修改 `TaskDispatch.dispatch_thread` 中处理已完成future的逻辑, 避免cpu浪费
|
|
361
|
+
- 删除 `util_errors` 中一些不必要的错误类
|
|
362
|
+
- 将 `TaskGraph._finalize_stages` 进行拆分, 删除不必要的机制, 将剩余机制移至其他方法
|
|
363
|
+
- 修改 `TaskGraph.set_reporter` 的逻辑, 使其与 `set_ctree` 保持一致
|
|
364
|
+
- 将 `TaskOutQueue` 中的 `queue_list|target_name|_name_to_idx` 改为 `_queues`, 并删除 `put_channel`
|
|
365
|
+
- 我有些困惑为什么最初我没有这么做
|
|
366
|
+
- 在 `TaskExecutor` 中合并 `_get_task_repr` and `_get_result_repr`
|
|
367
|
+
- 这两个方法原先差异巨大, 但后来经过多次其他部分的重构, 现在逻辑已经一致
|
|
368
|
+
- 将 `TaskStage` 中的 `_status` 交给 `TaskMetrics` 维护
|
|
369
|
+
- 依旧是为了合并 `TaskExecutor.start/start_async` 与 `TaskStage.start_stage/start_stage_async`
|
|
370
|
+
- 移除 `TaskGraph` 中的 `put_stage_queue`
|
|
371
|
+
- 任务输入完全使用 `TaskExecutor` 中的 `put_task` `put_signal` 方法
|
|
372
|
+
- fix:
|
|
373
|
+
- 在 `TaskDispatch.worker/worker_async` 中添加错误捕捉, 捕捉为 `CRITICAL` 级错误
|
|
374
|
+
- 避免 `worker` 级出错导致计数错误, 永远无法退出
|
|
375
|
+
- 在 `spout` 与 `reporter` 的 `stop` 操作中添加对于线程没有成功 `join` 的报错
|
|
376
|
+
- 修复 `TaskGraph.restore_db` 与 `TaskExecutor.restore_db` 中开启 `filter_by_error_type=Ture` 时跳过 `status=pending` 任务记录的问题
|
|
377
|
+
- 修复 `TaskGraph` 中部分 `get_*` 方法依赖 `_build_analysis` 的产物, 但 `_build_analysis` 未执行导致的问题
|
|
378
|
+
- 修复 `TaskMetrics` 中 `get_counts` 与 `is_tasks_finished` 中可能导致死锁的问题
|
|
379
|
+
- 修复 `TaskReporter` 中 `stop` 里进行的最后一次 `_refresh_all` 没有错误捕捉, 导致后续收尾未完成的问题
|
|
270
380
|
|
|
271
381
|
更多过往日志可看:
|
|
272
382
|
|
|
@@ -114,16 +114,27 @@ uv sync --group dev
|
|
|
114
114
|
```python
|
|
115
115
|
from celestialflow import TaskStage, TaskGraph
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
|
|
118
|
+
def add(x, y):
|
|
118
119
|
return x + y
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
|
|
122
|
+
def square(x):
|
|
123
|
+
return x**2
|
|
124
|
+
|
|
122
125
|
|
|
123
126
|
if __name__ == "__main__":
|
|
124
127
|
# 定义两个任务节点
|
|
125
|
-
stage1 = TaskStage(
|
|
126
|
-
|
|
128
|
+
stage1 = TaskStage(
|
|
129
|
+
name="Adder",
|
|
130
|
+
func=add,
|
|
131
|
+
stage_mode="thread",
|
|
132
|
+
execution_mode="thread",
|
|
133
|
+
unpack_task_args=True,
|
|
134
|
+
)
|
|
135
|
+
stage2 = TaskStage(
|
|
136
|
+
name="Squarer", func=square, stage_mode="thread", execution_mode="thread"
|
|
137
|
+
)
|
|
127
138
|
|
|
128
139
|
# 构建任务图结构
|
|
129
140
|
graph = TaskGraph(name="DemoGraph")
|
|
@@ -145,12 +156,12 @@ if __name__ == "__main__":
|
|
|
145
156
|
- [TaskExecutor.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/stage/core_executor.md)
|
|
146
157
|
- [TaskStage.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/stage/core_stage.md)
|
|
147
158
|
- [TaskGraph.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/graph/core_graph.md)
|
|
148
|
-
- [TaskProgress.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/observability/core_progress.md)
|
|
149
159
|
- [TaskMetrics.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/runtime/core_metrics.md)
|
|
150
160
|
- [TaskQueue.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/runtime/core_queue.md)
|
|
151
161
|
- [TaskStages.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/stage/core_stages.md)
|
|
152
162
|
- [TaskReport.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/observability/core_report.md)
|
|
153
163
|
- [TaskStructure.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/graph/core_structure.md)
|
|
164
|
+
- [BaseObserver.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/src/observability/core_observer.md)
|
|
154
165
|
- [Go Worker.md](https://github.com/Mr-xiaotian/CelestialFlow/blob/main/docs/zh-CN/other/go_worker.md)
|
|
155
166
|
|
|
156
167
|
推荐阅读顺序:
|
|
@@ -163,7 +174,7 @@ flowchart TD
|
|
|
163
174
|
classDef execution fill:#f3e8ff,stroke:#a855f7,color:#581c87;
|
|
164
175
|
|
|
165
176
|
TM[TaskExecutor.md] --> TS[TaskStage.md] --> TG[TaskGraph.md]
|
|
166
|
-
TM -->
|
|
177
|
+
TM --> OB[BaseObserver.md]
|
|
167
178
|
TM --> TME[TaskMetrics.md]
|
|
168
179
|
|
|
169
180
|
TG --> TQ[TaskQueue.md]
|
|
@@ -227,26 +238,126 @@ flowchart TD
|
|
|
227
238
|
|
|
228
239
|
## 文件结构(File Structure)
|
|
229
240
|
|
|
241
|
+
```
|
|
242
|
+
📁 CelestialFlow (587MB 684KB 313B)
|
|
243
|
+
📁 bench (296KB 842B)
|
|
244
|
+
📁 [1项排除的目录] (194KB 222B)
|
|
245
|
+
🐍 bench_datastructures.py (6KB 690B)
|
|
246
|
+
🐍 bench_execution_mode.py (2KB 831B)
|
|
247
|
+
🐍 bench_futures_memory.py (2KB 269B)
|
|
248
|
+
🐍 bench_gil_vs_nogil.py (10KB 7B)
|
|
249
|
+
🐍 bench_graph_mode.py (7KB 450B)
|
|
250
|
+
🐍 bench_hash.py (7KB 67B)
|
|
251
|
+
🐍 bench_hash_container.py (3KB 1009B)
|
|
252
|
+
🐍 bench_hash_memory.py (3KB 642B)
|
|
253
|
+
🐍 bench_http_grpc.py (2KB 608B)
|
|
254
|
+
🐍 bench_ipc_queue.py (7KB 104B)
|
|
255
|
+
🐍 bench_lock_overhead.py (9KB 421B)
|
|
256
|
+
🐍 bench_mpqueue_vs_shared_memory.py (13KB 127B)
|
|
257
|
+
🐍 bench_observer.py (7KB 860B)
|
|
258
|
+
🐍 bench_persistence_spout.py (4KB 279B)
|
|
259
|
+
🐍 bench_queue.py (5KB 857B)
|
|
260
|
+
🐍 bench_requests.py (6KB 813B)
|
|
261
|
+
🐍 bench_tqdm.py (1KB 235B)
|
|
262
|
+
🐍 bench_utils.py (543B)
|
|
263
|
+
📁 demo (147KB 87B)
|
|
264
|
+
📁 [1项排除的目录] (100KB 9B)
|
|
265
|
+
🐍 demo_executor.py (1KB 495B)
|
|
266
|
+
🐍 demo_funnel.py (2KB 289B)
|
|
267
|
+
🐍 demo_graph.py (3KB 565B)
|
|
268
|
+
🐍 demo_network.py (3KB 879B)
|
|
269
|
+
🐍 demo_observer.py (4KB 270B)
|
|
270
|
+
🐍 demo_redis.py (9KB 144B)
|
|
271
|
+
🐍 demo_stages.py (4KB 336B)
|
|
272
|
+
🐍 demo_structure.py (12KB 24B)
|
|
273
|
+
🐍 demo_utils.py (6KB 148B)
|
|
274
|
+
📁 dist (147KB 48B)
|
|
275
|
+
❓ .gitignore (1B)
|
|
276
|
+
❓ celestialflow-3.2.7-py3-none-any.whl (79KB 235B)
|
|
277
|
+
📦 celestialflow-3.2.7.tar.gz (67KB 836B)
|
|
278
|
+
📁 docs (1MB 722KB 21B)
|
|
279
|
+
📁 en[已折叠] (568KB 991B)
|
|
280
|
+
📁 ja[已折叠] (642KB 383B)
|
|
281
|
+
📁 zh-CN[已折叠] (534KB 695B)
|
|
282
|
+
📁 experiments (2KB 1015B)
|
|
283
|
+
🐍 experiment_networkx.py (1KB 878B)
|
|
284
|
+
🐍 experiment_tqdm.py (1KB 137B)
|
|
285
|
+
📁 img (5MB 871KB 242B)
|
|
286
|
+
📷 file_structure.svg (4MB 918KB 1000B)
|
|
287
|
+
📷 logo(old).png (836KB 542B)
|
|
288
|
+
📷 logo.png (122KB 747B)
|
|
289
|
+
📷 scc_condensation.svg (17KB 1B)
|
|
290
|
+
📁 src (1MB 858KB 89B)
|
|
291
|
+
📁 celestialflow[已折叠] (1MB 839KB 629B)
|
|
292
|
+
📁 celestialflow.egg-info[已折叠] (18KB 484B)
|
|
293
|
+
📁 tests (4MB 167KB 552B)
|
|
294
|
+
📁 benchmark[已折叠] (38KB 197B)
|
|
295
|
+
📁 funnel[已折叠] (96KB 363B)
|
|
296
|
+
📁 graph[已折叠] (721KB 564B)
|
|
297
|
+
📁 observability[已折叠] (189KB 236B)
|
|
298
|
+
📁 persistence[已折叠] (388KB 426B)
|
|
299
|
+
📁 runtime[已折叠] (1MB 295KB 529B)
|
|
300
|
+
📁 stage[已折叠] (382KB 155B)
|
|
301
|
+
📁 utils[已折叠] (639KB 527B)
|
|
302
|
+
📁 [1项排除的目录] (487KB 589B)
|
|
303
|
+
🐍 conftest.py (1KB 38B)
|
|
304
|
+
🐍 __init__.py (0B)
|
|
305
|
+
📁 [12项排除的目录] (573MB 420KB 160B)
|
|
306
|
+
❓ .env (468B)
|
|
307
|
+
❓ .gitignore (1KB 314B)
|
|
308
|
+
📝 AGENTS.md (1KB 173B)
|
|
309
|
+
❓ LICENSE (1KB 65B)
|
|
310
|
+
❓ Makefile (155B)
|
|
311
|
+
⚙️ pyproject.toml (2KB 668B)
|
|
312
|
+
📝 README.md (19KB 24B)
|
|
313
|
+
🔒 uv.lock (97KB 510B)
|
|
314
|
+
```
|
|
230
315
|
<p align="center">
|
|
231
|
-
<
|
|
232
|
-
<br/>
|
|
233
|
-
<em>celestial-flow 3.2.6</em>
|
|
316
|
+
<em>celestial-flow 3.2.8</em>
|
|
234
317
|
</p>
|
|
235
318
|
|
|
236
319
|
(该视图由我的另一个项目[CelestialVault](https://github.com/Mr-xiaotian/CelestialVault)中inst_file.FileTree.print_tree()生成。转换为图片则借助[Carbon](https://carbon.now.sh)。)
|
|
237
320
|
|
|
238
321
|
## 版本日志(Version Log)
|
|
239
|
-
- 3.2.
|
|
322
|
+
- 3.2.8
|
|
240
323
|
- feat:
|
|
241
|
-
-
|
|
242
|
-
-
|
|
324
|
+
- [IMPORTANT] 添加 `TaskGraph.run_async`, 现在可以直接进行图级别的异步
|
|
325
|
+
- 根据 `bench_graph_mode.py` 最新测试, 在I/O密集型任务中, `serial`+`async`(6.05s),比 `serial`+`serial`(69.04s)快 **11.4x**
|
|
326
|
+
- 在 `core_structure` 中添加报错校验
|
|
327
|
+
- 不再允许设置 `execution_mode=async` 时调用 `TashExecutor.start`(如今的`TashExecutor.run`), 只能调用 `TashExecutor.run_async`
|
|
328
|
+
- 另外调用 `TashExecutor.run_async` 时也会进行模式检查, 不是 `execution_mode=async` 就会抛出异常
|
|
329
|
+
- 在 `BaseObserver` 中添加 `observer_error`, 用于处理 `BaseObserver.on_*` 函数的报错
|
|
330
|
+
- 在 `TaskGraph._finish_start_graph` `TaskExecutor._finish_start` 中对所有执行步骤进行 `try-except`, 以使收尾步骤尽量全部完成
|
|
331
|
+
- 添加 `core_scope`, 用于独立控制 `funnel` 的生命周期, 并用于 `TaskGraph.run/run_async` `TaskExecutor.run/run_async`
|
|
243
332
|
- refactor:
|
|
244
|
-
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
|
|
248
|
-
-
|
|
249
|
-
|
|
333
|
+
- [IMPORTANT] 原 `TaskGraph.start_graph/start_graph_async` `TaskExecutor.start/start_async` 已被重命名为 `TaskGraph.run/run_async` `TaskExecutor.run/run_async`
|
|
334
|
+
- 破坏性更新
|
|
335
|
+
- 其底层缘由是为了合并 `TaskStage.start_stage/start_stage_async` 与 `TaskExecutor.start/start_async` 而做出的一系列重构之一
|
|
336
|
+
- 现在的 `TaskGraph.start/start_async` `TaskExecutor.start/start_async` 不再接受任务, 而是专注于处理现有任务列表中的任务
|
|
337
|
+
- [IMPORTANT] 将funnel从 `TaskGraph` `TaskExecutor` 中的显性调用与显性传递, 改为所有使用端均从独立文件中import
|
|
338
|
+
- 一来是因为原先的传递链太丑了, 二来是为了简化 `TaskExecutor.start/start_async` 逻辑, 为其与 `TaskStage.start_stage/start_stage_async` 的合并做准备
|
|
339
|
+
- 移动部分文件以解决部分模块级的循环引用问题
|
|
340
|
+
- 原先并非文件级循环引用, 在使用上并无问题
|
|
341
|
+
- 修改 `TaskDispatch.dispatch_thread` 中处理已完成future的逻辑, 避免cpu浪费
|
|
342
|
+
- 删除 `util_errors` 中一些不必要的错误类
|
|
343
|
+
- 将 `TaskGraph._finalize_stages` 进行拆分, 删除不必要的机制, 将剩余机制移至其他方法
|
|
344
|
+
- 修改 `TaskGraph.set_reporter` 的逻辑, 使其与 `set_ctree` 保持一致
|
|
345
|
+
- 将 `TaskOutQueue` 中的 `queue_list|target_name|_name_to_idx` 改为 `_queues`, 并删除 `put_channel`
|
|
346
|
+
- 我有些困惑为什么最初我没有这么做
|
|
347
|
+
- 在 `TaskExecutor` 中合并 `_get_task_repr` and `_get_result_repr`
|
|
348
|
+
- 这两个方法原先差异巨大, 但后来经过多次其他部分的重构, 现在逻辑已经一致
|
|
349
|
+
- 将 `TaskStage` 中的 `_status` 交给 `TaskMetrics` 维护
|
|
350
|
+
- 依旧是为了合并 `TaskExecutor.start/start_async` 与 `TaskStage.start_stage/start_stage_async`
|
|
351
|
+
- 移除 `TaskGraph` 中的 `put_stage_queue`
|
|
352
|
+
- 任务输入完全使用 `TaskExecutor` 中的 `put_task` `put_signal` 方法
|
|
353
|
+
- fix:
|
|
354
|
+
- 在 `TaskDispatch.worker/worker_async` 中添加错误捕捉, 捕捉为 `CRITICAL` 级错误
|
|
355
|
+
- 避免 `worker` 级出错导致计数错误, 永远无法退出
|
|
356
|
+
- 在 `spout` 与 `reporter` 的 `stop` 操作中添加对于线程没有成功 `join` 的报错
|
|
357
|
+
- 修复 `TaskGraph.restore_db` 与 `TaskExecutor.restore_db` 中开启 `filter_by_error_type=Ture` 时跳过 `status=pending` 任务记录的问题
|
|
358
|
+
- 修复 `TaskGraph` 中部分 `get_*` 方法依赖 `_build_analysis` 的产物, 但 `_build_analysis` 未执行导致的问题
|
|
359
|
+
- 修复 `TaskMetrics` 中 `get_counts` 与 `is_tasks_finished` 中可能导致死锁的问题
|
|
360
|
+
- 修复 `TaskReporter` 中 `stop` 里进行的最后一次 `_refresh_all` 没有错误捕捉, 导致后续收尾未完成的问题
|
|
250
361
|
|
|
251
362
|
更多过往日志可看:
|
|
252
363
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "celestialflow"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.8"
|
|
8
8
|
description = "A flexible GRAPH-based task orchestration framework."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -13,7 +13,6 @@ keywords = ["workflow", "task", "graph", "async", "CelestialFlow"]
|
|
|
13
13
|
requires-python = ">=3.12"
|
|
14
14
|
|
|
15
15
|
dependencies = [
|
|
16
|
-
"tqdm",
|
|
17
16
|
"requests",
|
|
18
17
|
]
|
|
19
18
|
|
|
@@ -28,6 +27,9 @@ classifiers = [
|
|
|
28
27
|
"Homepage" = "https://github.com/Mr-xiaotian/CelestialFlow"
|
|
29
28
|
"Bug Tracker" = "https://github.com/Mr-xiaotian/CelestialFlow/issues"
|
|
30
29
|
|
|
30
|
+
[tool.uv]
|
|
31
|
+
index-url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
32
|
+
|
|
31
33
|
[tool.pytest.ini_options]
|
|
32
34
|
addopts = "-s"
|
|
33
35
|
log_cli = true
|
|
@@ -54,12 +56,17 @@ dev = [
|
|
|
54
56
|
"pyright>=1.1.409",
|
|
55
57
|
"redis",
|
|
56
58
|
"celestialtree>=0.1.3",
|
|
59
|
+
"tqdm",
|
|
57
60
|
]
|
|
58
61
|
|
|
62
|
+
[tool.celestialflow]
|
|
63
|
+
log_level = "INFO"
|
|
64
|
+
|
|
59
65
|
[tool.pyright]
|
|
60
66
|
include = ["src"]
|
|
61
67
|
ignore = ["tests", "**/tests/**", "typings", "demo", "bench", "experiments", "examples", "temp"]
|
|
62
68
|
|
|
69
|
+
pythonVersion = "3.14"
|
|
63
70
|
typeCheckingMode = "strict"
|
|
64
71
|
reportUnusedFunction = "none"
|
|
65
72
|
reportImplicitOverride = "none"
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
提供任务图构建、执行调度、实时监控与持久化等核心能力。
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from .benchmark.util_benchmark import benchmark_executor, benchmark_graph
|
|
7
8
|
from .funnel import BaseInlet, BaseSpout
|
|
8
9
|
from .graph import (
|
|
9
10
|
TaskChain,
|
|
@@ -14,11 +15,12 @@ from .graph import (
|
|
|
14
15
|
TaskLoop,
|
|
15
16
|
TaskWheel,
|
|
16
17
|
)
|
|
17
|
-
from .observability import BaseObserver,
|
|
18
|
+
from .observability import BaseObserver, TaskReporter
|
|
18
19
|
from .persistence.util_sqlite import (
|
|
19
20
|
load_records,
|
|
20
21
|
load_tasks_grouped_by_stage,
|
|
21
22
|
)
|
|
23
|
+
from .runtime.util_format import format_table
|
|
22
24
|
from .runtime.util_hash import make_hashable
|
|
23
25
|
from .runtime.util_types import TerminationSignal
|
|
24
26
|
from .stage import (
|
|
@@ -27,8 +29,6 @@ from .stage import (
|
|
|
27
29
|
TaskSplitter,
|
|
28
30
|
TaskStage,
|
|
29
31
|
)
|
|
30
|
-
from .utils.util_benchmark import benchmark_executor, benchmark_graph
|
|
31
|
-
from .utils.util_format import format_table
|
|
32
32
|
|
|
33
33
|
__all__ = [
|
|
34
34
|
"BaseInlet",
|
|
@@ -41,7 +41,7 @@ __all__ = [
|
|
|
41
41
|
"TaskGraph",
|
|
42
42
|
"TaskGrid",
|
|
43
43
|
"TaskLoop",
|
|
44
|
-
"
|
|
44
|
+
"TaskReporter",
|
|
45
45
|
"TaskRouter",
|
|
46
46
|
"TaskSplitter",
|
|
47
47
|
"TaskStage",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# benchmark/util_benchmark.py
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import time
|
|
@@ -6,9 +6,9 @@ from collections.abc import Iterable, Mapping
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from ..graph import TaskGraph
|
|
9
|
+
from ..runtime.util_format import format_table
|
|
9
10
|
from ..stage import TaskExecutor
|
|
10
11
|
from .util_clone import clone_executor, clone_graph
|
|
11
|
-
from .util_format import format_table
|
|
12
12
|
|
|
13
13
|
type AnyTaskExecutor = TaskExecutor[Any, Any]
|
|
14
14
|
|
|
@@ -41,7 +41,8 @@ async def benchmark_executor(
|
|
|
41
41
|
cloned_executor.set_execution_mode(mode)
|
|
42
42
|
|
|
43
43
|
start: float = time.perf_counter()
|
|
44
|
-
cloned_executor.
|
|
44
|
+
cloned_executor.put_tasks(task_list)
|
|
45
|
+
cloned_executor.start()
|
|
45
46
|
use_time.append([time.perf_counter() - start])
|
|
46
47
|
results.append(cloned_executor.get_success_pairs())
|
|
47
48
|
|
|
@@ -50,7 +51,8 @@ async def benchmark_executor(
|
|
|
50
51
|
cloned_executor.set_execution_mode(mode)
|
|
51
52
|
|
|
52
53
|
start = time.perf_counter()
|
|
53
|
-
|
|
54
|
+
cloned_executor.put_tasks(task_list)
|
|
55
|
+
await cloned_executor.start_async()
|
|
54
56
|
use_time.append([time.perf_counter() - start])
|
|
55
57
|
results.append(cloned_executor.get_success_pairs())
|
|
56
58
|
|
|
@@ -68,7 +70,7 @@ async def benchmark_executor(
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
|
|
71
|
-
def benchmark_graph(
|
|
73
|
+
async def benchmark_graph(
|
|
72
74
|
sync_graph: TaskGraph,
|
|
73
75
|
async_graph: TaskGraph,
|
|
74
76
|
init_tasks_dict: Mapping[str, Iterable[Any]],
|
|
@@ -88,38 +90,39 @@ def benchmark_graph(
|
|
|
88
90
|
:return: 包含测试结果的字典
|
|
89
91
|
"""
|
|
90
92
|
stage_modes = stage_modes or ["serial", "thread"]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
execution_sync_modes = execution_sync_modes or ["serial", "thread"]
|
|
94
|
+
execution_async_modes = execution_async_modes or ["async"]
|
|
93
95
|
|
|
94
96
|
base_tasks: dict[str, list[Any]] = {
|
|
95
97
|
stage_name: list(tasks) for stage_name, tasks in init_tasks_dict.items()
|
|
96
98
|
}
|
|
97
|
-
execution_modes: list[str] =
|
|
99
|
+
execution_modes: list[str] = execution_sync_modes + execution_async_modes
|
|
98
100
|
|
|
99
101
|
test_table_list: list[list[float]] = []
|
|
100
102
|
|
|
101
103
|
for stage_mode in stage_modes:
|
|
102
104
|
time_list: list[float] = []
|
|
103
|
-
|
|
105
|
+
|
|
106
|
+
for execution_mode in execution_sync_modes:
|
|
104
107
|
cloned_graph: TaskGraph = clone_graph(sync_graph)
|
|
105
108
|
cloned_graph.set_graph_mode(stage_mode, execution_mode)
|
|
106
109
|
|
|
107
|
-
|
|
110
|
+
sync_run_tasks: dict[str, Iterable[Any]] = {
|
|
108
111
|
stage_name: list(tasks) for stage_name, tasks in base_tasks.items()
|
|
109
112
|
}
|
|
110
113
|
start_time: float = time.perf_counter()
|
|
111
|
-
cloned_graph.
|
|
114
|
+
cloned_graph.run(sync_run_tasks)
|
|
112
115
|
time_list.append(time.perf_counter() - start_time)
|
|
113
116
|
|
|
114
|
-
for execution_mode in
|
|
117
|
+
for execution_mode in execution_async_modes:
|
|
115
118
|
cloned_graph = clone_graph(async_graph)
|
|
116
119
|
cloned_graph.set_graph_mode(stage_mode, execution_mode)
|
|
117
120
|
|
|
118
|
-
|
|
121
|
+
async_run_tasks: dict[str, Iterable[Any]] = {
|
|
119
122
|
stage_name: list(tasks) for stage_name, tasks in base_tasks.items()
|
|
120
123
|
}
|
|
121
124
|
start_time = time.perf_counter()
|
|
122
|
-
cloned_graph.
|
|
125
|
+
await cloned_graph.run_async(async_run_tasks)
|
|
123
126
|
time_list.append(time.perf_counter() - start_time)
|
|
124
127
|
|
|
125
128
|
test_table_list.append(time_list)
|
|
@@ -128,12 +131,12 @@ def benchmark_graph(
|
|
|
128
131
|
test_table_list,
|
|
129
132
|
stage_modes,
|
|
130
133
|
execution_modes,
|
|
131
|
-
|
|
134
|
+
"stage/execution",
|
|
132
135
|
)
|
|
133
136
|
print(f"Time table:\n{time_table}")
|
|
134
137
|
return {
|
|
135
138
|
"table": time_table,
|
|
136
139
|
"stage_modes": stage_modes,
|
|
137
|
-
"sync_modes":
|
|
138
|
-
"async_modes":
|
|
140
|
+
"sync_modes": execution_sync_modes,
|
|
141
|
+
"async_modes": execution_async_modes if async_graph else [],
|
|
139
142
|
}
|