Merge feat/runpod-serverless-workers (now including main) into feat/dynamic-image-list-queue

Resolve api/queue_orchestration.py, nodes/v3.py and
tests/helpers/v3_runtime_check.py to keep both the worker sink (#99)
and the image-list nodes. Stub nodes.image_list in the V3 collector
transfer test.
This commit is contained in:
Robert Wojciechowski
2026-10-13 22:17:30 +00:00
20 changed files with 2091 additions and 62 deletions
+24
View File
@@ -148,6 +148,30 @@ async def fetch_worker_path_separator(worker, trace_execution_id=None):
return None
async def fetch_worker_has_node(worker, node_class, trace_execution_id=None):
"""Best-effort check that a worker has ``node_class`` registered.
Uses ComfyUI's ``GET /object_info/{node_class}``, which returns ``{}`` for
unknown classes. Any failure counts as "not available" so callers fall
back to nodes every ComfyUI install has.
"""
url = build_worker_url(worker, f"/object_info/{node_class}")
session = await get_client_session()
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
if resp.status != 200:
return False
payload = await resp.json()
return isinstance(payload, dict) and node_class in payload
except Exception as exc:
message = f"Failed to probe node '{node_class}' on worker {worker.get('id')}: {exc}"
if trace_execution_id:
trace_debug(trace_execution_id, message)
else:
debug_log(f"[Distributed] {message}")
return False
async def _upload_media_to_worker(worker, filename, file_bytes, file_hash, mime_type, trace_execution_id=None):
"""Upload one media file to worker iff missing or hash-mismatched."""
session = await get_client_session()
+20 -3
View File
@@ -328,8 +328,19 @@ def _find_delegate_master_retained_upstream_nodes(prompt_obj, start_ids):
return connected
def prune_prompt_for_worker(prompt_obj):
"""Prune worker prompt to distributed nodes and their upstream dependencies."""
WORKER_SINK_CLASS = "DistributedWorkerSink"
def prune_prompt_for_worker(prompt_obj, use_worker_sink=False):
"""Prune worker prompt to distributed nodes and their upstream dependencies.
Removing downstream nodes can leave the worker prompt without an output
node. One is re-added after each such distributed node: the hidden
``DistributedWorkerSink`` when ``use_worker_sink`` is set (the worker must
have it registered), otherwise PreviewImage/PreviewAudio for workers
running older versions. The preview nodes write every frame to the
worker's temp directory, which the sink avoids.
"""
collector_ids = find_nodes_by_class(prompt_obj, "DistributedCollector")
upscale_ids = find_nodes_by_class(prompt_obj, "UltimateSDUpscaleDistributed")
distributed_ids = collector_ids + upscale_ids
@@ -364,7 +375,13 @@ def prune_prompt_for_worker(prompt_obj):
and len(inputs["audio"]) == 2
)
if image_connected:
if use_worker_sink and (image_connected or audio_connected):
pruned_prompt[next_id()] = {
"inputs": {"value": [dist_id, 0 if image_connected else 1]},
"class_type": WORKER_SINK_CLASS,
"_meta": {"title": "Distributed Worker Sink (auto-added)"},
}
elif image_connected:
preview_id = next_id()
pruned_prompt[preview_id] = {
"inputs": {"images": [dist_id, 0]},