Skip to content

fix(polling_source): eliminate per-batch schema inference, fix zero-row crash (ENG-952) - #261

Merged
eywalker merged 10 commits into
mainfrom
eywalker/eng-952-fix-upstream-pollingsource-dies-when-a-poll-returns-a-zero
Aug 28, 2026
Merged

fix(polling_source): eliminate per-batch schema inference, fix zero-row crash (ENG-952)#261
eywalker merged 10 commits into
mainfrom
eywalker/eng-952-fix-upstream-pollingsource-dies-when-a-poll-returns-a-zero

Conversation

@kurodo3

@kurodo3 kurodo3 Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Root cause: _build_stream_from_df called infer_schema_nullable on every batch. A zero-row batch has null_count == 0 for all columns, so every field was inferred nullable=False. _validate_combining_schemas then raised SchemaInconsistencyError against the accumulated stream's nullable schema — killing the source on the first empty poll.
  • Fix: Establish _canonical_arrow_schema exactly once per source, never re-infer per batch.
    • Declared-schema path — when impl.schema() returns a non-None Schema, derive the Arrow schema from Python type annotations (T | Nonenullable=True). No inference, no warning.
    • Infer-once path — when impl.schema() returns None, infer from the first non-empty batch and emit a WARNING prompting the caller to declare a schema. Zero-row frames before schema establishment return None (skipped) so they cannot lock in a spurious all-non-nullable schema.
  • 5 regression tests added in TestPollingSourceZeroRowBatch.
  • PS4 entry added to DESIGN_ISSUES.md.

Fixes ENG-952

Test plan

  • uv run pytest tests/test_channels/test_polling_source.py::TestPollingSourceZeroRowBatch -v — 5 new tests, all pass
  • uv run pytest tests/test_channels/test_polling_source.py -v — 66 tests, all pass
  • uv run pytest tests/ -x -q — 4774 passed, 93 skipped, 2 xfailed, 0 failures

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an ENG-952 regression in PollingSource where per-batch Arrow nullability inference could crash the polling loop when a zero-row batch followed a batch containing nulls. The change makes schema/nullability a per-source invariant by establishing a canonical Arrow schema once and applying it consistently to subsequent batches.

Changes:

  • Establish _canonical_arrow_schema once per PollingSource instance (declared-schema path uses impl.schema(); otherwise infer once from the first non-empty batch) and apply canonical nullability to every batch via name-based casting.
  • Skip zero-row frames before canonical schema establishment on the infer-once path to avoid locking in an all-non-nullable schema.
  • Add 5 async regression tests covering the crash scenario, warning behavior, and empty-first-batch behavior; document the resolved design issue (PS4) and add supporting spec/plan artifacts.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/orcapod/core/sources/polling_source.py Adds per-source canonical Arrow schema handling and avoids zero-row nullability re-inference that caused SchemaInconsistencyError.
tests/test_channels/test_polling_source.py Adds ENG-952 regression tests validating stability across zero-row batches and correct warning behavior for schema inference.
DESIGN_ISSUES.md Logs PS4 as resolved with a concise description of the root cause and fix.
superpowers/specs/2026-08-27-eng-952-polling-source-zero-row-batch-fix.md Captures the design rationale and intended behavior for the canonical-schema approach.
superpowers/plans/2026-08-27-eng-952-polling-source-zero-row-batch-fix.md Provides an implementation/test plan matching the applied fix and added coverage.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@kurodo3

kurodo3 Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Copilot raised no inline comments and found no issues across all 5 changed files. No code changes are needed in response to this review round — the PR is ready as-is.

kurodo3 Bot added 10 commits August 28, 2026 00:33
…g from inline test impls

Follow the established inline-class pattern used throughout the file: test
impl classes define only identity, schema, poll, fetch, and close.  Also add
a descriptive failure message to the _accumulated_stream assertion so failures
self-describe rather than raising an AttributeError on the next line.
…ed for _batches API

ITL-617 replaced _accumulated_stream with an append-only _batches list.
Zero-row batches produce empty ArrowTableStream entries in _batches, so the
assertion must count total rows across all batches rather than batch-list length.

Also rename DESIGN_ISSUES PS4 entry to PS5 to avoid collision with the
ITL-617 PS4 entry that was added to main.
@kurodo3
kurodo3 Bot force-pushed the eywalker/eng-952-fix-upstream-pollingsource-dies-when-a-poll-returns-a-zero branch from 42db3c0 to 1eb1d43 Compare August 28, 2026 00:37
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kurodo3

kurodo3 Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the approval, @eywalker! No changes needed — the PR is ready to merge whenever you'd like.

@eywalker
eywalker merged commit 373856e into main Aug 28, 2026
11 checks passed
@eywalker
eywalker deleted the eywalker/eng-952-fix-upstream-pollingsource-dies-when-a-poll-returns-a-zero branch August 28, 2026 02:00
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.

2 participants