Skip to content

Fix Python VarIntCoder OverflowError on uint64 values#39047

Merged
jrmccluskey merged 3 commits into
apache:masterfrom
AviKndr:fix-varintcoder-uint64
Jul 1, 2026
Merged

Fix Python VarIntCoder OverflowError on uint64 values#39047
jrmccluskey merged 3 commits into
apache:masterfrom
AviKndr:fix-varintcoder-uint64

Conversation

@AviKndr

@AviKndr AviKndr commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #39048

Addresses #19696 — this makes VarIntCoder overflow behavior consistent between the compiled and pure-Python paths, but does not fully resolve that issue, so it is intentionally not closed on merge.

What

VarIntCoder raises OverflowError when handed a Python int in the unsigned 64-bit range [2**63, 2**64) (a uint64), even though such a value has a well-defined VarInt encoding.

Why

In the Cython build, the stream methods take a signed int64_t:

cpdef write_var_int64(self, libc.stdint.int64_t signed_v):
    cdef libc.stdint.uint64_t v = signed_v   # body already works on unsigned

The body re-casts to uint64_t and encodes the bit pattern correctly, but Cython converts the incoming Python int to the signed int64_t parameter at the call boundary — before the body runs — so any uint64 value is rejected with OverflowError. The same applies to get_varint_size used by estimate_size.

Fix

A uint64 value v and the signed int64 v - 2**64 (its two's-complement twin) produce identical VarInt bytes. VarIntCoderImpl now folds uint64 values to that signed twin before they cross into Cython, in both encode_to_stream and estimate_size:

def _as_signed_int64(value):
  if (1 << 63) <= value < (1 << 64):
    return int(value) - (1 << 64)
  return value
  • Wire-compatible — byte-identical to Java's signed VarIntCoder.
  • No regression — normal signed ints pass through untouched; the pure-Python slow_stream path produces the same bytes.
  • Still bounded — values >= 2**64 are left unchanged and still overflow in the Cython path, preserving the coder's 64-bit contract.

Note: decoding remains signed (read_var_int64 returns int64_t), matching Java — so the encoding of 2**64 - 1 decodes back to -1. This change removes the crash and guarantees correct wire bytes; it does not make the coder round-trip uint64 back to unsigned (that would be a separate coder).

Tests

Adds test_varint_coder_uint64 to the shared coders_test_common.py suite (runs both compiled and uncompiled): no-overflow encoding, wire equivalence to the signed twin, size estimation, signed decode semantics, and the still-raising out-of-range case (guarded on the compiled implementation).


Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:

  • Mention the appropriate issue in your description (for example: addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, comment fixes #<ISSUE NUMBER> instead.
  • Update CHANGES.md with noteworthy changes.
  • If this contribution is large, please file an Apache Individual Contributor License Agreement.

🤖 Generated with Claude Code

The Cython write_var_int64/get_varint_size stream methods take a signed
int64_t parameter. A Python int in the unsigned 64-bit range [2**63,
2**64) -- a uint64 -- is converted to that signed parameter at the call
boundary and rejected with an OverflowError before the method body runs,
even though the body already operates on the unsigned bit pattern and the
VarInt wire encoding is well-defined.

Fold such values to the signed int64 with the identical bit pattern (and
thus identical VarInt encoding) before handing them to the stream. This
matches Java's signed VarIntCoder on the wire; decoding remains signed.
Values past 64 bits are left unchanged and still overflow downstream,
preserving the coder's documented 64-bit limit.

Adds test_varint_coder_uint64 covering no-overflow encoding, wire
equivalence to the signed twin, size estimation, signed decode, and the
still-raising out-of-range case (guarded on the compiled implementation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AviKndr AviKndr marked this pull request as ready for review June 28, 2026 04:41
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where the Python VarIntCoder incorrectly raised an OverflowError for unsigned 64-bit integers. By normalizing these values to their signed 64-bit counterparts before they reach the Cython implementation, the coder now correctly handles the full 64-bit range while maintaining compatibility with existing wire formats and Java implementations.

Highlights

  • VarIntCoder Overflow Fix: Implemented a helper function to fold unsigned 64-bit integers into their signed 64-bit two's-complement equivalents before passing them to Cython, preventing unnecessary OverflowErrors.
  • Wire Compatibility: Ensured that the encoding remains wire-compatible with Java's VarIntCoder, maintaining consistent byte representation for uint64 values.
  • Test Coverage: Added comprehensive tests in coders_test_common.py to verify uint64 encoding, size estimation, and signed decoding semantics.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a helper function _as_signed_int64 to fold uint64 values in the range [2**63, 2**64) into signed int64 values, preventing Cython overflow issues in VarIntCoderImpl and aligning Python's behavior with Java's VarIntCoder. The feedback suggests enforcing the 64-bit integer range limit in the pure-Python path as well to ensure consistent behavior between the compiled and uncompiled implementations, which would also allow simplifying the new unit tests by removing the compiled-only guard.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread sdks/python/apache_beam/coders/coder_impl.py
Comment thread sdks/python/apache_beam/coders/coders_test_common.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Checks are failing. Will not request review until checks are succeeding. If you'd like to override that behavior, comment assign set of reviewers

AviKndr and others added 2 commits June 28, 2026 12:01
Address review feedback: raise OverflowError in _as_signed_int64 for
values outside the 64-bit range (reusing fits_in_64_bits) so the
pure-Python path matches the Cython int64_t overflow instead of silently
encoding out-of-range values. Both encode_to_stream and estimate_size
already wrap OverflowError, so the user-facing message is unchanged.

Drop the is_compiled guard in test_varint_coder_uint64 so the
out-of-range case runs on both paths, add the negative lower-bound case
(-(2**63) - 1), and remove the now-unused coder_impl import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The compiled encode() cast value to a C int64_t (ivalue, typed via
coder_impl.pxd) for the small-ints fast path, which overflowed on uint64
values before reaching encode_to_stream's fold -- the cause of the
test_varint_coder_uint64 CI failure on the Python PreCommit suites.

Do the small-ints check with a plain Python-object comparison so it can't
overflow; non-small values (including uint64 and out-of-range ints) fall
through to StreamCoderImpl.encode -> encode_to_stream, where
_as_signed_int64 folds them and raises the wrapped OverflowError for
genuinely out-of-range values. Drop the now-unused ivalue int64_t local
hint from coder_impl.pxd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Assigning reviewers:

R: @jrmccluskey for label python.

Note: If you would like to opt out of this review, comment assign to next reviewer.

Available commands:

  • stop reviewer notifications - opt out of the automated review tooling
  • remind me after tests pass - tag the comment author after tests pass
  • waiting on author - shift the attention set back to the author (any comment or push by the author will return the attention set to the reviewers)

The PR bot will only process comments in the main thread (not review comments).

@AviKndr AviKndr changed the title [BEAM] Fix Python VarIntCoder OverflowError on uint64 values Fix Python VarIntCoder OverflowError on uint64 values Jun 29, 2026

@jrmccluskey jrmccluskey 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.

LGTM, thanks!

@jrmccluskey jrmccluskey merged commit e87f50e into apache:master Jul 1, 2026
130 of 131 checks passed

cdef list small_ints
cdef class VarIntCoderImpl(StreamCoderImpl):
@cython.locals(ivalue=libc.stdint.int64_t)

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.

This is a common (hot) code path. There might be performance implication here as it added multiple branches and removed this libc.stdint.int64_t C type.

Did some local benchmark

current HEAD:

VarIntCoderImpl benchmark (SLOW_STREAM=False)
Iterations per test: 20,000,000

--- Small Integers (<32767) ---
Encode: 2.4943s (8,018,329 ops/sec)
Decode: 1.1410s (17,528,902 ops/sec)

--- Large Integers (64-bit) ---
Encode: 3.1621s (6,324,850 ops/sec)
Decode: 1.3801s (14,492,127 ops/sec)

reverting this change:

VarIntCoderImpl benchmark (SLOW_STREAM=False)
Iterations per test: 20,000,000

--- Small Integers (<32767) ---
Encode: 1.9062s (10,492,259 ops/sec)
Decode: 1.1310s (17,683,731 ops/sec)

--- Large Integers (64-bit) ---
Encode: 2.5946s (7,708,456 ops/sec)
Decode: 1.4151s (14,133,459 ops/sec)

Ran it several times and the difference appears to be significant

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.

We even had dedicated micro-optimization for its Java counterpart: #29689

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Python VarIntCoder raises OverflowError on uint64 integers

3 participants