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

Valery Kharseko
11 hours ago 5d176c691527e3fe4bc529ff8947f3b3648970bd
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/PersistentServerState.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.replication.plugin;
@@ -24,6 +25,8 @@
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;
@@ -55,6 +58,11 @@
   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.
@@ -104,12 +112,70 @@
  /**
   * 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();
    }
  }
@@ -118,6 +184,16 @@
   */
  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)
@@ -141,6 +217,11 @@
     * Inconsistencies may append after a crash.
     */
    checkAndUpdateServerState();
    if (hadCSNs)
    {
      state.setSaved(false);
    }
  }
  /**
@@ -262,9 +343,8 @@
    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)))
    {
@@ -274,20 +354,36 @@
  }
  /**
   * 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()
  {