Skip to content

[#1029] Send a directory server only the updates it gives send-window credit for - #1034

Open
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/1029-replica-offline-msg-send-window-permit
Open

[#1029] Send a directory server only the updates it gives send-window credit for#1034
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/1029-replica-offline-msg-send-window-permit

Conversation

@vharseko

Copy link
Copy Markdown
Member

Fixes #1029

The accounting

The replication server takes a permit of the session's send window for every message it hands to
the writer (ServerHandler.take(), :991-994), and the only release is the WindowMsg the
directory server sends (updateWindow(), :1103). The directory server gives that credit for the
updates it replays alone: its listener calls processUpdateDone() - the one road to
updateWindowAfterReplay() - only when contributesToDomainState() holds
(ReplicationDomain.java:3264-3273). A ReplicaOfflineMsg never does
(ReplicaOfflineMsg.java:104-107), so one sent to a directory server costs the session a permit
for good.

ReplicationServerDomain.put() never queues that message for a directory server
(isUpdateMsgFiltered(), :498), but the catch-up path reads the changelog, where the cursor of a
replica which went offline synthesizes one from its offline CSN (FileChangelogDB.java:843-853,
ReplicaCursor.java:107-131), and MessageHandler.updateServerState() (:659-665) returns
!serverState.cover(csn) for it without moving the state - so every catch-up round, a hundred
records at most (:449), read the same message again, one permit each.

Two things the issue does not say:

  • the session stalls after losing more than half the window, not all of it. The directory
    server sends a WindowMsg once fifty replays have accumulated (halfRcvWindow,
    ReplicationBroker.java:2708); with fewer than fifty permits left the replication server cannot
    send enough to get there, the semaphore stays at zero and the writer sits in
    acquirePermitInSendWindow() until the session is closed. Some five thousand changes of
    catch-up past one offline replica, fewer with large entries (a round is also capped at 50 KB).
    Nothing is logged; only a reconnect - a new handler, a new semaphore - gets it moving.
  • it is not only the long catch-up. Every directory server which connects while a replica is
    offline is sent the message once, even an up-to-date one: its first round holds nothing but the
    offline message, and the !queueContributesToDomainState branch of getNextMessage()
    (MessageHandler.java:329-346) returns it. Only directory-server sessions leak: a peer
    replication server credits every UpdateMsg it receives (ServerHandler.put()
    decAndCheckWindow(), :1243-1246).

The change

DataServerHandler overrides updateServerState() and returns false for any update which does
not contribute to the domain state: the same condition the directory server credits on, so the
two sides of the accounting agree by construction. That method is the gate all three return paths
of getNextMessage() go through, and it runs ahead of take() and its permit. Of the three
places the issue lists, this is the first; the writer's own filter (ServerWriter.java:108) is
after take(), so a filter placed there - the letter of option 1 - would leak exactly the
same way, and option 2 would break the symmetry with a peer replication server, which credits
every message it is sent.

The comment #947 left in ServerWriter described the delivery this removes and is reworded; the
!handler.isDataServer() guards stay, since they say whose forward the shutdown counts.

Tests

  • ReplicaOfflineMsgCatchUpTest - a replica publishes a change and its ReplicaOfflineMsg and
    leaves; a directory server connects and catches up; a third replica publishes a change, and once
    the directory server holds that one nothing is left to account for. The send window is read from
    current-send-window on the monitor entry of the handler, and the broker of the test never gives
    credit, so the window is the announced size less the messages sent.
    • aDirectoryServerBehindTheOfflineReplicaIsSentItsChangesButNotItsOfflineMessage - the round
      holds the change and the offline message. On master: current-send-window 97 where 98 is
      expected, the directory server received [DeleteMsg, ReplicaOfflineMsg].
    • aDirectoryServerUpToDateWithTheOfflineReplicaIsNotSentItsOfflineMessage - the directory
      server announces a state holding the change, so the round holds nothing but the offline
      message (the other branch of getNextMessage()). On master: 98 where 99 is expected, the
      directory server received [ReplicaOfflineMsg].
  • ReplicationServerShutdownSyncTest.theForwardToADirectoryServerDoesNotEndTheWait ([#917] Wait for every peer replication server to forward the ReplicaOfflineMsg #947)
    asserted that the directory server receives the message queued for its handler, which is the
    delivery this PR removes. Renamed theDirectoryServerIsNeitherSentTheMessageNorEndsTheWait: a
    change is queued behind the message, the directory server receives the change and not the
    message, and the shutdown still waits the grace period. Red on master (the message was received).
  • ReplicationTestCase.receiveUntil() - receives up to a given CSN and returns what came before,
    so that what a broker was not sent can be asserted without waiting out a timeout.

Locally, with the fix: org/opends/server/replication/server/**, ReSyncTest, StateMachineTest,
UpdateOperationTest - 1862 tests, no failures.

Not in this PR

ServerWriter.isUpdateMsgFiltered() drops updates for a directory server in BAD_GEN_ID or
FULL_UPDATE status after take() charged the permit as well. Harmless today - both statuses end
in a reconnect, which brings a new handler and a new semaphore - and left alone here.

#964 (approved, open) edits the wait loop of getNextMessage() and adds isFedByTheDomain() to
DataServerHandler just before isDataServer(); the override here goes just after it. Adjacent
hunks, no shared lines - this branch is cut from master, whichever lands first the other rebases
cleanly.

…it gives send-window credit for

The replication server takes a permit of the session's send window for every
message it hands to the writer (ServerHandler.take()), and a directory server
gives credit only for the updates it replays: its listener calls
processUpdateDone() - the one road to updateWindowAfterReplay() - for the
messages which contribute to the domain state alone. A ReplicaOfflineMsg does
not, so one sent to a directory server cost the session a permit for good.

ReplicationServerDomain.put() never queues that message for a directory
server, but the catch-up path reads the changelog, where the cursor of a
replica which went offline synthesizes one from its offline CSN, and
MessageHandler.updateServerState() does not move the state of the handler past
an offline CSN, so every catch-up round - a hundred records at most - read the
same message again, one permit each. A directory server catching up some five
thousand changes past one offline replica lost more than half its window; the
directory server then never accumulated the fifty replays which earn a
WindowMsg, the semaphore stayed at zero and the writer sat in
acquirePermitInSendWindow() until the session was closed. Nothing was logged.

DataServerHandler now overrides updateServerState() to drop any update which
does not contribute to the domain state before getNextMessage() returns it -
the gate all three of its return paths go through, and the one place ahead of
take() and its permit. The writer's own filter runs after the permit is taken,
so a filter there would have leaked the same way.

ReplicationServerShutdownSyncTest.theForwardToADirectoryServerDoesNotEndTheWait
asserted the delivery this removes; it now asserts that a directory server is
not sent the message queued for it and that the shutdown still waits.
ReplicaOfflineMsgCatchUpTest pins the accounting on both catch-up branches
through current-send-window on the handler: 97 and 98 on master where 98 and
99 are expected, the missing permit being the offline message each directory
server was sent.
@vharseko vharseko added bug replication concurrency Thread-safety / race-condition bugs java Changes to Java sources tests Test suites: fixing, enabling, un-disabling labels Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug concurrency Thread-safety / race-condition bugs java Changes to Java sources replication tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A ReplicaOfflineMsg sent to a directory server on the catch-up path takes a send-window permit the session never gets back

1 participant