Allow distributed worker registration preflight

This commit is contained in:
Dominik Bargiel
2026-09-10 11:22:48 +02:00
parent 2fda2a8ac0
commit 58a0e0b4e8
5 changed files with 44 additions and 2 deletions
+1
View File
@@ -103,6 +103,7 @@ Every variation is checked on the selected Deadline worker after input staging,
- removes fixed `ImpactSwitch`, `LatentSwitch`, and `SEGSSwitch` pass-through nodes by reconnecting consumers to the selected upstream input;
- sends the complete submitted `extra_pnginfo.workflow` to metadata-aware nodes;
- verifies that ComfyUI history reports the expected output nodes and that every reported file exists;
- permits the exact `DeadlineWorkerRegistration` control prompt when both worker and distributed modes are enabled; this prompt completes through history and intentionally produces no file;
- turns prompt-validation errors, execution exceptions, early process exits, HTTP timeouts, missing output, and Deadline task timeouts into task failures.
Dynamic/connected switch selections are not rewritten. Browser-driven chooser/picker/preview-bridge nodes (`FL_ImagePicker`, `easy imageChooser`, `ImageChooser`, `PreviewChooser`, `PreviewBridge`, and `ImpactPreviewBridge`) are rejected because a farm render has no user to answer them. Other nodes using `PROMPT`, `DYNPROMPT`, `UNIQUE_ID`, or `EXTRA_PNGINFO` remain supported when the worker has the node and the submission includes full workflow metadata. Unknown third-party interactive nodes cannot be identified from `/object_info` alone; add their exact `class_type` to `KNOWN_UI_DEPENDENT_NODE_TYPES` after confirming that they wait for frontend state.
+14 -1
View File
@@ -1606,11 +1606,24 @@ sys.exit(0 if result.get('success') else 1)
if definition.get("output_node") and re.search(r"save|preview|video|image|audio", class_type, re.I):
expected_outputs.append(str(node_id))
if not expected_outputs:
worker_mode, distributed_mode, _ = get_distributed_config_for_plugin(self)
is_registration_prompt = (
worker_mode
and distributed_mode
and len(prompt) == 1
and next(iter(prompt.values())).get("class_type") == "DeadlineWorkerRegistration"
)
if not expected_outputs and not is_registration_prompt:
raise ComfyUIError(
f"Headless preflight found no file-producing output node on worker {self.GetSlaveName()}."
)
self.current_prompt_payload = prompt
if is_registration_prompt:
self.LogInfo(
f"Headless preflight accepted the distributed worker registration prompt on "
f"{self.GetSlaveName()}; registration completes through ComfyUI history and does not write a file."
)
return prompt, expected_outputs
self.LogInfo(
f"Headless preflight passed for {len(prompt)} node(s) on worker {self.GetSlaveName()}; "
f"expected output nodes: {', '.join(expected_outputs)}"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "comfyui-deadline-plugin"
version = "1.0.8"
version = "1.0.9"
description = "ComfyUI plugin for submitting workflows to Thinkbox Deadline for distributed rendering"
readme = "README.md"
license = {text = "MIT"}
+27
View File
@@ -33,6 +33,10 @@ def object_info():
},
"FL_ImagePicker": {"input": {"required": {}, "hidden": {"unique_id": "UNIQUE_ID"}}, "output_node": False},
"Explode": {"input": {"required": {}, "hidden": {}}, "output_node": False},
"DeadlineWorkerRegistration": {
"input": {"required": {}, "optional": {}, "hidden": {}},
"output_node": True,
},
}
@@ -93,6 +97,29 @@ class HeadlessPreflightTests(unittest.TestCase):
"2": {"class_type": "SaveImage", "inputs": {"images": ["7", 0]}},
}, WORKFLOW)
def test_distributed_worker_registration_does_not_require_a_file_output(self):
with tempfile.TemporaryDirectory() as temp:
plugin = self.make_plugin(Path(temp))
flags = {"WorkerMode": True, "DistributedMode": True}
plugin.GetBooleanPluginInfoEntryWithDefault = lambda key, default: flags.get(key, default)
prompt = {
"1": {"class_type": "DeadlineWorkerRegistration", "inputs": {}},
}
result, expected = plugin._preflight_worker_payload(prompt)
self.assertEqual(result, prompt)
self.assertEqual(expected, [])
def test_distributed_non_registration_prompt_still_requires_file_output(self):
with tempfile.TemporaryDirectory() as temp:
plugin = self.make_plugin(Path(temp))
flags = {"WorkerMode": True, "DistributedMode": True}
plugin.GetBooleanPluginInfoEntryWithDefault = lambda key, default: flags.get(key, default)
with self.assertRaisesRegex(ComfyUIError, "no file-producing output node"):
plugin._preflight_worker_payload({
"1": {"class_type": "DeadlineWorkerRegistration", "inputs": {}},
"3": {"class_type": "Explode", "inputs": {}},
})
def test_invalid_staged_path_is_rejected_before_queue(self):
with tempfile.TemporaryDirectory() as temp:
plugin = self.make_plugin(Path(temp))
+1
View File
@@ -103,6 +103,7 @@ def new_plugin(**attrs):
plugin.standard_workflow = None
plugin.GetSlaveName = lambda: "M21"
plugin.GetPluginInfoEntryWithDefault = lambda key, default: default
plugin.GetBooleanPluginInfoEntryWithDefault = lambda key, default: default
plugin.LogInfo = lambda message: plugin.logs.append(("info", message))
plugin.LogWarning = lambda message: plugin.logs.append(("warning", message))
for key, value in attrs.items():