/* * 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 2026 3A Systems, LLC. */ package org.opends.server.replication.plugin; import java.util.concurrent.atomic.AtomicReference; /** * The session restart a replication domain has been asked for and has not run yet. *

* A change which was released without being replayed is delivered again only over a new * session, so the thread which released it asks this domain for one. The request is what * the restart is run from, and it outlives the thread which made it: several threads * releasing changes at once are answered by one restart, and a restart which could not * run gives the request back rather than take it away with it - a change nobody asks for * again holds the ServerState of this domain back for as long as the server is up. *

* The wait a failing backend is owed belongs to the request rather than to the thread * which runs it. A replay which failed asks for the restart with the wait, and a replay * thread on its way out asks for it without - the backend is not what is going away - and * either thread can end up running what the other asked for: the one which is owed no * wait must not spend the wait the other's request was made with, and the one which is * owed the wait must not have its request run without it, whichever of the two runs it. */ class SessionRestartRequests { /** * What a domain has been asked to do with its session, from what is not being asked for * to what is asked for most insistently: {@link #merge(SessionRestart)} keeps the * furthest down this list, so that a request is never answered by less than it asked * for. */ enum SessionRestart { /** Nothing is being asked for: every request made has been run. */ NONE, /** The session is to be restarted as soon as a thread can run it. */ NOW, /** * The session is to be restarted once the backend has been left the time to recover * which the restarts made in a row have climbed to. */ AFTER_BACKOFF; } private final AtomicReference requested = new AtomicReference<>(SessionRestart.NONE); /** * Asks this domain to restart its session. * * @param restart what is being asked for, {@link SessionRestart#NONE} asking for * nothing */ void request(SessionRestart restart) { merge(restart); } /** * Takes the request which is standing, so that the caller runs it. *

* It is taken before the restart is run rather than once it has run: a change released * while the restart was under way is not one that restart asks for - its delivery would * have been turned down as a duplicate of a change a replay thread still owned - so the * request it makes must outlive the restart which was already running. * * @return what is being asked for, {@link SessionRestart#NONE} when nothing is */ SessionRestart take() { return requested.getAndSet(SessionRestart.NONE); } /** * Asks again for a restart which was taken and could not be run. *

* What is asked for again is not what {@link #take()} returned: the caller asks for the * restart with the backoff whether or not the request it took was made with one, since * a session which could not be started is the very thing that wait is for. A request * made while the restart was running is not undone by it - the two are merged, and the * one which asks for more wins. * * @param restart what the caller which could not run the restart asks for again */ void giveBack(SessionRestart restart) { merge(restart); } /** Forgets what this domain was asked for, its pending changes being gone with it. */ void clear() { requested.set(SessionRestart.NONE); } /** * Returns whether a restart is being asked for. * * @return {@code true} when a restart has been asked for and not run yet */ boolean isPending() { return requested.get() != SessionRestart.NONE; } private void merge(SessionRestart restart) { requested.accumulateAndGet(restart, (standing, asked) -> standing.compareTo(asked) >= 0 ? standing : asked); } }