aglinxinyuan opened a new pull request, #6883:
URL: https://github.com/apache/texera/pull/6883

   ### What changes were proposed in this PR?
   
   `SyncExecutionResource.collectOperatorResult` read operator results through 
an **unbounded** `document.get()` iterator. Three paths abandoned that iterator 
with its Iceberg Parquet reader / S3 input stream still open, leaving the GC 
finalizer to reclaim it (`[S3InputStream] Unclosed input stream created by ...` 
WARNs — the "SyncExecutionResource" rows of the #6881 inventory):
   
   ```
   document.get()                       ── opens reader on first next()
     ├─ visualization branch            ── next() once, early return   → LEAK 
(every sync viz fetch)
     ├─ oversized-first-tuple branch    ── next() once, early return   → LEAK
     ├─ truncation loops                ── drain to exhaustion         → ok
     └─ catch-all on exception          ── abandons mid-drain          → LEAK
   ```
   
   Since #6882, a bounded `getRange(from, until)` iterator releases its reader 
inside the `next()` that serves its last record, and `hasNext` probes open 
nothing. So the fix is to size each read to what the method actually consumes — 
no `close()` API added to `VirtualDocument`:
   
   | Path | Consumes | Read after this PR | Close point |
   |---|---|---|---|
   | visualization result (`totalCount == 1`) | 1 tuple | `getRange(0, 1)` | 
inside the single `next()` |
   | oversized first tuple | 1 tuple | same `getRange(0, 1)` | inside the 
single `next()` |
   | table result (truncation loops) | all remaining | `getRange(1, 
totalCount)` | inside the `next()` serving the bound / exhaustion |
   | swallowed-exception catch-all | partial | catch drains the bounded 
remainder | at the bound |
   
   Notes for review:
   
   - The catch-path drain is bounded: it reads at most the records the 
successful path would have converted to JSON anyway, and a failing reader ends 
the drain early (finalizer remains the backstop for that truly exceptional 
case).
   - The truncation loops previously drained whatever the iterator produced, 
even records appended after `getCount` was sampled; they are now capped at the 
same `totalCount` used for the `totalCount`/`skippedRows` fields, keeping the 
response internally consistent.
   - Deterministic close relies on the iterator semantics of #6882; until that 
lands, these bounded reads behave as before (finalizer-reclaimed), so merge 
order is safe either way.
   
   ### Any related issues, documentation, discussions?
   
   Follow-up to the residual-leak inventory in #6881 
(`SyncExecutionResource.collectOperatorResult` rows). Builds on the iterator 
restructure in #6882. The remaining #6881 rows (`ResultExportService` 
client-disconnect, `InputPortMaterializationReaderThread` interrupt) are lower 
priority and left for separate PRs.
   
   ### How was this PR tested?
   
   Verified by inspection against the bounded-read semantics of #6882: each 
path's read bound equals its consumption, so every path ends in a limit-close, 
an exhaustion-close, or nothing opened. The leak itself is only observable as 
GC-finalizer WARNs, so no unit test pins it; `getRange` range semantics are 
covered by the existing `VirtualDocumentSpec`/`IcebergDocumentSpec` suites, and 
this endpoint's behavior is unchanged (same tuples, same truncation, same 
response shape). Local `WorkflowExecutionService/compile` and `scalafmtCheck` 
pass.
   
   ### Was this PR authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Fable 5)
   


-- 
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