mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-03-08 02:37:42 +08:00
feat(isolation-bootstrap): cli flag + host/child startup fencing
This commit is contained in:
parent
1f1ec377ce
commit
3cfd5e3311
@ -179,6 +179,8 @@ parser.add_argument("--disable-api-nodes", action="store_true", help="Disable lo
|
|||||||
|
|
||||||
parser.add_argument("--multi-user", action="store_true", help="Enables per-user storage.")
|
parser.add_argument("--multi-user", action="store_true", help="Enables per-user storage.")
|
||||||
|
|
||||||
|
parser.add_argument("--use-process-isolation", action="store_true", help="Enable process isolation for custom nodes with pyisolate.yaml manifests.")
|
||||||
|
|
||||||
parser.add_argument("--verbose", default='INFO', const='DEBUG', nargs="?", choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help='Set the logging level')
|
parser.add_argument("--verbose", default='INFO', const='DEBUG', nargs="?", choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help='Set the logging level')
|
||||||
parser.add_argument("--log-stdout", action="store_true", help="Send normal process output to stdout instead of stderr (default).")
|
parser.add_argument("--log-stdout", action="store_true", help="Send normal process output to stdout instead of stderr (default).")
|
||||||
|
|
||||||
|
|||||||
96
main.py
96
main.py
@ -1,7 +1,21 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
IS_PYISOLATE_CHILD = os.environ.get("PYISOLATE_CHILD") == "1"
|
||||||
|
|
||||||
|
if __name__ == "__main__" and IS_PYISOLATE_CHILD:
|
||||||
|
del os.environ["PYISOLATE_CHILD"]
|
||||||
|
IS_PYISOLATE_CHILD = False
|
||||||
|
|
||||||
|
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
if CURRENT_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, CURRENT_DIR)
|
||||||
|
|
||||||
|
IS_PRIMARY_PROCESS = (not IS_PYISOLATE_CHILD) and __name__ == "__main__"
|
||||||
|
|
||||||
import comfy.options
|
import comfy.options
|
||||||
comfy.options.enable_args_parsing()
|
comfy.options.enable_args_parsing()
|
||||||
|
|
||||||
import os
|
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import folder_paths
|
import folder_paths
|
||||||
import time
|
import time
|
||||||
@ -9,24 +23,38 @@ from comfy.cli_args import args, enables_dynamic_vram
|
|||||||
from app.logger import setup_logger
|
from app.logger import setup_logger
|
||||||
from app.assets.scanner import seed_assets
|
from app.assets.scanner import seed_assets
|
||||||
import itertools
|
import itertools
|
||||||
import utils.extra_config
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
from comfy_execution.progress import get_progress_state
|
|
||||||
from comfy_execution.utils import get_executing_context
|
|
||||||
from comfy_api import feature_flags
|
|
||||||
|
|
||||||
import comfy_aimdo.control
|
if '--use-process-isolation' in sys.argv:
|
||||||
|
from comfy.isolation import initialize_proxies
|
||||||
|
initialize_proxies()
|
||||||
|
|
||||||
if enables_dynamic_vram():
|
# Explicitly register the ComfyUI adapter for pyisolate (v1.0 architecture)
|
||||||
comfy_aimdo.control.init()
|
try:
|
||||||
|
import pyisolate
|
||||||
|
from comfy.isolation.adapter import ComfyUIAdapter
|
||||||
|
pyisolate.register_adapter(ComfyUIAdapter())
|
||||||
|
logging.info("PyIsolate adapter registered: comfyui")
|
||||||
|
except ImportError:
|
||||||
|
logging.warning("PyIsolate not installed or version too old for explicit registration")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to register PyIsolate adapter: {e}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if not IS_PYISOLATE_CHILD:
|
||||||
#NOTE: These do not do anything on core ComfyUI, they are for custom nodes.
|
if 'PYTORCH_CUDA_ALLOC_CONF' not in os.environ:
|
||||||
|
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'backend:native'
|
||||||
|
|
||||||
|
if not IS_PYISOLATE_CHILD:
|
||||||
|
from comfy_execution.progress import get_progress_state
|
||||||
|
from comfy_execution.utils import get_executing_context
|
||||||
|
from comfy_api import feature_flags
|
||||||
|
|
||||||
|
if IS_PRIMARY_PROCESS:
|
||||||
os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1'
|
os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1'
|
||||||
os.environ['DO_NOT_TRACK'] = '1'
|
os.environ['DO_NOT_TRACK'] = '1'
|
||||||
|
|
||||||
setup_logger(log_level=args.verbose, use_stdout=args.log_stdout)
|
if not IS_PYISOLATE_CHILD:
|
||||||
|
setup_logger(log_level=args.verbose, use_stdout=args.log_stdout)
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
os.environ['MIMALLOC_PURGE_DELAY'] = '0'
|
os.environ['MIMALLOC_PURGE_DELAY'] = '0'
|
||||||
@ -78,14 +106,15 @@ if args.enable_manager:
|
|||||||
|
|
||||||
|
|
||||||
def apply_custom_paths():
|
def apply_custom_paths():
|
||||||
|
from utils import extra_config # Deferred import - spawn re-runs main.py
|
||||||
# extra model paths
|
# extra model paths
|
||||||
extra_model_paths_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "extra_model_paths.yaml")
|
extra_model_paths_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "extra_model_paths.yaml")
|
||||||
if os.path.isfile(extra_model_paths_config_path):
|
if os.path.isfile(extra_model_paths_config_path):
|
||||||
utils.extra_config.load_extra_path_config(extra_model_paths_config_path)
|
extra_config.load_extra_path_config(extra_model_paths_config_path)
|
||||||
|
|
||||||
if args.extra_model_paths_config:
|
if args.extra_model_paths_config:
|
||||||
for config_path in itertools.chain(*args.extra_model_paths_config):
|
for config_path in itertools.chain(*args.extra_model_paths_config):
|
||||||
utils.extra_config.load_extra_path_config(config_path)
|
extra_config.load_extra_path_config(config_path)
|
||||||
|
|
||||||
# --output-directory, --input-directory, --user-directory
|
# --output-directory, --input-directory, --user-directory
|
||||||
if args.output_directory:
|
if args.output_directory:
|
||||||
@ -158,14 +187,16 @@ def execute_prestartup_script():
|
|||||||
else:
|
else:
|
||||||
import_message = " (PRESTARTUP FAILED)"
|
import_message = " (PRESTARTUP FAILED)"
|
||||||
logging.info("{:6.1f} seconds{}: {}".format(n[0], import_message, n[1]))
|
logging.info("{:6.1f} seconds{}: {}".format(n[0], import_message, n[1]))
|
||||||
logging.info("")
|
logging.info("")
|
||||||
|
|
||||||
apply_custom_paths()
|
if not IS_PYISOLATE_CHILD:
|
||||||
|
apply_custom_paths()
|
||||||
|
|
||||||
if args.enable_manager:
|
if args.enable_manager and not IS_PYISOLATE_CHILD:
|
||||||
comfyui_manager.prestartup()
|
comfyui_manager.prestartup()
|
||||||
|
|
||||||
execute_prestartup_script()
|
if not IS_PYISOLATE_CHILD:
|
||||||
|
execute_prestartup_script()
|
||||||
|
|
||||||
|
|
||||||
# Main code
|
# Main code
|
||||||
@ -177,17 +208,22 @@ import gc
|
|||||||
if 'torch' in sys.modules:
|
if 'torch' in sys.modules:
|
||||||
logging.warning("WARNING: Potential Error in code: Torch already imported, torch should never be imported before this point.")
|
logging.warning("WARNING: Potential Error in code: Torch already imported, torch should never be imported before this point.")
|
||||||
|
|
||||||
|
import comfy_aimdo.control
|
||||||
|
|
||||||
|
if enables_dynamic_vram():
|
||||||
|
comfy_aimdo.control.init()
|
||||||
|
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
|
|
||||||
import execution
|
if not IS_PYISOLATE_CHILD:
|
||||||
import server
|
import execution
|
||||||
from protocol import BinaryEventTypes
|
import server
|
||||||
import nodes
|
from protocol import BinaryEventTypes
|
||||||
import comfy.model_management
|
import nodes
|
||||||
import comfyui_version
|
import comfy.model_management
|
||||||
import app.logger
|
import comfyui_version
|
||||||
import hook_breaker_ac10a0
|
import app.logger
|
||||||
|
import hook_breaker_ac10a0
|
||||||
|
|
||||||
import comfy.memory_management
|
import comfy.memory_management
|
||||||
import comfy.model_patcher
|
import comfy.model_patcher
|
||||||
@ -384,6 +420,10 @@ def start_comfyui(asyncio_loop=None):
|
|||||||
asyncio.set_event_loop(asyncio_loop)
|
asyncio.set_event_loop(asyncio_loop)
|
||||||
prompt_server = server.PromptServer(asyncio_loop)
|
prompt_server = server.PromptServer(asyncio_loop)
|
||||||
|
|
||||||
|
if args.use_process_isolation:
|
||||||
|
from comfy.isolation import start_isolation_loading_early
|
||||||
|
start_isolation_loading_early(asyncio_loop)
|
||||||
|
|
||||||
if args.enable_manager and not args.disable_manager_ui:
|
if args.enable_manager and not args.disable_manager_ui:
|
||||||
comfyui_manager.start()
|
comfyui_manager.start()
|
||||||
|
|
||||||
@ -428,7 +468,9 @@ def start_comfyui(asyncio_loop=None):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Running directly, just start ComfyUI.
|
# Running directly, just start ComfyUI.
|
||||||
logging.info("Python version: {}".format(sys.version))
|
logging.info("Python version: {}".format(sys.version))
|
||||||
logging.info("ComfyUI version: {}".format(comfyui_version.__version__))
|
if not IS_PYISOLATE_CHILD:
|
||||||
|
import comfyui_version
|
||||||
|
logging.info("ComfyUI version: {}".format(comfyui_version.__version__))
|
||||||
|
|
||||||
if sys.version_info.major == 3 and sys.version_info.minor < 10:
|
if sys.version_info.major == 3 and sys.version_info.minor < 10:
|
||||||
logging.warning("WARNING: You are using a python version older than 3.10, please upgrade to a newer one. 3.12 and above is recommended.")
|
logging.warning("WARNING: You are using a python version older than 3.10, please upgrade to a newer one. 3.12 and above is recommended.")
|
||||||
|
|||||||
43
nodes.py
43
nodes.py
@ -1925,6 +1925,7 @@ class ImageInvert:
|
|||||||
|
|
||||||
class ImageBatch:
|
class ImageBatch:
|
||||||
SEARCH_ALIASES = ["combine images", "merge images", "stack images"]
|
SEARCH_ALIASES = ["combine images", "merge images", "stack images"]
|
||||||
|
ESSENTIALS_CATEGORY = "Image Tools"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@ -2306,6 +2307,27 @@ async def init_external_custom_nodes():
|
|||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
|
whitelist = set()
|
||||||
|
isolated_module_paths = set()
|
||||||
|
if args.use_process_isolation:
|
||||||
|
from pathlib import Path
|
||||||
|
from comfy.isolation import await_isolation_loading, get_claimed_paths
|
||||||
|
from comfy.isolation.host_policy import load_host_policy
|
||||||
|
|
||||||
|
# Load Global Host Policy
|
||||||
|
host_policy = load_host_policy(Path(folder_paths.base_path))
|
||||||
|
whitelist_dict = host_policy.get("whitelist", {})
|
||||||
|
# Normalize whitelist keys to lowercase for case-insensitive matching
|
||||||
|
# (matches ComfyUI-Manager's normalization: project.name.strip().lower())
|
||||||
|
whitelist = set(k.strip().lower() for k in whitelist_dict.keys())
|
||||||
|
logging.info(f"][ Loaded Whitelist: {len(whitelist)} nodes allowed.")
|
||||||
|
|
||||||
|
isolated_specs = await await_isolation_loading()
|
||||||
|
for spec in isolated_specs:
|
||||||
|
NODE_CLASS_MAPPINGS.setdefault(spec.node_name, spec.stub_class)
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS.setdefault(spec.node_name, spec.display_name)
|
||||||
|
isolated_module_paths = get_claimed_paths()
|
||||||
|
|
||||||
base_node_names = set(NODE_CLASS_MAPPINGS.keys())
|
base_node_names = set(NODE_CLASS_MAPPINGS.keys())
|
||||||
node_paths = folder_paths.get_folder_paths("custom_nodes")
|
node_paths = folder_paths.get_folder_paths("custom_nodes")
|
||||||
node_import_times = []
|
node_import_times = []
|
||||||
@ -2329,6 +2351,16 @@ async def init_external_custom_nodes():
|
|||||||
logging.info(f"Blocked by policy: {module_path}")
|
logging.info(f"Blocked by policy: {module_path}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if args.use_process_isolation:
|
||||||
|
if Path(module_path).resolve() in isolated_module_paths:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Tri-State Enforcement: If not Isolated (checked above), MUST be Whitelisted.
|
||||||
|
# Normalize to lowercase for case-insensitive matching (matches ComfyUI-Manager)
|
||||||
|
if possible_module.strip().lower() not in whitelist:
|
||||||
|
logging.warning(f"][ REJECTED: Node '{possible_module}' is blocked by security policy (not whitelisted/isolated).")
|
||||||
|
continue
|
||||||
|
|
||||||
time_before = time.perf_counter()
|
time_before = time.perf_counter()
|
||||||
success = await load_custom_node(module_path, base_node_names, module_parent="custom_nodes")
|
success = await load_custom_node(module_path, base_node_names, module_parent="custom_nodes")
|
||||||
node_import_times.append((time.perf_counter() - time_before, module_path, success))
|
node_import_times.append((time.perf_counter() - time_before, module_path, success))
|
||||||
@ -2343,6 +2375,14 @@ async def init_external_custom_nodes():
|
|||||||
logging.info("{:6.1f} seconds{}: {}".format(n[0], import_message, n[1]))
|
logging.info("{:6.1f} seconds{}: {}".format(n[0], import_message, n[1]))
|
||||||
logging.info("")
|
logging.info("")
|
||||||
|
|
||||||
|
if args.use_process_isolation:
|
||||||
|
from comfy.isolation import isolated_node_timings
|
||||||
|
if isolated_node_timings:
|
||||||
|
logging.info("\nImport times for isolated custom nodes:")
|
||||||
|
for timing, path, count in sorted(isolated_node_timings):
|
||||||
|
logging.info("{:6.1f} seconds: {} ({})".format(timing, path, count))
|
||||||
|
logging.info("")
|
||||||
|
|
||||||
async def init_builtin_extra_nodes():
|
async def init_builtin_extra_nodes():
|
||||||
"""
|
"""
|
||||||
Initializes the built-in extra nodes in ComfyUI.
|
Initializes the built-in extra nodes in ComfyUI.
|
||||||
@ -2435,7 +2475,6 @@ async def init_builtin_extra_nodes():
|
|||||||
"nodes_audio_encoder.py",
|
"nodes_audio_encoder.py",
|
||||||
"nodes_rope.py",
|
"nodes_rope.py",
|
||||||
"nodes_logic.py",
|
"nodes_logic.py",
|
||||||
"nodes_resolution.py",
|
|
||||||
"nodes_nop.py",
|
"nodes_nop.py",
|
||||||
"nodes_kandinsky5.py",
|
"nodes_kandinsky5.py",
|
||||||
"nodes_wanmove.py",
|
"nodes_wanmove.py",
|
||||||
@ -2443,12 +2482,10 @@ async def init_builtin_extra_nodes():
|
|||||||
"nodes_zimage.py",
|
"nodes_zimage.py",
|
||||||
"nodes_glsl.py",
|
"nodes_glsl.py",
|
||||||
"nodes_lora_debug.py",
|
"nodes_lora_debug.py",
|
||||||
"nodes_textgen.py",
|
|
||||||
"nodes_color.py",
|
"nodes_color.py",
|
||||||
"nodes_toolkit.py",
|
"nodes_toolkit.py",
|
||||||
"nodes_replacements.py",
|
"nodes_replacements.py",
|
||||||
"nodes_nag.py",
|
"nodes_nag.py",
|
||||||
"nodes_sdpose.py",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
import_failed = []
|
import_failed = []
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import sys
|
|||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import nodes
|
import nodes
|
||||||
import folder_paths
|
import folder_paths
|
||||||
import execution
|
import execution
|
||||||
@ -196,6 +195,8 @@ def create_block_external_middleware():
|
|||||||
class PromptServer():
|
class PromptServer():
|
||||||
def __init__(self, loop):
|
def __init__(self, loop):
|
||||||
PromptServer.instance = self
|
PromptServer.instance = self
|
||||||
|
if loop is None:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
mimetypes.add_type('application/javascript; charset=utf-8', '.js')
|
mimetypes.add_type('application/javascript; charset=utf-8', '.js')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user