mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-03-14 21:57:33 +08:00
Adds opt-in process isolation for custom nodes using pyisolate's bwrap sandbox and JSON-RPC bridge. Each isolated node pack runs in its own child process with zero-copy tensor transfer via shared memory. Core infrastructure: - CLI flag --use-process-isolation to enable isolation - Host/child startup fencing via PYISOLATE_CHILD env var - Manifest-driven node discovery and extension loading - JSON-RPC bridge between host and child processes - Shared memory forensics for leak detection Proxy layer: - ModelPatcher, CLIP, VAE, and ModelSampling proxies - Host service proxies (folder_paths, model_management, progress, etc.) - Proxy base with automatic method forwarding Execution integration: - Extension wrapper with V3 hidden param mapping - Runtime helpers for isolated node execution - Host policy for node isolation decisions - Fenced sampler device handling and model ejection parity Serializers for cross-process data transfer: - File3D (GLB), PLY (structured + gaussian), NPZ (streaming frames), VIDEO (VideoFromFile + VideoFromComponents) serializers - data_type flag in SerializerRegistry for type-aware dispatch - Isolated get_temp_directory() fence New core save nodes: - SavePLY and SaveNPZ with comfytype registrations (Ply, Npz) DynamicVRAM compatibility: - comfy-aimdo early init gated by isolation fence Tests: - Integration and policy tests for isolation lifecycle - Manifest loader, host policy, proxy, and adapter unit tests Depends on: pyisolate >= 0.9.2
99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, Optional
|
|
|
|
|
|
class AnyTypeProxy(str):
|
|
"""Replacement for custom AnyType objects used by some nodes."""
|
|
|
|
def __new__(cls, value: str = "*"):
|
|
return super().__new__(cls, value)
|
|
|
|
def __ne__(self, other): # type: ignore[override]
|
|
return False
|
|
|
|
|
|
class FlexibleOptionalInputProxy(dict):
|
|
"""Replacement for FlexibleOptionalInputType to allow dynamic inputs."""
|
|
|
|
def __init__(self, flex_type, data: Optional[Dict[str, object]] = None):
|
|
super().__init__()
|
|
self.type = flex_type
|
|
if data:
|
|
self.update(data)
|
|
|
|
def __getitem__(self, key): # type: ignore[override]
|
|
return (self.type,)
|
|
|
|
def __contains__(self, key): # type: ignore[override]
|
|
return True
|
|
|
|
|
|
class ByPassTypeTupleProxy(tuple):
|
|
"""Replacement for ByPassTypeTuple to mirror wildcard fallback behavior."""
|
|
|
|
def __new__(cls, values):
|
|
return super().__new__(cls, values)
|
|
|
|
def __getitem__(self, index): # type: ignore[override]
|
|
if index >= len(self):
|
|
return AnyTypeProxy("*")
|
|
return super().__getitem__(index)
|
|
|
|
|
|
def _restore_special_value(value: Any) -> Any:
|
|
if isinstance(value, dict):
|
|
if value.get("__pyisolate_any_type__"):
|
|
return AnyTypeProxy(value.get("value", "*"))
|
|
if value.get("__pyisolate_flexible_optional__"):
|
|
flex_type = _restore_special_value(value.get("type"))
|
|
data_raw = value.get("data")
|
|
data = (
|
|
{k: _restore_special_value(v) for k, v in data_raw.items()}
|
|
if isinstance(data_raw, dict)
|
|
else {}
|
|
)
|
|
return FlexibleOptionalInputProxy(flex_type, data)
|
|
if value.get("__pyisolate_tuple__") is not None:
|
|
return tuple(
|
|
_restore_special_value(v) for v in value["__pyisolate_tuple__"]
|
|
)
|
|
if value.get("__pyisolate_bypass_tuple__") is not None:
|
|
return ByPassTypeTupleProxy(
|
|
tuple(
|
|
_restore_special_value(v)
|
|
for v in value["__pyisolate_bypass_tuple__"]
|
|
)
|
|
)
|
|
return {k: _restore_special_value(v) for k, v in value.items()}
|
|
if isinstance(value, list):
|
|
return [_restore_special_value(v) for v in value]
|
|
return value
|
|
|
|
|
|
def restore_input_types(raw: Dict[str, object]) -> Dict[str, object]:
|
|
"""Restore serialized INPUT_TYPES payload back into ComfyUI-compatible objects."""
|
|
|
|
if not isinstance(raw, dict):
|
|
return raw # type: ignore[return-value]
|
|
|
|
restored: Dict[str, object] = {}
|
|
for section, entries in raw.items():
|
|
if isinstance(entries, dict) and entries.get("__pyisolate_flexible_optional__"):
|
|
restored[section] = _restore_special_value(entries)
|
|
elif isinstance(entries, dict):
|
|
restored[section] = {
|
|
k: _restore_special_value(v) for k, v in entries.items()
|
|
}
|
|
else:
|
|
restored[section] = _restore_special_value(entries)
|
|
return restored
|
|
|
|
|
|
__all__ = [
|
|
"AnyTypeProxy",
|
|
"FlexibleOptionalInputProxy",
|
|
"ByPassTypeTupleProxy",
|
|
"restore_input_types",
|
|
]
|