/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2014-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.server.replication.service; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import java.util.Collection; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.forgerock.opendj.ldap.DN; import org.opends.server.replication.common.CSN; /** * Class useful for the case where DS/RS instances are collocated inside the * same JVM. It synchronizes the shutdown of the DS and RS sides. *
* More specifically, it ensures a ReplicaOfflineMsg sent by the DS is * relayed/forwarded by the collocated RS to the other RSs in the topology * before the whole process shuts down. *
* The state is kept per domain and per instance: the collocated DS and RS * sides coordinate through the single instance MultimasterReplication hands * to both of them. * * @since OPENDJ-1453 */ public class DSRSShutdownSync { /** * How long a ReplicaOfflineMsg may hold back the shutdown of the collocated * RS, in milliseconds, counted from the moment the message was announced. */ public static final long REPLICA_OFFLINE_GRACE_PERIOD = 5000; private final long gracePeriod; /** * The ReplicaOfflineMsg which is still owed a forward, per domain and per * replica of that domain. *
* An entry lives until every replication server the message was queued for * has forwarded it, so it legitimately holds a message some of them have * already sent: what is pending is the forward, not the message. *
* It is kept per domain because a domain sends this message whenever its * replication service is disabled - an online import, a restore, a * configuration change - and not only when the process shuts down. A single * entry for the whole process would be the one of the first such message and * would leave no grace period at all to the shutdown this class exists for. *
* It is kept per replica because the collocated RS relays the message of * every replica connected to it, and the forward of another replica's * message says nothing about this one. *
* Each entry knows the replication servers its message was queued for, because each of them is
* served by its own writer: the forward of one of them says nothing about the others, whose
* queue the shutdown is about to clear.
*/
private final ConcurrentMap
* The announcement comes before the message is published rather than after: a collocated
* replication server can forward the message as soon as it is on the wire, and a forward which
* finds nothing announced has nothing to clear. The announcement of a message the broker then
* refuses is taken back by {@link #replicaOfflineMsgNotSent(DN, CSN)}.
*
* A replica announces itself offline on every disableService(), so this may take the place of
* an earlier announcement of the same replica which is still owed its forward. The earlier one
* is kept behind the new one: a forward of the newer message, which the replication server
* queued behind the earlier one, covers both, and a withdrawal of the newer one gives the
* earlier one its wait back. It is kept only while its own grace period runs: past it, the
* announcement holds nothing back any more, and keeping it would chain every announcement of
* a replica whose message nobody in this process forwards - a directory server without a
* collocated replication server, or connected to a remote one - for the life of the process.
*
* @param baseDN
* the domain for which the message is being sent
* @param offlineCSN
* the CSN of the message, which identifies both the replica which announces itself
* offline and the announcement being waited for
*/
public void replicaOfflineMsgSent(DN baseDN, CSN offlineCSN)
{
final long announcedAt = System.nanoTime();
replicaOfflineMsgs
.computeIfAbsent(baseDN, dn -> new ConcurrentHashMap
* The announcement is made before the message is published, since a collocated replication
* server can forward it as soon as it is on the wire, so the announcement of a message the
* broker then refused has to be taken back: nobody will forward it, and the shutdown would
* spend the whole grace period waiting for that forward. Only the announcement carrying that
* CSN is withdrawn, and the announcement it displaced - an earlier message of the same replica
* which did go out and is still owed its forward - takes its place again.
*
* Whatever is reported about that earlier message while the announcement of the refused one
* stands in its place is not seen by it. A forward, or the loss of a peer it was queued for,
* is lost, and the shutdown then waits out what is left of the earlier message's own grace
* period; the peers it is queued for, if they are recorded in that window, are lost too, with
* the opposite effect - the first forward ends its wait, as for a message no peer was recorded
* for. That window is the one publish the broker refuses: at once on a connection error or a
* pending recovery, the broker's retry loop up to the reconnect when it has no session. The
* wait it can cost is bounded by a grace period which is already running.
*
* @param baseDN
* the domain for which the message was announced
* @param offlineCSN
* the CSN of the message which was not sent
*/
public void replicaOfflineMsgNotSent(DN baseDN, CSN offlineCSN)
{
final ConcurrentMap
* This must be called before the message is queued for any of them: a replication server can
* forward it as soon as it is in its queue, and a forward which finds no recipient recorded
* ends the wait at once.
*
* @param baseDN
* the domain for which the message has been sent
* @param offlineCSN
* the CSN of the message which is being queued
* @param replicationServerIds
* the server ids of the replication servers the message is being queued for
*/
public void replicaOfflineMsgDispatched(
DN baseDN, CSN offlineCSN, Collection
* Whatever it was given can no longer reach it, so the shutdown must not spend the rest of its
* grace period waiting for it.
*
* @param baseDN
* the domain the replication server is connected to
* @param replicationServerId
* the server id of the replication server which will not forward the message
*/
public void replicaOfflineMsgNotForwarded(DN baseDN, int replicationServerId)
{
final ConcurrentMap
* The shutdown itself blocks on {@link #awaitReplicaOfflineMsgsForwarded(Collection, long)}
* rather than polling this; it is the same state, observable without waiting for it.
*
* @param baseDN
* the baseDN of the domain being shut down
* @return true if the shutdown of this domain need not wait any longer, i.e. its message was
* forwarded or its grace period has expired, false otherwise
*/
public boolean canShutdown(DN baseDN)
{
return remainingGracePeriod(baseDN) <= 0;
}
/**
* Returns the time by which every wait of one shutdown must be over.
*
* A process shuts its domains down one after the other and each of them may have a message
* pending, so a deadline computed once and shared by all of them keeps the whole shutdown
* bounded by one grace period instead of one per domain.
*
* @return the point in time, on the {@link System#nanoTime()} clock, by which the waits must
* be over
*/
public long newShutdownDeadline()
{
return System.nanoTime() + MILLISECONDS.toNanos(gracePeriod);
}
/**
* Waits for the ReplicaOfflineMsg of every provided domain to be forwarded, or for their grace
* periods or the provided deadline to expire.
*
* This must be called before the server handlers of those domains are stopped: stopping them
* deactivates their consumer, clears their message queue and closes their session, after which
* the message can no longer be forwarded.
*
* All the domains of one shutdown wait together rather than one after the other, so that the
* shutdown is bounded by one grace period without the wait of one domain spending the grace
* period of the next.
*
* @param baseDNs
* the baseDNs of the domains whose messages must be forwarded
* @param deadline
* the point in time, on the {@link System#nanoTime()} clock, by which this wait must
* be over whatever the domains announce in the meantime - see
* {@link #newShutdownDeadline()}. A deadline which is not in the future returns
* without waiting at all, for a caller which has nothing to wait for.
*/
public void awaitReplicaOfflineMsgsForwarded(Collection
* This deliberately does not override {@code equals}: the two-argument
* {@link ConcurrentMap#remove(Object, Object)} of the forward guard must match the very
* announcement it read, not another one which happens to carry the same values.
*/
private static final class PendingOfflineMsg
{
/** The CSN of the message, so that the forward of an older one is not taken for this one. */
private final CSN csn;
/** When the message was announced, on the {@link System#nanoTime()} clock. */
private final long sentTime;
/**
* The announcement of the same replica this one took the place of and which is still owed its
* forward, null when there was none or when its grace period had already expired. It is
* given its place back if this message is withdrawn.
*/
private final PendingOfflineMsg displaced;
/**
* The replication servers the message was queued for and which have not forwarded it yet,
* null as long as it has not been queued for anybody.
*/
private volatile Set