Fix R2 configuration v3 class cloning

This commit is contained in:
Ken Simpson
2026-08-25 14:08:38 -07:00
parent 11e57c810b
commit 9ff74c45cc
4 changed files with 31 additions and 4 deletions
+1 -1
View File
@@ -99,7 +99,7 @@ New workflows declare every available remote capacity pool explicitly:
3. Optionally add an **R2 Storage Configuration** node to provide shared object storage to Vast.ai and SSH targets.
4. Connect every capacity and storage node's `REMOTE_CONFIGURATION` output to the configurator's growing input group.
The connected graph is authoritative. Connecting only Modal configurations produces a Modal-only plan; connecting several providers lets the planner choose among all of them. Each capacity configuration has its own name and limit, so one workflow can offer several Modal GPU types, several independent Vast marketplace searches, and several SSH hosts at the same time. Storage configurations are sibling values in the same configurator but never become scheduler targets or contribute capacity.
The connected graph is authoritative. Connecting only Modal configurations produces a Modal-only plan; connecting several providers lets the planner choose among all of them. Each capacity configuration has its own name and limit, so one workflow can offer several Modal GPU types, several independent Vast marketplace searches, and several SSH hosts at the same time. Storage configurations are sibling values in the same configurator but never become scheduler targets or contribute capacity. All configuration nodes are concrete ComfyUI v3 data nodes, so the branch remains valid when ComfyUI executes it after queue-time planning.
Capacity limits are concurrency ceilings, not eager provisioning requests. Modal containers scale as work needs them, Vast instances are quoted during planning and rented only for selected slots, and SSH worker containers are prepared lazily on the declared host. Sequential components can reuse one slot; parallel components consume distinct slots or wait when that is cheaper than another environment.
+1
View File
@@ -514,6 +514,7 @@
- Treat the disconnected Vast configuration node as a local v3 output sink. This preserves it in the queued prompt without wiring it into user data flow, and it must remain in the local-only class set during automatic placement.
- The durable execution-plan payload already carries safe metadata for both capacity and storage configurations. Configurator UI should split those concepts at render time: capacity configurations belong in assignment and readiness rows, while storage configurations belong in a distinct summary beneath the plan. That preserves the provider-neutral payload, avoids portraying storage as schedulable capacity, and lets provider-specific cards expose useful policy such as an R2 bucket, jurisdiction, write-back mode, and key prefix without surfacing credential references.
- R2's account-level REST metrics do not provide a per-bucket total, while its bucket analytics use a separate Cloudflare API authorization surface. A controller that retains only bucket-scoped S3 credentials can still compute exact current bucket payload bytes and object count through paginated `ListObjectsV2`; cache that aggregate briefly so the Configurator can show useful usage without enumerating the bucket on every workflow submission.
- ComfyUI's v3 execution path shallow-clones a node class and then rebuilds it with a metaclass that rejects class mutation. Adding Python's `ABC` to an intermediate v3 node base changes that clone to `ABCMeta`, whose construction writes `__abstractmethods__` and collides with ComfyUI's lock. Extension-owned v3 node bases must stay concrete (using an explicit `NotImplementedError` for provider hooks) unless ComfyUI's class-lock implementation becomes ABC-aware.
- Revalidate every offer response locally even when the marketplace query contains identical hard filters. Rank compatible offers deterministically by total hourly price, then reliability, DLPerf, download rate, and offer ID.
- Keep idle retention separate from minimum offer duration. Retention is a billed lease-lifecycle decision; offer duration only describes how long the marketplace host claims it will remain available.
- The offline Vast simulator should exercise offer disappearance between search and rent, loading-to-running polling, state changes, and destruction while never recording authorization or per-instance API keys.
+2 -3
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
import logging
from abc import ABC, abstractmethod
from collections.abc import Mapping
from typing import Any
@@ -63,13 +62,13 @@ RemoteConfigurationType = io.Custom(REMOTE_CONFIGURATION_IO_TYPE)
RemoteConfigurationSetType = io.Custom(REMOTE_CONFIGURATION_SET_IO_TYPE)
class StorageBackingConfigurationNode(io.ComfyNode, ABC):
class StorageBackingConfigurationNode(io.ComfyNode):
"""Base ComfyUI node for workflow-scoped shared storage configuration."""
@classmethod
@abstractmethod
def define_schema(cls) -> io.Schema:
"""Expose one concrete storage provider's non-secret settings."""
raise NotImplementedError
class R2StorageConfiguration(StorageBackingConfigurationNode):
+27
View File
@@ -46,6 +46,33 @@ def test_configuration_nodes_expose_typed_outputs_and_autogrow_sink(
)
def test_r2_configuration_executes_through_locked_v3_clone(
remote_configuration_nodes_module: Any,
) -> None:
"""R2's concrete v3 node must survive ComfyUI's execution-time class lock."""
from comfy_api.internal import make_locked_method_func
module = remote_configuration_nodes_module
node_class = module.R2StorageConfiguration
class_clone = node_class.PREPARE_CLASS_CLONE(None)
execute = make_locked_method_func(node_class, "execute", class_clone)
output = execute(
configuration_name="shared-r2",
account_id="a" * 32,
bucket="comfy-models",
credential_id="opaque-reference",
jurisdiction="eu",
write_back_mode="async",
key_prefix="comfy-modal-cache/v1/blobs/sha256",
)
assert output.result is not None
configuration = output.result[0]
assert configuration.configuration_id == "r2-storage"
assert configuration.bucket == "comfy-models"
def test_compiler_collects_r2_storage_without_treating_it_as_capacity(
remote_configuration_nodes_module: Any,
) -> None: