| | |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2012-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | |
| | | import java.util.Collections; |
| | | import java.util.Iterator; |
| | | import java.util.LinkedList; |
| | | import java.util.concurrent.locks.Lock; |
| | | import java.util.concurrent.locks.ReentrantLock; |
| | | |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | |
| | | private final DN baseDN; |
| | | private final int serverId; |
| | | private final ServerState state; |
| | | /** |
| | | * Held by the save which is writing the state to the backend. It is taken |
| | | * with {@link Lock#tryLock()} and never waited for: see {@link #save()}. |
| | | */ |
| | | private final Lock saveLock = new ReentrantLock(); |
| | | |
| | | /** |
| | | * The attribute name used to store the state in the backend. |
| | |
| | | |
| | | /** |
| | | * Save this object to persistent storage. |
| | | * <p> |
| | | * Only one save writes the state at a time: two of them would otherwise each |
| | | * take their own snapshot, and the write of the older one landing last would |
| | | * leave a state on disk that is both stale and marked as saved. |
| | | * <p> |
| | | * A save which finds another one writing gives up its turn instead of waiting |
| | | * for it, and this method never blocks. Waiting would close a lock cycle: |
| | | * when the write goes to the domain configuration entry it ends up in |
| | | * {@code LDAPReplicationDomain.applyConfigurationChange()}, which takes the |
| | | * very lock {@code disable()} holds while calling this method. |
| | | * <p> |
| | | * Giving up the turn loses nothing, because the state is marked as saved |
| | | * before the snapshot of the write in flight is taken. Whatever this save |
| | | * would have written is therefore either already in that snapshot, or has |
| | | * cleared the flag again after it was set - in which case the flag is still |
| | | * clear when that write completes, and the next save writes it. |
| | | */ |
| | | public void save() |
| | | { |
| | | if (!state.isSaved()) |
| | | if (state.isSaved()) |
| | | { |
| | | state.setSaved(updateStateEntry()); |
| | | // Nothing to write: stay out of the way of whoever is writing. |
| | | return; |
| | | } |
| | | |
| | | if (!saveLock.tryLock()) |
| | | { |
| | | return; |
| | | } |
| | | try |
| | | { |
| | | if (state.isSaved()) |
| | | { |
| | | // The save which just completed carried what this one came to write. |
| | | return; |
| | | } |
| | | /* |
| | | * Mark the state as saved before the snapshot that goes to the backend |
| | | * is taken, so that an update landing while the write is in flight |
| | | * clears the flag again and gets written by the next save. Marking it |
| | | * afterwards would swallow such an update: it is not part of the write |
| | | * it raced with, yet the state would look saved. The persisted state |
| | | * would then stay stale until some later update happened to dirty it |
| | | * again - which, on a domain as quiet as cn=schema, may never happen. |
| | | */ |
| | | state.setSaved(true); |
| | | boolean written = false; |
| | | try |
| | | { |
| | | written = updateStateEntry(); |
| | | } |
| | | finally |
| | | { |
| | | if (!written) |
| | | { |
| | | // The write reported a failure, or blew up on its way to the |
| | | // backend: the state is not on disk, so leave it to the next save. |
| | | state.setSaved(false); |
| | | } |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | saveLock.unlock(); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | public void loadState() |
| | | { |
| | | /* |
| | | * Whatever the state holds on the way in has, as far as this object knows, |
| | | * never been written: what follows only merges in what the backend holds. |
| | | * No shipped path comes in holding anything - the constructor is handed the |
| | | * state a ReplicationDomain has just created, and loadDataState() empties |
| | | * it first - so this guards a caller which does not exist yet rather than |
| | | * one which does. |
| | | */ |
| | | final boolean hadCSNs = !state.isEmpty(); |
| | | |
| | | // try to load the state from the base entry. |
| | | SearchResultEntry stateEntry = searchBaseEntry(); |
| | | if (stateEntry == null) |
| | |
| | | * Inconsistencies may append after a crash. |
| | | */ |
| | | checkAndUpdateServerState(); |
| | | |
| | | if (hadCSNs) |
| | | { |
| | | state.setSaved(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | op.setInternalOperation(true); |
| | | op.setSynchronizationOperation(true); |
| | | op.setDontSynchronize(true); |
| | | op.run(); |
| | | |
| | | final ResultCode resultCode = op.getResultCode(); |
| | | final ResultCode resultCode = runModify(op); |
| | | if (resultCode != ResultCode.SUCCESS |
| | | && !(resultCode == ResultCode.NO_SUCH_OBJECT && serverStateEntryDN.equals(baseDN))) |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Empty the ServerState. |
| | | * After this call the Server State will be in the same state |
| | | * as if it was just created. |
| | | * Runs the modify operation that writes the state to the backend. |
| | | * <p> |
| | | * Kept separate, and overridable, so that a test can reach the point where |
| | | * the snapshot has been taken but the write has not gone through yet: see |
| | | * {@code PersistentServerStateTest}. Do not inline it. |
| | | * |
| | | * @param op The modify operation carrying the state to be written. |
| | | * @return A ResultCode indicating if the operation was successful. |
| | | */ |
| | | ResultCode runModify(ModifyOperationBasis op) |
| | | { |
| | | op.run(); |
| | | return op.getResultCode(); |
| | | } |
| | | |
| | | /** |
| | | * Empty the ServerState in memory. |
| | | * <p> |
| | | * The emptied state is marked as not saved, so the next save writes the empty |
| | | * state out - which is what {@link #clear()} is after. A caller that only |
| | | * means to drop the in-memory copy, and expects the backend to keep what it |
| | | * holds, has to keep saves away until it has loaded the state back. |
| | | */ |
| | | public void clearInMemory() |
| | | { |
| | | state.clear(); |
| | | state.setSaved(false); |
| | | } |
| | | |
| | | /** |
| | | * Empty the ServerState. |
| | | * After this call the Server State will be in the same state |
| | | * as if it was just created. |
| | | * Empty the ServerState and write the emptied state to persistent storage. |
| | | */ |
| | | void clear() |
| | | { |