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

Valery Kharseko
2 days ago 80481f756d71bd58b4bda627758dcd774e9d5dcd
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * 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);
 
  SessionRestartRequests()
  {
    /*
     * Every request is made on a replay-failure road, where an allocation may be what has
     * just failed, and the first execution of merge() in a JVM allocates: the call site
     * of its lambda and the VarHandle site inside accumulateAndGet() are linked when they
     * are first run, and nothing runs them before a replay fails. Run once here, on a
     * thread which can allocate, so that a request made on the road out of an
     * OutOfMemoryError asks for nothing the JVM has just refused (issue #954). The same
     * goes for what takes a request, which is run on the same roads.
     */
    merge(SessionRestart.NONE);
    take();
  }
 
  /**
   * 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);
  }
}