fix: unwedge Optimized Cold Start on ComfyUI v0.3.68+ - #146
Open
KarrixLee wants to merge 1 commit into
Open
Conversation
Runs on machines with `optimized_runner` enabled hung forever in "running"
with no error, no traceback, and no node output after "Executing prompt".
ComfyUI v0.3.68 changed PromptExecutor(server, cache_type, cache_size) to
take `cache_args` (a dict), and execute_async began dereferencing
self.cache_args["ram"]. Our forked prompt_worker constructed
PromptExecutor(server) with no cache config, so cache_args stayed None.
From v0.20.0 that dereference sits ABOVE execute_async's try/except:
742 self.add_message("execution_start", ...) <- last thing the user sees
745 ram_headroom = int(self.cache_args["ram"] * ...) <- TypeError
750 try:
so it raised immediately after execution_start, killed the daemon worker
thread, and q.task_done() was never called. The traceback went to bare
stderr via threading.excepthook rather than through DualHandler, which is
why nothing surfaced in machine logs.
Only the optimized runner is affected: prompt_worker lives on
_ComfyDeployRunner, whose sole subclass is ComfyDeployRunnerOptimizedImports.
ComfyDeployRunner extends BaseComfyDeployRunner and runs ComfyUI's real
main.py as a subprocess, so it was never touched by the drift.
my_app.py:
- build_prompt_executor() introspects PromptExecutor.__init__ and passes
cache_type=CLASSIC plus complete cache_args on v0.3.68+, falling back to
the bare call on <= v0.3.67. CLASSIC is pinned deliberately rather than
adopting v0.30's RAM_PRESSURE default -- CLASSIC is what this runner has
always effectively used (cache_type=False falls through to
init_classic_cache), and RAM-pressure eviction is untested against the
model caches held across Modal memory snapshots.
- Guard e.execute() so an executor exception can never again kill the worker
thread: log via logging (so it reaches the log stream), emit
execution_error so the run is marked FAILED, and always call task_done.
- Merge item[5] (the sensitive-keys slot added in v0.3.68) back into
extra_data and pass process_item to strip it from /history. Without this
comfy.org API-node auth was silently dropped on optimized runners; the
custom node already pushes the 6-tuple correctly.
session.py:
- Introduce SESSION_OPTIMIZED_RUNNER as the single source of truth. Session
creation hardcoded False (disabled in 1efd895 when GPU memory snapshotting
shipped) while increase_timeout read the real DB flag, so on a flagged
machine the session ran on ComfyDeployRunner but the timeout extension was
spawned against ComfyDeployRunnerOptimizedImports -- a different Modal
class, so the live container was never extended and the session died early.
Sessions stay pinned to the non-optimized runner; this only stops the two
call sites from disagreeing.
Note: my_app.py is re-copied from the API's current source on every machine
deploy, so existing machines must be redeployed to pick this up.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Runs on machines with Optimized Cold Start (
optimized_runner) enabled hang forever inrunning— no error, no traceback, no node output. Reported log ends at:Root cause
ComfyUI v0.3.68 changed
PromptExecutor(server, cache_type, cache_size)→cache_args(a dict), andexecute_asyncbegan dereferencingself.cache_args["ram"]. Our forkedprompt_workerconstructedPromptExecutor(server)with no cache config, socache_argsstayedNone.From v0.20.0 that dereference sits above
execute_async'stry:So it raised right after
execution_start, killed the daemon worker thread, andq.task_done()was never called. The traceback went to bare stderr viathreading.excepthookinstead of throughDualHandler— which is why nothing showed up in machine logs.Broke at v0.3.68 (2025-11-04), not v0.30. On v0.3.68–v0.4.x the same deref sits at the end of the node loop, so it died after the first node; v0.20.0 hoisted it above the
try, turning it into "dies instantly".Blast radius
Optimized runner only.
prompt_workerlives on_ComfyDeployRunner, whose sole subclass isComfyDeployRunnerOptimizedImports.ComfyDeployRunnerextendsBaseComfyDeployRunnerand runs ComfyUI's realmain.pyas a subprocess, so it never drifted.Changes
my_app.pybuild_prompt_executor()introspectsPromptExecutor.__init__, passingcache_type=CLASSIC+ completecache_argson v0.3.68+ and falling back to the bare call on ≤v0.3.67. CLASSIC is pinned deliberately rather than adopting v0.30's RAM_PRESSURE default — CLASSIC is what this runner always effectively used, and RAM-pressure eviction is untested against the model caches held across Modal memory snapshots.e.execute()so an executor exception can never again kill the worker: log vialogging, emitexecution_errorso the run is marked FAILED, always calltask_done.item[5](sensitive-keys slot added in v0.3.68) back intoextra_data+ passprocess_itemto strip it from/history. Without this, comfy.org API-node auth was silently dropped on optimized runners.session.pySESSION_OPTIMIZED_RUNNERas single source of truth. Creation hardcodedFalse(disabled in 1efd895 when GPU memory snapshotting shipped) whileincrease_timeoutread the real DB flag — so on a flagged machine the session ran onComfyDeployRunnerbut the timeout extension was spawned againstComfyDeployRunnerOptimizedImports, a different Modal class, so the container was never extended and the session died early. Sessions stay pinned to non-optimized; this only stops the two call sites disagreeing.Tests
tests/api/modal/test_prompt_worker.py— 6 passing. AST-extracts the real functions (my_app.py can't be imported: needs modal + generatedcd_config) and drives them against fakes mirroring v0.30.x contracts. Includes a test reproducing the original wedge: thread dies, item stuck incurrently_running, no error to client. The constructor logic was also checked against the realPromptExecutorsignature at every tag v0.3.64 → v0.30.2.Deploy note
my_app.pyis re-copied from the API's current source on every machine deploy, so existing machines need a redeploy to pick this up — deploying the API alone won't unstick them.🤖 Generated with Claude Code