apache/beam · error · ValueError
Stop position %r did not change after a successful split of
Error message
Stop position %r did not change after a successful split of source %r at fraction %r.
What it means
Raised when try_split reports success (returns a non-None result) but the RangeTracker's stop_position() is unchanged after the split. A successful dynamic split must shrink the current range, so its stop position must move to the split position; an unchanged stop position means the tracker claims a split happened without actually updating its range, which would double-read records in a real pipeline. This catches RangeTrackers that return a split result but mutate no state (or mutate only a copy).
Source
Thrown at sdks/python/apache_beam/io/source_test_utils.py:321
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:
if not split_result:
raise ValueError(
'Expected split of source %r at fraction %r to be '
'successful after reading %d elements. But '
'the split failed.' %
(source, split_fraction, num_items_to_read_before_split))
elif expected_outcome == ExpectedSplitOutcome.MUST_FAIL:
if split_result:
raise ValueError(
'Expected split of source %r at fraction %r after '
'reading %d elements to fail. But splitting '
'succeeded with result %r.' % (
source,View on GitHub (pinned to 12126d8942)
Solutions
- Assign the new stop position to the tracker's own state inside try_split (self.stop_position = split_position).
- Ensure try_split mutates the same instance that get_range_tracker handed to read().
- Add thread-safety (locks) if try_split may be called from another thread, so the update isn't lost.
- Write a unit test asserting stop_position() changes after a successful try_split.
Example fix
# before
def try_split(self, fraction):
new_stop = self.position_at_fraction(fraction)
return (new_stop, fraction) # state never updated
# after
def try_split(self, fraction):
new_stop = self.position_at_fraction(fraction)
if new_stop is None:
return None
self._stop = new_stop
return (new_stop, 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() != before, "split claimed success but range unchanged" Type guard
def split_mutates_state(tracker, pos):
before = tracker.stop_position()
res = tracker.try_split(pos)
return res is None or tracker.stop_position() != before Try / catch
try:
source_test_utils.assert_split_at_fraction_behavior(src, n, f, outcome)
except ValueError as e:
if "did not change after a successful" in str(e):
logger.error("try_split returned result without updating state: %s", e)
raise Prevention
- Persist the shrunk range on the tracker instance inside try_split
- Make try_split thread-safe so updates are never lost
- Unit-test that stop_position() changes after every successful split
When it happens
Trigger: _assert_split_at_fraction_behavior compares stop_position() before and after try_split; split_result is truthy but stop_position_after_split == stop_position_before_split, e.g. try_split computes and returns (pos, fraction) but writes the new stop to a local variable or a different tracker instance.
Common situations: Custom RangeTracker implementations with immutably-copied state, thread-unsafe updates lost on return, or implementations that forget to persist the shrunk range on self.
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
- After a successful split, the stop position of the RangeTrac
- 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/1b46ac719e723e10.
Report an issue: GitHub.