Skip to content

[fix](editlog) Fix JournalObservable.upperBound() off-by-one in binary search#62065

Open
dataroaring wants to merge 1 commit intoapache:masterfrom
dataroaring:fix/journal-upperbound
Open

[fix](editlog) Fix JournalObservable.upperBound() off-by-one in binary search#62065
dataroaring wants to merge 1 commit intoapache:masterfrom
dataroaring:fix/journal-upperbound

Conversation

@dataroaring
Copy link
Copy Markdown
Contributor

Summary

  • The binary search uses `right = size - 1` and `right = middle - 1`, a lower_bound variant that can miss notifying observers at boundaries
  • When `size > 1` and the matching element is at the boundary, `right = middle - 1` can skip past it, returning an index that excludes observers that should have been notified
  • The post-loop fixup (`if (right == -1) return 0` and `value >= rightValue ? right + 1 : right`) is fragile and doesn't cover all edge cases
  • Fix: use the standard upper_bound pattern with `right = size` and `right = middle` (no `-1`), which correctly returns the count of elements whose `targetJournalVersion <= value`

Test plan

  • Verify followers waiting on specific journal versions are notified correctly
  • Verify `waitOn()` completes when the expected journal version is replayed

🤖 Generated with Claude Code

…y search

The binary search uses right = size - 1 and right = middle - 1, which is
a lower_bound variant that can miss notifying observers. Specifically, when
size == 1 and the single observer's targetJournalVersion <= journalId,
the function sets right = size - 1 = 0, then the post-loop check compares
value >= rightValue and returns right + 1 = 1. But when size > 1 and the
matching element is at the boundary, right = middle - 1 can skip past it,
returning an index that excludes observers that should be notified.

Fix: use the standard upper_bound pattern with right = size and
right = middle (no -1), which correctly returns the count of elements
whose targetJournalVersion <= value.
Copilot AI review requested due to automatic review settings April 2, 2026 14:29
@Thearas
Copy link
Copy Markdown
Contributor

Thearas commented Apr 2, 2026

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

Copy link
Copy Markdown

Copilot AI left a comment

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 off-by-one error in JournalObservable.upperBound() by switching to a standard upper_bound binary search variant, ensuring observers at boundary journal versions are correctly included.

Changes:

  • Replaced inclusive-right binary search (right = size - 1, right = middle - 1) with standard upper_bound (right = size, right = middle).
  • Removed fragile post-loop “fixup” logic and returned the computed insertion index directly.
  • Updated method comments to describe the intended upper_bound semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +66 to +67
// Return the number of elements whose targetJournalVersion <= value.
// This is the standard upper_bound pattern: returns the index of the first element > value.
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The updated comment explains the return semantics, but the method still relies on important preconditions that aren’t stated: (1) array[0..size) must be sorted ascending by targetJournalVersion, and (2) the first size entries must be non-null JournalObservers. Adding these assumptions to the comment/Javadoc would make the method safer to reuse and easier to reason about.

Suggested change
// Return the number of elements whose targetJournalVersion <= value.
// This is the standard upper_bound pattern: returns the index of the first element > value.
// Return the number of elements in array[0..size) whose targetJournalVersion <= value.
// This is the standard upper_bound pattern: returns the index of the first element > value.
// Preconditions:
// - The first 'size' elements of 'array' are non-null JournalObserver instances.
// - array[0..size) is sorted in ascending order by getTargetJournalVersion().

Copilot uses AI. Check for mistakes.
// return min pos which is bigger than value
// Return the number of elements whose targetJournalVersion <= value.
// This is the standard upper_bound pattern: returns the index of the first element > value.
public static int upperBound(Object[] array, int size, Long value) {
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The parameter name value is ambiguous for this domain (it represents a journal version threshold). Renaming it to something like journalVersion / targetJournalVersion would improve readability without affecting the method signature for callers.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to 77
if (((JournalObserver) array[middle]).getTargetJournalVersion() <= value) {
left = middle + 1;
} else {
right = middle - 1;
right = middle;
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Inside the loop, the cast and virtual call are embedded directly in the condition. Consider assigning (JournalObserver) array[middle] (and/or its targetJournalVersion) to a local variable for slightly clearer code and to avoid repeating complex expressions in hot paths.

Copilot uses AI. Check for mistakes.
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.

3 participants