apache/beam · error · ValueError
After a successful split, the stop position of the RangeTrac
Error message
After a successful split, the stop position of the RangeTracker must be the same as the returned split position. Observed %r and %r which are different.
What it means
Raised when, after a successful try_split, the RangeTracker's stop_position() does not equal the returned split position. Per the Beam RangeTracker contract, a successful split must set the current range's stop to the returned split position (the remainder becomes the other split). A mismatch means the custom RangeTracker updated its range inconsistently. Note the error-formatting line itself has a % typo in this Beam version, but the condition checked is the stop/split-position mismatch.
Source
Thrown at sdks/python/apache_beam/io/source_test_utils.py:308
reader = source.read(range_tracker)
# Reading 'num_items_to_read_before_split' items.
reader_iter = iter(reader)
for _ in range(num_items_to_read_before_split):
current_items.append(next(reader_iter))
suggested_split_position = range_tracker.position_at_fraction(split_fraction)
stop_position_before_split = range_tracker.stop_position()
split_result = range_tracker.try_split(suggested_split_position)
if split_result is not None:
if len(split_result) != 2:
raise ValueError(
'Split result must be a tuple that contains split '
'position and split fraction. Received: %r' % (split_result, ))
if range_tracker.stop_position() != split_result[0]:
raise ValueError(
'After a successful split, the stop position of the '
'RangeTracker must be the same as the returned split '
'position. Observed %r and %r which are different.' %
(range_tracker.stop_position() % (split_result[0], )))
if split_fraction < 0 or split_fraction > 1:
raise ValueError(
'Split fraction must be within the range [0,1]',
'Observed split fraction was %r.' % (split_result[1], ))
stop_position_after_split = range_tracker.stop_position()
if split_result and stop_position_after_split == stop_position_before_split:
raise ValueError(
'Stop position %r did not change after a successful '
'split of source %r at fraction %r.' %
(stop_position_before_split, source, split_fraction))
if expected_outcome == ExpectedSplitOutcome.MUST_SUCCEED_AND_BE_CONSISTENT:View on GitHub (pinned to 12126d8942)
Solutions
- Inside try_split, set the tracker's stop position to the returned split position before returning.
- Use iobase.RangeTracker's built-in implementation rather than a from-scratch RangeTracker.
- Verify position_at_fraction and try_split agree on position semantics (same position space).
- Check for an off-by-one where the range keeps one extra/missing record after the split.
Example fix
# before
def try_split(self, fraction):
pos = self.position_at_fraction(fraction)
return (pos, fraction)
# after
def try_split(self, fraction):
pos = self.position_at_fraction(fraction)
if pos is None:
return None
old_stop = self.stop_position
self.stop_position = pos
return (pos, fraction) Defensive patterns
Strategy: validation
Validate before calling
before = tracker.stop_position()
res = tracker.try_split(pos)
if res is not None:
assert tracker.stop_position() == res[0], (tracker.stop_position(), res[0]) Type guard
def split_updates_stop(tracker, pos):
before = tracker.stop_position()
res = tracker.try_split(pos)
return res is None or tracker.stop_position() == res[0] Try / catch
try:
source_test_utils.assert_split_at_fraction_behavior(src, n, f, outcome)
except ValueError as e:
if "stop position of the RangeTracker" in str(e):
logger.error("try_split did not shrink range: %s", e)
raise Prevention
- Always set self stop position to the split position inside try_split
- Mirror OffsetRangeTracker's try_split implementation
- Assert stop_position() == returned split position in your own tests
When it happens
Trigger: Custom RangeTracker.try_split returns (split_position, fraction) but does not call _stop_position = split_position (or calls it with a different value), then _assert_split_at_fraction_behavior re-reads stop_position().
Common situations: Hand-rolled RangeTracker implementations that forget to shrink the range after yielding the split, or that shrink by the wrong amount (off-by-one position).
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Split result must be a tuple that contains split position an
- Stop position %r did not change after a successful split of
- Expected split of source %r at fraction %r to be successful
- Error determining if %s allows dynamic splitting
- The first record [starting at %d] must be at a split point
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/a49e9f7f4c35112a.
Report an issue: GitHub.