Merge feat/runpod-serverless-workers into feat/dynamic-image-list-queue

This commit is contained in:
Robert Wojciechowski
2026-10-13 21:33:28 +00:00
17 changed files with 407 additions and 116 deletions
+1 -1
View File
@@ -88,7 +88,7 @@ def _split_host_and_port(host):
def build_worker_url(worker, endpoint=""):
"""Construct the worker base URL with optional endpoint."""
if worker.get("type") == "serverless":
raise ValueError("Serverless pools have no ComfyUI worker URL")
raise ValueError("Serverless workers have no ComfyUI worker URL")
host = (worker.get("host") or "").strip()
port = int(worker.get("port", worker.get("listen_port", 8188)) or 8188)
+13 -3
View File
@@ -38,11 +38,17 @@ _ENDPOINT_ID_RE = re.compile(r"[A-Za-z0-9_-]{1,200}", re.ASCII)
_UNSUPPORTED_SECRET_STORE_MESSAGE = (
"Serverless credential storage is currently supported on POSIX masters only"
)
# Images per run is also this worker's effective parallelism: every image job
# is submitted at once (the runtime's fixed PER_WORKER_JOB_LIMIT equals this),
# and the Runpod endpoint's max workers decides how many actually run together.
MAX_JOBS_PER_RUN = 16
_NUMERIC_FIELDS = {
"jobs_per_run": (1, 16, 1),
"concurrency": (1, 16, 1),
"jobs_per_run": (1, MAX_JOBS_PER_RUN, 1),
"timeout_seconds": (30, 86400, 1800),
}
# Older configs saved a separate concurrency setting. It is accepted so they
# still load, then dropped: PER_WORKER_JOB_LIMIT replaces it.
_LEGACY_IGNORED_FIELDS = ("concurrency",)
_THREAD_LOCKS: dict[str, threading.Lock] = {}
_THREAD_LOCKS_GUARD = threading.Lock()
@@ -67,7 +73,9 @@ def validate_serverless_worker(worker: dict) -> dict:
endpoint_id = result.get("endpoint_id")
if not isinstance(endpoint_id, str) or not _ENDPOINT_ID_RE.fullmatch(endpoint_id):
raise ServerlessConfigError("endpoint_id must be an opaque Runpod identifier")
raise ServerlessConfigError(
"Endpoint ID must be just the Runpod endpoint ID (letters, numbers, - or _), not a URL"
)
if result.get("type", "serverless") != "serverless":
raise ServerlessConfigError("type must be serverless")
@@ -81,6 +89,8 @@ def validate_serverless_worker(worker: dict) -> dict:
if type(value) is not int or not minimum <= value <= maximum:
raise ServerlessConfigError(f"{field} must be an integer from {minimum} to {maximum}")
result[field] = value
for field in _LEGACY_IGNORED_FIELDS:
result.pop(field, None)
result["type"] = "serverless"
result["provider"] = "runpod"