Problem:
When multiple monitors are removed from the monmap in a single update,
the iterative rank adjustment in notify_rank_removed() fails to maintain
the invariant that our local rank matches the monmap's new_rank until
all iterations complete.
Problem:
The old code decremented rank for each removed rank:
```
for (auto i = monmap->removed_ranks.rbegin(); ...) {
int remove_rank = *i;
int new_rank = monmap->get_rank(messenger->get_myaddrs());
// Old logic (in ConnectionTracker):
if (remove_rank < rank) {
--rank;
}
// This assert would fail on first iteration when removed_ranks.size() > 1
ceph_assert(rank == new_rank);
}
```
When removed_ranks = {0, 1} and we are mon.d (originally rank 3):
- new_rank from monmap = 1 (final correct value after removing 0 and 1)
Iteration 1 (remove rank 1):
Before: rank = 3
Execute: rank_removed=1 < 3, so --rank -> rank = 2
Check: rank == new_rank? -> 2 == 1? FAIL
Iteration 2 (remove rank 0):
Before: rank = 2
Execute: rank_removed=0 < 2, so --rank -> rank = 1
Check: rank == new_rank? → 1 == 1? PASS
The invariant is violated during intermediate iterations because each
--rank adjustment only accounts for ONE removed rank, but new_rank
from monmap already accounts for ALL removed ranks.
Solution:
Trust the caller's new_rank from monmap instead of trying to compute it
iteratively. The monmap has already recalculated ranks correctly by
removing all monitors atomically, so we should just use that value.
Changed:
```
if (rank_removed < rank) {
--rank;
}
```
To:
// Trust the caller's new_rank from the monmap rather than trying to compute it.
// This handles cases where multiple ranks are removed simultaneously.
rank = new_rank;
Now the invariant holds on every iteration regardless of how many ranks
are removed.
Fixes: https://tracker.ceph.com/issues/77423
Signed-off-by: Kamoltat (Junior) Sirivadhna <ksirivad@redhat.com>
ceph_assert((peer_reports.size() == starting_size) ||
(peer_reports.size() + 1 == starting_size));
- if (rank_removed < rank) { // if the rank removed is lower than us, we need to adjust.
- --rank;
- my_reports.rank = rank; // also adjust my_reports.rank.
- }
+ // Trust the caller's new_rank from the monmap rather than trying to compute it.
+ // This handles cases where multiple ranks are removed simultaneously.
+ rank = new_rank;
+ my_reports.rank = new_rank;
ldout(cct, 20) << "my rank after: " << rank << dendl;
ldout(cct, 20) << "peer_reports after: " << peer_reports << dendl;
ldout(cct, 20) << "my_reports after: " << my_reports << dendl;
- //check if the new_rank from monmap is equal to our adjusted rank.
- ceph_assert(rank == new_rank);
-
increase_version();
}