Allow distributed worker registration preflight
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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"}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user