Skip to content

plan, exec, optimizer, plan-codec: Python UDF relations: IR, frame seam, operators and codec feature (P3-T014a, PR-26a) - #746

Open
techgopal wants to merge 34 commits into
mainfrom
p3/t014a-udf-relations-ir
Open

techgopal wants to merge 34 commits into
mainfrom
p3/t014a-udf-relations-ir

Conversation

@techgopal

Copy link
Copy Markdown
Contributor

P3-T014 PR-26a: the shared IR, runtime seam and operators for Python UDFs that see a relation rather than a column (design P3-T085-embedded-python-udf-kinds.md §3.1–3.2, approved by the owner on 2026-11-12, Tier U; P3-T014-connect-udfs-sandbox.md §3.2 and §7).

What it adds

  • IR (krishiv-plan).
    • New plan nodes: MapPartitions, GroupMap, CoGroupMap and PythonTableFunction.
    • A new generator kind, GeneratorKind::PythonUdtf.
    • Python UDAF calls in Aggregate.
    • PythonEvalType gains Spark's codes 201–217, 250–254 and 300–302.
    • Analysis refuses a Python UDAF mixed with built-in aggregates, as INVALID_PANDAS_UDF_PLACEMENT. Physical
      planning also raises it, as Spark does.
  • Optimizer. Every new node is opaque: nothing is pushed through it.
  • Seam (krishiv-exec/src/python_relation.rs).
    • UdfRuntime::prepare_relation is a default method that returns KRISHIV_UDF_NOT_AVAILABLE, so the existing
      runtimes compile unchanged.
    • PreparedRelationUdf::open hands out a UdfTask, which is fed Frames: Batch{side}, EndGroup, End.
    • Frames lead with the key columns, computed keys included, which is Spark's layout.
    • RelationCallSpec carries the input column names of each side.
    • A cogroup key with rows on one side only gets a zero-row frame for the other side.
  • Operators for map, grouped map, cogrouped map and grouped aggregate:
    • a hash exchange on the keys (a single partition when there are no keys) and the spillable sort;
    • one task per driver, with a bounded frame window (Blocked while it is full);
    • results reserved as they arrive and stored as they come, spilling when the pool is full;
    • for the aggregate, keys joined with the values per group, with rows going to the spillable store.
  • Refusals.
    • barrier=True raises KRISHIV_UNSUPPORTED_FEATURE.
    • Python window aggregates and UDTFs are IR and codec only here; planning refuses them until PR-26c.
    • DISTINCT, FILTER and WITHIN GROUP on a Python UDAF are refused.
    • A result that does not match the declared schema or the group count raises
      KRISHIV_PYTHON_UDF_RESULT_MISMATCH (39000).
  • Plan codec. A new named feature, python_udf_relations, covers the nodes, the generator kind and the eval
    types. It has goldens, and a reader without the feature refuses the plan.

No runtime implements the relation seam yet, so no query can reach these nodes. The process-pool runtime comes in
PR-26c, the in-process runtime in PR-48, and Connect decode in PR-26d.

Review

An Opus diff review took three rounds, recorded in krishiv-internal ops/reviews/2026-11-13-opus-B-t014a-udf-relations-ir-diff.md:
CHANGES_REQUIRED, then CHANGES_REQUIRED, then APPROVE_WITH_NITS. All the findings that could give a wrong result,
hang, leak or crash are fixed: UDAF modifiers, keys reaching the task, empty cogroup sides, memory reservations,
cogroup side order, and a finalize spin. One nit is fixed in the last commit: an aggregate lane holding only empty
batches no longer makes finalize spin.

Known follow-ups:

  • R2: in finalize, the lent bytes are freed before a spilled part is written. Fixing it means passing the credit
    into add_spilled_part.
  • R3: a retried result's bytes go to the pool rather than to the credit.
  • R4: an error message.

Checks

  • nextest on plan, exec, plan-codec and optimizer: 1,869/1,869.
  • The Python relation tests rerun after the last fix.
  • Clippy -D warnings, layering, file size and cargo check -p krishiv-python pass.
  • Diff coverage: 94.3%.
  • Size: about 5,280 counted lines, over the 5,000 cap. size-approved is needed; the Opus review has passed with
    every blocking finding fixed.

PythonEvalType gains Spark's relation-kind codes (201-217, 251-254,
300-302; pyspark/util.py at 4.2.0) with a kind classifier and
is_scalar. The logical IR gains MapPartitions, GroupMap, CoGroupMap,
PythonTableFunction (with its TABLE argument) and
GeneratorKind::PythonUdtf; each references its UDF by PythonUdfRef,
never the callable. Walkers, printer, canonical encoding and cache
fingerprint cover them. An aggregate mixing a grouped-aggregate
Python UDF with another aggregate is refused with Spark's
INVALID_PANDAS_UDF_PLACEMENT (0A000).

The plan codec refuses the Python UDTF generator until its feature
lands.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
No filter moves through MapPartitions, GroupMap or CoGroupMap (the
pushdown's default arm keeps it above), and column pruning reads every
column of their inputs: the UDF sees them all (group_ops.py L305 at
4.2.0; ADR 0022 §5).

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
The relation kinds get a second runtime seam beside PreparedUdf:
RelationCallSpec, PreparedRelationUdf::open, a UdfTask fed by Frames
(Batch with a side, EndGroup, End) and read through next. A task's
send and next may be awaited at once, so a runtime can wait for its
results to be taken. UdfRuntime::prepare_relation defaults to
KRISHIV_UDF_NOT_AVAILABLE, so the existing runtimes keep compiling.

RelationSink runs map, grouped, cogrouped and aggregate shapes in
PythonEval's exchange shape: one task per driver opened at its first
frame; rows held until a frame is full (maxRecordsPerBatch, and
maxBytesPerBatch when set) or the group or side ends; groups cut where
the sorted key changes; Blocked while a lane's window of frames is
full; results stored in the shared plan's spillable list. Held rows and
queued frames are reserved; a refused batch changes nothing; cancel and
failure drop the tasks.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…P3-T014a, T085 §3.1)

Physical nodes PythonMap, PythonGroupMap, PythonCoGroupMap and
PythonAggregate (pure data: the UDF by name and version, frame
columns, declared result) run on RelationSink. The planner gives a
grouped kind a hash exchange on its keys (one partition without keys)
and the spillable sort; a cogroup's two sides go through one union
stream sorted by key then side, so a key's left rows reach the UDF
before its right rows. An aggregate whose aggregates are Python UDAFs
plans onto PythonAggregate; mixing them with built-ins is
INVALID_PANDAS_UDF_PLACEMENT. Barrier mode, Python window aggregates
and UDTFs are KRISHIV_UNSUPPORTED_FEATURE (PR-26c); a scalar
PythonEval refuses a relation eval type. A worker without a runtime
refuses with KRISHIV_UDF_NOT_AVAILABLE.

Plan codec feature python_udf_relations (semantic): the four nodes,
GENERATOR_KIND_PYTHON_UDTF and every eval type past the scalar ones.
Proto messages, encode and decode, inventory cases, goldens, and a
reader without the feature refuses each plan naming it.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
The structural cache key of each relation node, rewrites of their
children and expressions, frames cut by maxBytesPerBatch, and a task
result of another width failing the phase.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…(P3-T014a review W1)

The Python aggregate feeds every row of a group to the UDF, so these
clauses would be ignored silently. Refused at planning with
KRISHIV_UNSUPPORTED_FEATURE until Spark's class is verified.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…s an empty frame (P3-T014a review M1, M2)

Spark's layout (PandasGroupUtils.resolveArgOffsets): a grouped or
cogrouped kind's frames lead with its key columns, computed keys
included, so a runtime can build `key` for f(key, pdf). RelationCallSpec
carries each side's column names; arity counts the data columns only.
A cogroup key without rows on a side sends one zero-row Batch for that
side (left before the right rows, right before EndGroup), so the Python
side builds Spark's empty DataFrame from the frame's schema.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…eamed aggregate (P3-T014a review Md1-Md3, m3)

- Each task result is reserved as the pump reads it; a refused one stops
  the task's reads until the driver reserves it (sink returns NeedMemory,
  finalize waits). finalize stores results as they arrive, writing a
  part to a spill file when the pool has no room.
- The frame window is checked per frame: rows cut but not framed wait in
  a reserved backlog. A chunk's reservation is shared by the lanes'
  frames and released by the last taker; client-form copies are reserved.
- A grouped aggregate keeps one key batch per input batch and joins the
  oldest pending groups with the values as every lane yields them, so the
  rows reach the spillable store; a task yielding another number of
  values than groups fails.
- Result mismatches raise KRISHIV_PYTHON_UDF_RESULT_MISMATCH (39000), not
  INTERNAL_ERROR.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…-m6, m8-m10)

- Test helper: Blocked took the batch, so the next batch follows (m1);
  a driver blocked on every batch sends each row once.
- A slow fake task blocks the real driver and loses nothing (m2).
- A weighted (change-stream) batch is refused (m4).
- Only a Python aggregate UDF over a window gets the PR-26c refusal; a
  scalar one keeps the window planner's own error (m5).
- INVALID_PANDAS_UDF_PLACEMENT is raised at physical planning only, as
  Spark does; analysis accepts the aggregate (m6).
- Seam docs: one task per driver; UdfTask takes &self and must not call
  back into the operator (m8, m10); cache.rs doc comment back on plan (m9).

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…P3-T014a re-review N1)

A right-only cogroup key whose rows spanned two input batches got a second
zero-row left frame after its first right frames. The empty side now counts
as seen when its frame is queued. Regression test for a left-only and a
right-only key split over two batches.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…spill refused results, hand stored bytes over (P3-T014a re-review N2-N5)

- N2: `wait` counts an aggregate driver ready only when every lane has
  values (what `pair::joined` needs), so `finalize` no longer loops inside
  one poll while one lane is ahead.
- N3: in `finalize` a refused map or grouped result is stored with the
  others (spill file when the pool is full) instead of waiting for memory;
  an aggregate's refused value is reserved with `try_grow` after each step
  and otherwise waits for its memory while the other lanes keep moving.
  A finished aggregate with values left over is `check_complete`'s error
  rather than another loop.
- N4: a stored result's reservation passes to the store's reservation
  through the consumer's grant credit (`lend`), never counted twice.
- N5: tests for two aggregate lanes at different speeds (unit, bounded by a
  poll count; planner, values paired per key) and for a result refused in
  `finalize` under a small memory limit.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
A column key is also a data column, so `groupBy("a")` frames are `a, a, b`:
the `RelationCallSpec::input_names` doc says a runtime splits by key_count,
never by name.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…3-T014a re-review R1)

wait() counted a lane with any queued result as joinable, but joined()
needs rows; a lane holding only 0-row batches made finalize spin.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…s-ir

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>

# Conflicts:
#	crates/krishiv-common/krishiv-error-classes.json
#	crates/krishiv-exec/tests/planner/main.rs
@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ❌ Failed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ❌ Failed 2026-11-15T03:19:02.692819Z
We couldn’t complete this review. Comment @codex review to try again.
dfb1320 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec31271d09

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codecov

codecov Bot commented Nov 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 34 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 224 untouched benchmarks
⏩ 5 skipped benchmarks1


Comparing p3/t014a-udf-relations-ir (1ce019a) with main (62a1bab)

Open in CodSpeed

Footnotes

  1. 5 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a573cab2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…CI docs)

The module doc linked UdfRuntime::prepare_relation, which is not in scope
there; rustdoc -D warnings failed the docs job. A reference link to
crate::python_eval::UdfRuntime::prepare_relation resolves both uses.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…view)

A Python aggregate's keys and call arguments went to the native compiler
with their scalar Python calls in them, and so did a scalar call over a
call's result: GROUP BY f(v), udaf(f(v)) and f(udaf(v)) failed at
planning. As the ordinary aggregate path does, and as Spark does
(ExtractGroupingPythonUDFFromAggregate, ExtractPythonUDFs and
ExtractPythonUDFFromAggregate at v4.2.0), the calls in keys and arguments
are extracted into PythonEval nodes below the node, which runs each call
over their columns, and the calls over its results into nodes above it.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…'s collation (#746 review)

GroupMap and CoGroupMap passed collated string keys as they are to the
exchange, the sort and the group boundaries, which compare bytes: under
UTF8_LCASE, 'A' and 'a' were two groups (two one-sided cogroup calls).
Spark groups and cogroups by the collation (GroupedIterator and
CoGroupedIterator orderings at v4.2.0). The planner now appends each
collated key's comparison key, the one the collation rewrite gives
aggregates, sorts and exchanges (krishiv_optimizer::collation_key, now
public), and clusters, sorts and cuts groups on it; the frames keep each
row's own key values.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…e memory (#746 review)

In finalize a frame whose copies the pool refused was awaited first,
while the lanes' results held reservations nothing could reclaim (the lane
queues are not spillable, and the store's parts are not until it
finalizes): the wait could only time out. A driver's results are now
stored before it waits, written out while a frame waits for memory, and
the frame tries again; the wait for what is still refused comes after
every driver stored its results.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
… rows (#746 review)

take_input reserves a whole batch, but cut_held split that reservation
over the held rows only: the last cut of a batch's first group took the
whole batch's bytes, so its later groups' slices, which keep the batch's
buffers, held none once the first group's frames were taken. The driver
now counts the rows the reservation covers (taken, not cut yet) and each
cut takes its share of all of them.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…746 review)

Spark's ArrowAggregatePythonExec (L156-157 at v4.2.0) returns an empty
partition as it is, without calling the UDF, so a keyless grouped-agg
Python UDF over empty input yields zero rows, unlike a built-in global
aggregate. The test pins that the relation sink does the same.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0ce59723f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/krishiv-plan/src/function.rs Resolved
@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: b081587b5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
@techgopal

Copy link
Copy Markdown
Contributor Author

Note on commit b7332a4: its message ("reviews: T063 N1b re-review ...") is wrong; a shared message file was overwritten before the commit ran. The commit is the fix for the two memory threads in python_relation/sink.rs: frame columns reserve every buffer beyond the input's (computed UDAF arguments), and the driver stays Blocked until the frame backlog has drained. The PR is squash-merged under its title, so the stray message does not reach main.

@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: b7332a4ab8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@techgopal
techgopal added this pull request to the merge queue Nov 14, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 14, 2026
…s-ir

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>

# Conflicts:
#	crates/krishiv-common/krishiv-error-classes.json
@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91f01669f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…as Spark's writer

With spark.sql.execution.arrow.maxBytesPerBatch set, the relation sink cut
frames by the held input batch's average bytes per row. Uneven rows (two
30 KiB strings, then empty ones) and computed arguments that grow the
payload made frames the average did not see.

The cut now sizes the frame columns the tasks get (computed arguments,
client forms) as Spark's writer does: rows are written while the frame's
Arrow size is under the cap, so a frame ends at the row that brings it to
the cap or past (BatchedPythonArrowInput.writeSizedBatch,
PythonArrowInput.scala:226-233 at v4.2.0; the size is Arrow Java's
getBufferSizeFor per field, ArrowWriter.scala:135-143 and 183-187). The
size of rows start..start+k comes from validity, offsets and value spans
(no copy), and grows with k, so the cut is a binary search. A map's rows
travel as one struct column (MapInBatchEvaluatorFactory.scala:56-71), whose
validity counts. Spark's default, Int.MaxValue, reads as no limit
(PythonArrowInput.scala:227).

Tests: the uneven-strings case (frames 2, 100), rows that each fill most of
the cap (two per frame), and a row over the cap by itself (it ends the
frame it joins; alone it is a frame of its own).

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53e4e3fbc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 53e4e3fbc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…s-ir

Conflicts with TABLESAMPLE (#761, `sample`), resolved keeping both sides:
- plan.proto: main's `SampleNode sample = 27` keeps its number; the
  python_udf_relations nodes move to 28-31 (python_map, python_group_map,
  python_co_group_map, python_aggregate). Generated code regenerated with
  plan-protogen; the four python_udf_relations node goldens re-blessed
  (eval types and the UDTF generator are byte-identical).
- krishiv-plan encode.rs: `Sample` keeps tag 36; MapPartitions, GroupMap,
  CoGroupMap and PythonTableFunction take 37-40 (ENCODING_VERSION unchanged:
  neither side had released them).
- plan.rs, walk.rs, lib.rs, physical/mod.rs, features.rs, codec tests: both
  sides' arms and variants.
- LogicalPlan's PartialEq compares the children pair by pair before the
  derived PlanNode comparison, which then finds them in the walk's memo:
  with both sides' new variants that derived frame (1,880 bytes unoptimised)
  nested once per level overflowed the 2 MiB stack at MAX_DEPTH
  (tests::depth::plans_at_the_cap_survive_every_traversal).

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
… of filling the pool

In an aggregate with several UDF lanes a group is stored only once every
lane has its value, and lane results are not spillable. A fast lane could
reserve results until the pool was full before a slower lane's first value
was reserved; nothing could be joined or released, and the slow lane's
value waited for memory until arbitration failed (UNABLE_TO_ACQUIRE_MEMORY).

A lane now reads no result while it holds more rows than a peer whose task
still runs (`lane.rs` `ahead`): it waits on its own task, holding at most
one result past the others, and is woken when a peer yields or ends. A
lane of a map or grouped shape has no peers. Memory accounting is
unchanged: every result is still reserved as it arrives.

Test: two lanes, the slow one gated, a 1 MiB pool with spill on: the fast
lane reads one value then waits, and finalize completes (before: the slow
lane's 104-byte value was refused).

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
…es to take

Round-5 review M1: the hold of 7da68d8 stopped a fast lane's reads while
any running peer had fewer rows. A runtime that takes no frame while its
results are unread then filled the fast lane's window, which stops the
frames to every lane; a peer runtime that needs later frames before it
yields (one batching values across groups) never got them, and exec has
no call deadline: a hang.

The hold now also needs the lagging peer to have frames it has not taken
(`lane.rs` `held_back`: `in_flight() > 0`). The peer wakes the waiting
pump when it takes its last frame, yields or stops (ended or failed). A
peer starved of input never holds anyone back, so progress depends only
on a lane taking frames it already has; past that the pool decides (a
spill or a classed error), never a hang. The lane with the fewest rows is
never held and a lane is held only by a peer with strictly fewer rows,
so the holds form no cycle. With the production window the lead stays
bounded by the window plus what the peer runtime buffers.

Also from the review:
- m1: only an aggregate's lanes are peers (`Lane::group(.., peered)`); a
  map or grouped shape's lanes are alone.
- m2: a running row count per lane (`LaneIo::rows`, `pop_output`,
  `took`) instead of re-summing the queue on every result.
- m3: "one result batch", not "one result".
- m4: a peer's waker is taken under its lock and woken after release.

Tests (M2) at the relation sink's production window (DEFAULT_WINDOW, 4):
a peer batching values across 40 groups with a fast runtime that takes
no frame while a result is unread (hung before, timeout 10 s); a peer
ending with fewer values (the mismatch error, hung before); a peer
failing while the fast lane is held (its error); the gated slow lane at
the production window. The deep-window reproduction of the original
stall is kept; once its slow lane opens it takes the whole window at
once, so the hold ends and the lanes share the pool (slower, 12 s in a
debug build, never stuck).

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 7da68d8f73

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@techgopal

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@techgopal

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 15, 2026 •

Copy link
Copy Markdown
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…st-frame wake is tested (#746 review r6)

- m2: when a task's `next` ends, its lane is marked stopped and its peers
  woken at once, before its send side finished, so a runtime that ends its
  output early while a send hangs cannot hold every peer.
- m3: a new test, a quiet peer that batches values across groups and
  signals its reader only on output, hangs without the "took its last
  frame" wake (checked by deleting it: the test times out) and passes with
  it. No earlier test failed without that wake.
- m1: the deep-window test's doc states what it checks (the hold while
  the gated peer has frames) and the bound once the gate opens; 3,000
  groups instead of 12,000 (0.25 s instead of 21 s) and a 10 s timeout
  instead of 40 s.
- `LaneIo::took` debug-asserts that the running row count does not drift.

Signed-off-by: gopal tathe <44522021+techgopal@users.noreply.github.com>
@techgopal

Copy link
Copy Markdown
Contributor Author

Diff review passed: Opus review of dfb1320 (lane hold only while a lagging peer has frames it has not taken), verdict APPROVE_WITH_NITS, 0 blockers, 0 majors; no hang path, exact row counter, memory accounting unchanged. The minors are fixed in 1ce019a: a lane whose output ends is marked stopped and wakes its peers; a quiet-peer test covers the last-frame wake (it hangs without it); the deep-window test is 3,000 groups with a 10 s timeout; a debug_assert guards the row counter.

@techgopal

Copy link
Copy Markdown
Contributor Author

main merged in for the CI disk fix (#776); lane: git pull before your next push

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant