How ComfyUI's Async/Await Server Architecture Handles Prompts: A Deep Dive

ComfyUI processes prompts through an asynchronous pipeline built on aiohttp that validates, enqueues, and executes generation workflows without blocking the HTTP server, enabling concurrent request handling and real-time WebSocket progress updates.

ComfyUI leverages a fully asynchronous HTTP and WebSocket server built on aiohttp to manage Stable Diffusion generation workflows. The async/await server architecture in the Comfy-Org/ComfyUI repository handles prompts through three distinct stages: reception and validation, enqueueing, and asynchronous execution. This design allows the server to accept multiple concurrent requests while streaming fine-grained progress updates to connected clients.

Async Prompt Reception and Validation

The prompt lifecycle begins when a client POSTs JSON to the /prompt endpoint. In server.py (lines 72-79), the PromptServer instance receives the payload and optionally patches it through the NodeReplaceManager.

Validation occurs immediately via execution.validate_prompt (defined in execution.py, lines 14-30). This async function traverses the workflow graph to confirm each node class exists, verifies that at least one output node is present, and validates input connections. Invalid prompts return immediately as JSON responses, preventing them from ever entering the execution queue.

The PromptQueue System

Once validated, prompts enter the PromptQueue class defined in execution.py (lines 37-55). The server wraps each prompt in a tuple structure:

(number, prompt_id, prompt, extra_data, outputs_to_execute, sensitive)

This tuple is pushed onto a thread-safe heapq protected by an RLock and Condition variable via self.prompt_queue.put() (as seen in server.py, lines 90-92). The heap structure enables priority ordering while maintaining thread safety across async boundaries. The server immediately returns the assigned prompt_id and queue number to the client, allowing the HTTP connection to close while processing continues asynchronously.

Asynchronous Execution Engine

A dedicated worker coroutine running in the main event loop continuously polls the queue. In main.py (lines 7-13), the run() coroutine enters a loop that calls q.get(timeout=...) on the PromptQueue.

When a prompt is dequeued, the worker extracts the data and invokes the execution engine via e.execute() (lines 50-62 in main.py). The execution.execute function (defined in execution.py, line 413) is fully async and:

  • Resolves node inputs through _async_map_node_over_list
  • Awaits each node's FUNCTION or async execute method when required
  • Respects interruption signals via comfy.model_management.throw_exception_if_processing_interrupted

After execution completes, the worker triggers garbage collection and model unloading logic (lines 84-99 in main.py) to free GPU/CPU memory before processing the next item.

Real-Time WebSocket Communication

During execution, the engine streams progress updates through the WebSocket publish loop. The PromptServer.publish_loop (lines 81-84 in server.py) broadcasts incremental events including executed, status, and preview_image messages to connected clients.

This async communication channel operates independently of the HTTP request lifecycle, enabling live progress bars and preview images even for long-running generation tasks. Binary image data transfers utilize protocols defined in protocol.py for efficient streaming.

Practical Implementation Examples

Submitting a Prompt via HTTP

Clients submit workflows through the REST API endpoint handled in server.py:

import requests, uuid

prompt = {
    "prompt": {
        "3": {"class_type": "LoadImage", "inputs": {"url": "https://example.com/img.png"}},
        "4": {"class_type": "CLIPTextEncode", "inputs": {"text": "a sunset over mountains"}},
        "5": {"class_type": "KSampler", "inputs": {"seed": 42, "steps": 20, "cfg": 7.0}},
        "6": {"class_type": "SaveImage", "inputs": {"filename_prefix": "out_"}, "output": True}
    },
    "prompt_id": str(uuid.uuid4())
}
resp = requests.post("http://127.0.0.1:8188/prompt", json=prompt)
print(resp.json())  # Returns: {"prompt_id": "...", "number": 0, "node_errors": {}}

Listening for Async Events via WebSocket

Real-time updates flow through the WebSocket handler implemented in PromptServer.websocket_handler (server.py, lines 50-71):

import asyncio, websockets, json

async def listen():
    async with websockets.connect("ws://127.0.0.1:8188/ws?clientId=myclient") as ws:
        init = json.loads(await ws.recv())
        print("Queue status:", init)
        
        while True:
            msg = json.loads(await ws.recv())
            typ, data = msg["type"], msg["data"]
            if typ == "executed":
                print(f"Node {data['node']} finished")
            elif typ == "progress":
                print(f"Progress: {data['value']}/{data['max']}")

asyncio.run(listen())

Direct Async API Access

For unit testing or embedded deployments, bypass HTTP entirely using the internal async API:

import asyncio, uuid
from server import PromptServer
from execution import PromptQueue, validate_prompt, execute

async def run_prompt():
    loop = asyncio.get_event_loop()
    srv = PromptServer(loop)
    queue = PromptQueue(srv)
    
    prompt = {...}  # Workflow JSON

    prompt_id = str(uuid.uuid4())
    
    valid, _, outputs, _ = await validate_prompt(prompt_id, prompt, None)
    if not valid:
        raise RuntimeError("Invalid prompt")
    
    queue.put((0, prompt_id, prompt, {}, outputs, {}))
    item, _ = queue.get()
    await execute(srv, item[2], {}, {}, item[4], set(), set(), set(), {})

asyncio.run(run_prompt())

Summary

  • ComfyUI implements a non-blocking prompt pipeline using aiohttp async route handlers in server.py to accept concurrent HTTP requests without thread blocking.
  • The PromptQueue in execution.py utilizes a thread-safe heapq with RLock and Condition variables to manage workflow ordering and synchronization.
  • Async validation occurs immediately upon reception via execution.validate_prompt, preventing invalid workflows from consuming resources.
  • A dedicated worker coroutine in main.py pulls items from the queue and executes them via execution.execute, supporting both sync and async node functions through await patterns.
  • Real-time progress flows through WebSocket publish loops, streaming executed and preview_image events independently of the request lifecycle.

Frequently Asked Questions

How does ComfyUI prevent invalid prompts from entering the execution queue?

The execution.validate_prompt async function (lines 14-30 in execution.py) validates the workflow graph structure, confirms node class existence, and checks for required output nodes before enqueueing. Invalid prompts receive immediate JSON error responses, ensuring only valid workflows reach the PromptQueue.

Can the prompt queue handle priority scheduling?

Yes. The PromptQueue implementation in execution.py (lines 37-55) uses a heapq data structure, allowing prompts to be inserted with priority values. The heap ordering ensures higher-priority workflows execute first while maintaining thread safety through RLock and Condition primitives.

What enables real-time progress updates during generation?

The WebSocket publish loop (PromptServer.publish_loop in server.py, lines 81-84) broadcasts incremental execution events asynchronously. As the engine processes each node via execution.execute, it dispatches progress, executed, and preview_image messages to connected clients without blocking the main computation thread.

How does the architecture handle GPU memory cleanup between prompts?

After each execution cycle, the worker coroutine in main.py (lines 84-99) triggers explicit garbage collection and model unloading logic. This ensures GPU/CPU resources are released before the next q.get() call retrieves subsequent prompts from the PromptQueue, preventing memory leaks during long-running server operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →