aglinxinyuan opened a new issue, #7248:
URL: https://github.com/apache/texera/issues/7248

   ### Describe the bug
   
   A Loop Start MERGES any unstamped, counter-0 `State` it receives into its 
loop variables (`LoopStartOperator.process_state` → 
`self.state.update(state)`). That merge is load-bearing: the back-edge writes 
the next iteration's variables to the Loop Start's own input-port state URI 
with the same "no loop" envelope (`_jump_to_loop_start` → `State.to_tuple(0)`), 
so the runtime cannot distinguish the loop's own state from someone else's.
   
   The side effect is that **any** operator emitting boundary state via the 
public `produce_state_on_start` / `produce_state_on_finish` API, upstream of or 
inside a loop containing a Loop Start, silently injects its keys into that 
loop's variables:
   
   | Emitted key | Effect |
   |---|---|
   | collides with a loop variable (e.g. the default `i`) | silently overwrites 
loop control — wrong iteration count, no error |
   | `table` | aborts the loop with `_reserved_name_error` at 
`produce_state_on_finish` |
   
   ```
   Source ──▶ statefulOp ──(0, "")──▶ LoopStart ──▶ ... ──▶ LoopEnd
                                         │
                                         └─ self.state.update({"i": ...}) ← 
clobbers the loop counter
   ```
   
   ### Where it comes from
   
   `operator.py`'s `self.state.update(state)` dates to #5700. It is not created 
by #6913, but #6913 makes operator-originated boundary state a first-class flow 
through loops (the Scala side now emits it with an explicit envelope), so the 
hazard is easier to reach.
   
   Raised by @Xiao-zhen-Liu in review on #6913 (`#discussion_r3696858248`). The 
Loop Start side is now documented and pinned there 
(`test_loopstart_merges_unstamped_state_instead_of_forwarding_it`), but the 
collision itself is undefended.
   
   Note the Loop End side is NOT affected: its inbound loop state is always 
stamped by the matching Loop Start, so it can tell the two apart and forwards a 
body operator's boundary state instead of consuming it.
   
   ### Possible directions
   
   1. Namespace the loop's own variables on the envelope (a producer tag on 
`StateFrame`) so a Loop Start can tell its own state from an upstream 
operator's, and reject/ignore the latter.
   2. Reject a merge that would overwrite an existing loop variable, with a 
message naming the colliding key.
   3. Document it as the contract and add an e2e for `Source → statefulOp → 
LoopStart`.
   
   ### Additional context
   
   Related: #6913, #6660, #6661, #5700.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to