mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
39 mins ago d27c892c81f6df051f737f54298cbdb530881d35
refs
author Valery Kharseko <vharseko@3a-systems.ru>
Friday, October 30, 2026 14:18 +0100
committer GitHub <noreply@github.com>
Friday, October 30, 2026 14:18 +0100
commitd27c892c81f6df051f737f54298cbdb530881d35
tree 9c5a3cbc68b4bdee0f3ec280eb22e1618ae64161 tree | zip | gz
parent ba575e4f45f790aea97a4d1bd3fae7f954bfed6c view | diff
[#1252] Go back to the changelog when the queue of a following handler dropped changes while its writer waited on it (#1253)

Fixes #1252

## Problem

A replication server can skip a run of changes for good, whether it
sends them to a peer replication server or to a directory server.

`docker-convergence (rs534, none)` of run
[40832432665](https://github.com/OpenIdentityPlatform/OpenDJ/actions/runs/40832432665)
showed it with nothing disturbing the topology. The changelog of `cv-r0`
lacked 218 changes of `cv-d1` which `cv-r1` held. The directory servers
behind `cv-r0` kept four entries the others had deleted. #1252 has the
analysis of that run.

The writer of a handler which follows its in-memory queue waits on the
queue while it is empty, and gives up the monitor of the queue while it
waits (`MessageHandler.getNextMessage()`):
1. More changes than the queue holds can arrive before the writer has
the monitor back. `MessageHandler.add()` then drops the oldest of them
and sets `following` to false.
2. Back from the wait, the writer looked only at whether the queue was
empty. It took the head of the queue, the first change after the dropped
ones, and `updateServerState()` moved the state of the consumer past
them.
3. The read of the changelog the handler went back to then started after
the dropped changes, and `consumerHasWhatTheQueueLacks()` found them
covered by that state. They were never sent.

The `removeFirst()` right after the wait predates #963, which only added
the `continue` on an empty queue. `DataServerHandler` runs the same
code.

## Fix

Back from the wait, the writer goes back to the changelog when the
handler stopped following, as it already did when the queue was empty.
`fillLateQueue()` reads from the state of the consumer, which does not
hold the dropped changes, and `queueLacksUpTo` keeps the handler off the
queue until the consumer has them (#1206).

## Tests


`MessageHandlerCatchUpTest.changesTheQueueDroppedWhileTheWriterWaitedOnItAreSent`:
- The handler has a queue of 10 and an empty changelog. Its writer finds
nothing to catch up on, follows the queue and waits on it.
- The test holds the monitor of the queue while 15 changes arrive
(written to the changelog, then handed to the queue), then lets the
writer go.
- The consumer must be sent all 15, in order.

Nothing else holds the writer off the monitor on cue, so the test takes
the monitor itself, through the private field of the queue. It checks
that the queue did stop following, so the case cannot pass without the
queue having dropped changes.

## Verification

- **Without the fix:** the new case fails. The consumer is sent seq 6 to
15, and the 5 changes the queue dropped are never sent, as in the CI
run. The 12 other cases of `MessageHandlerCatchUpTest` and the 8 of
`MissedUpdateRecoveryTest` pass.
- **With the fix:** 13 of 13 and 8 of 8 pass.
2 files modified
88 ■■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/replication/server/MessageHandler.java 11 ●●●●● diff | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/replication/server/MessageHandlerCatchUpTest.java 77 ●●●●● diff | view | raw | blame | history