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

Valery Kharseko
yesterday 129fc4e34608d8212e62435f07283132e76ee1d6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
 * 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.
 * <p>
 * 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.
 * <p>
 * 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<SessionRestart> 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.
   * <p>
   * 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.
   * <p>
   * 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);
  }
}