Skip to content

Conversation

@eramongodb
Copy link
Contributor

@eramongodb eramongodb commented Dec 9, 2025

Resolves CXX-3237 and CXX-3238 for the v1::change_stream component.

This component was tricky to deal with due to having upwards of three distinct states being tracked by both the change stream object and its iterators, despite the iterator state supposedly intended to be tracked by the associated change stream object (hence the "shared state" of associated iterators):

  • v_noabi::change_stream::impl::status_: one of k_pending, k_started, and k_dead.
  • v_noabi::change_stream::impl::exhausted_: bool.
  • v_noabi::change_stream::iterator::_type: one of k_tracking, k_default_constructed, and k_end.

After careful examination and testing, the v1 implementation narrows the state space down to only the following three exclusive states, which are shared by all associated objects (there is no state exclusive to iterators):

  • is_active: the change stream (iterator) has an event document available.
  • is_resumable: the change stream (iterator) has no event document available.
    • The term "resumable" is used to avoid conflating "exhaust" with that "exhaust cursors", which are similar in behavior but unrelated to change streams.
  • is_dead: the change stream (iterator) encounter an irrecoverable error.

Compare the differences in v1 and v_noabi's implementation of advance_iterator() and iterator::operator==() for further details, where the consequences of these changes are most clearly visible.

An important difference between v1 and v_noabi behavior to highlight is that consecutive calls to .begin() do advance the shared iterator state in v1, whereas v_noabi carves a special exception to permit consecutive calls to .begin() to leave the shared iterator state unchanged until the next explicit operator++. This exception is not inherited by the v1 API due to being inconsistent with similar stateful iterator API (that is, calling .begin() don't usually have an "unless" in their advance-the-iterator behavior). Nevertheless, this behavior is preserved by the v_noabi API for backward compatibility.

Additional notes:

  • Despite v1::change_stream::iterator documenting the moved-from state as assign-or-destroy-only, the implementation is trivial (equivalent to copy). This is intentional to leave open the possibility of changing _impl to have its own class impl; state rather than completely depending on the associated v1::change_stream object. If this is YAGNI, we can make all the SMFs implicitly trivial instead (Rule of Zero), but left as-is for now.
  • There are some unfortunate const_cast required to preserve API backward compatibility with the incorrect declaration of begin() const, which does modify the change_stream object. The v1 API fixes this declaration, but consequently, v_noabi must accomodate for this fix in its refactored implementation. Relevant incorrect-const workarounds are labeled with descriptive comments.
  • The v_noabi implementation still requires the iterator-specific _is_end state to preserve consecutive-.begin()-does-not-advance behavior.
  • As part of state simplification, all v1 end iterators (default-constructed or resumable) compare equal to one another, whereas v_noabi iterators treat default-constructed and resumable iterators as unequal.
  • Unclear why scan-build only warns about a potential null pointer dereference in v_noabi::change_stream::iterator::is_exhausted() and not anywhere else not-null is assumed.

@eramongodb eramongodb self-assigned this Dec 9, 2025
@eramongodb eramongodb requested a review from a team as a code owner December 9, 2025 18:45
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