Commit Graph

3 Commits

Author SHA1 Message Date
dante01yoon
85a12d0a83 Key metadata by token, not prompt_id, to survive id collisions
Adversarial review caught that a LIFO stack keyed by ``prompt_id`` still
mis-attributes events when queue execution order differs from registration
order: a second submission with the same ``prompt_id`` lands on top of the
stack, so the first prompt's events read the wrong workflow_id while it
runs, and the first's ``unregister`` then pops the second prompt's entry.

Replace the stack with an internal monotonic token. ``post_prompt``
registers metadata and stashes the returned token on ``extra_data`` under
``PROMPT_METADATA_TOKEN_KEY``. ``main.py``'s queue worker pulls the token
out, pins it on ``PromptServer.active_prompt_metadata_token`` for the
prompt's execution, and clears + unregisters in ``finally``. The merge in
``send_sync`` reads the active token, so each prompt's events are merged
with its own metadata regardless of ``prompt_id`` collisions.

- comfy_execution/metadata.py: ``merge_prompt_metadata`` now takes an
  active token; registry is ``dict[int, PromptMetadata]``; new
  ``PROMPT_METADATA_TOKEN_KEY`` constant for the extra_data carrier.
- server.py: ``register_prompt_metadata`` returns a token (or ``None``
  when no metadata applies); ``unregister`` takes a token;
  ``get_active_prompt_metadata`` snapshots the pinned entry.
- main.py: pops the token from extra_data, pins on the server, clears
  after the terminal "executing: {node: None}" send.
- execution.py ``PromptQueue``: wipe_queue / delete_queue_item now
  unregister by token extracted from each item's extra_data.
- comfy_execution/progress.py: reads workflow_id via
  ``get_active_prompt_metadata`` rather than per-prompt_id lookup.
- tests: unit tests updated for the token signature, plus a real E2E
  test (test_prompt_metadata_e2e.py) that instantiates the actual
  PromptServer and verifies same-prompt_id-different-workflow_id
  submissions don't cross-attribute.

Verified end-to-end against a live ComfyUI server: two submissions with
identical client-supplied prompt_id but different workflow_id each emit
their full execution event stream (execution_start, execution_cached,
executing, executed, execution_success, progress_state, terminal executing)
with the correct workflow_id top-level. 68 / 68 tests pass.
2026-05-19 22:19:15 +09:00
dante01yoon
b0c05af67f Address adversarial review: stack registry + tighten TEXT routing
Two issues raised against the per-prompt metadata registry:

1. Client-supplied prompt_id can collide (post_prompt accepts the id
   verbatim). With a flat dict-keyed registry, the second submission
   clobbered the first and a single unregister could erase metadata still
   needed by the other prompt. Now stored as a LIFO stack per prompt_id —
   most recent registration wins on merge, unregister pops one entry, the
   key is dropped only when the stack drains.

2. BinaryEventTypes.TEXT (send_progress_text) bypasses the metadata merge
   because the payload is bytes, and the wire format has no prompt_id /
   workflow_id field. The merge can't fix this without a wire-format
   change + frontend feature flag, which is out of scope for FE-745. Inside
   scope: default the sid to PromptServer.client_id so other clients no
   longer silently receive untagged text frames. Cross-tab isolation
   inside a single client still depends on the wire-format follow-up.

- comfy_execution/metadata.py: registry is dict[str, list[PromptMetadata]];
  merge_prompt_metadata reads stack[-1]; new resolve_progress_text_sid
  helper extracted so the routing default is unit-testable without the
  full server import chain.
- server.py: register_prompt_metadata appends to the stack;
  unregister_prompt_metadata pops; get_prompt_metadata returns a copy of
  the top entry; send_progress_text routes through resolve_progress_text_sid.
- tests: collision LIFO behavior, sid resolution default, and the existing
  merge tests updated to the stack shape. 16 new assertions in this file,
  104/104 pass overall.
2026-05-19 21:43:55 +09:00
dante01yoon
5396b4fe67 Propagate workflow_id via per-prompt metadata registry (FE-745)
PR #13684 added workflow_id directly to ~9 dict literals across execution.py,
progress.py and main.py, along with executor.workflow_id and
server.last_workflow_id state. It was reverted because the execution layer
should not know about workflow concepts and because a finally-clear race
emitted workflow_id=None on the terminal "executing" frame.

Instead, register per-prompt metadata on PromptServer at submission time
and merge it onto outbound WebSocket payloads inside send_sync. The merge
keys off prompt_id (already present on every execution event), so
execution.py stays workflow-agnostic. Metadata is unregistered in main.py's
queue loop AFTER the terminal executing send, which structurally removes
the race.

- New comfy_execution/metadata.py: PromptMetadata TypedDict +
  build_prompt_metadata + merge_prompt_metadata helpers.
- PromptServer: prompt_metadata registry (lock-protected), register on
  post_prompt, merge in send_sync, expose get_prompt_metadata.
- jobs.py: extracted extract_workflow_id with strict isinstance guards;
  _extract_job_metadata delegates.
- main.py: try/finally around the queue iteration; unregister after the
  terminal "executing: {node: None}" send.
- execution.py PromptQueue: drop registry entries on wipe_queue /
  delete_queue_item so cancellations don't leak.
- progress.py: look up workflow_id from the server registry for the
  per-node nested copies and the binary preview metadata, matching #13684's
  wire shape so the frontend needs no changes.
- Tests: tests-unit/server_test/test_prompt_metadata.py covers the merge,
  the passthrough cases (no prompt_id, unknown prompt_id, binary payloads),
  and the terminal-frame race regression.
2026-05-19 17:16:11 +09:00