1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.regionserver;
20
21 import java.text.SimpleDateFormat;
22 import java.util.Date;
23 import java.util.Map;
24 import java.util.concurrent.ConcurrentHashMap;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.hadoop.conf.Configuration;
29 import org.apache.hadoop.hbase.HConstants;
30 import org.apache.hadoop.hbase.ScheduledChore;
31 import org.apache.hadoop.hbase.Stoppable;
32 import org.apache.hadoop.hbase.classification.InterfaceAudience;
33 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
34 import org.apache.hadoop.hbase.util.NonceKey;
35
36 import com.google.common.annotations.VisibleForTesting;
37
38
39
40
41
42 @InterfaceAudience.Private
43 public class ServerNonceManager {
44 public static final String HASH_NONCE_GRACE_PERIOD_KEY = "hbase.server.hashNonce.gracePeriod";
45 private static final Log LOG = LogFactory.getLog(ServerNonceManager.class);
46
47
48
49 private int conflictWaitIterationMs = 30000;
50
51 private static final SimpleDateFormat tsFormat = new SimpleDateFormat("HH:mm:ss.SSS");
52
53
54 private static class OperationContext {
55 static final int DONT_PROCEED = 0;
56 static final int PROCEED = 1;
57 static final int WAIT = 2;
58
59
60 private long data = 0;
61 private static final long STATE_BITS = 3;
62 private static final long WAITING_BIT = 4;
63 private static final long ALL_FLAG_BITS = WAITING_BIT | STATE_BITS;
64
65 private volatile long mvcc;
66
67 @Override
68 public String toString() {
69 return "[state " + getState() + ", hasWait " + hasWait() + ", activity "
70 + tsFormat.format(new Date(getActivityTime())) + "]";
71 }
72
73 public OperationContext() {
74 setState(WAIT);
75 reportActivity();
76 }
77
78 public void setState(int state) {
79 this.data = (this.data & ~STATE_BITS) | state;
80 }
81
82 public int getState() {
83 return (int)(this.data & STATE_BITS);
84 }
85
86 public void setHasWait() {
87 this.data = this.data | WAITING_BIT;
88 }
89
90 public boolean hasWait() {
91 return (this.data & WAITING_BIT) == WAITING_BIT;
92 }
93
94 public void reportActivity() {
95 long now = EnvironmentEdgeManager.currentTime();
96 this.data = (this.data & ALL_FLAG_BITS) | (now << 3);
97 }
98
99 public boolean isExpired(long minRelevantTime) {
100 return getActivityTime() < (minRelevantTime & (~0l >>> 3));
101 }
102
103 private long getActivityTime() {
104 return this.data >>> 3;
105 }
106
107 public void setMvcc(long mvcc) {
108 this.mvcc = mvcc;
109 }
110
111 public long getMvcc() {
112 return this.mvcc;
113 }
114 }
115
116
117
118
119
120
121
122
123
124 private ConcurrentHashMap<NonceKey, OperationContext> nonces =
125 new ConcurrentHashMap<NonceKey, OperationContext>();
126
127 private int deleteNonceGracePeriod;
128
129 public ServerNonceManager(Configuration conf) {
130
131 deleteNonceGracePeriod = conf.getInt(HASH_NONCE_GRACE_PERIOD_KEY, 30 * 60 * 1000);
132 if (deleteNonceGracePeriod < 60 * 1000) {
133 LOG.warn("Nonce grace period " + deleteNonceGracePeriod
134 + " is less than a minute; might be too small to be useful");
135 }
136 }
137
138 @VisibleForTesting
139 public void setConflictWaitIterationMs(int conflictWaitIterationMs) {
140 this.conflictWaitIterationMs = conflictWaitIterationMs;
141 }
142
143
144
145
146
147
148
149
150
151 public boolean startOperation(long group, long nonce, Stoppable stoppable)
152 throws InterruptedException {
153 if (nonce == HConstants.NO_NONCE) return true;
154 NonceKey nk = new NonceKey(group, nonce);
155 OperationContext ctx = new OperationContext();
156 while (true) {
157 OperationContext oldResult = nonces.putIfAbsent(nk, ctx);
158 if (oldResult == null) return true;
159
160
161 synchronized (oldResult) {
162 int oldState = oldResult.getState();
163 LOG.debug("Conflict detected by nonce: " + nk + ", " + oldResult);
164 if (oldState != OperationContext.WAIT) {
165 return oldState == OperationContext.PROCEED;
166 }
167 oldResult.setHasWait();
168 oldResult.wait(this.conflictWaitIterationMs);
169 if (stoppable.isStopped()) {
170 throw new InterruptedException("Server stopped");
171 }
172 }
173 }
174 }
175
176
177
178
179
180
181
182 public void endOperation(long group, long nonce, boolean success) {
183 if (nonce == HConstants.NO_NONCE) return;
184 NonceKey nk = new NonceKey(group, nonce);
185 OperationContext newResult = nonces.get(nk);
186 assert newResult != null;
187 synchronized (newResult) {
188 assert newResult.getState() == OperationContext.WAIT;
189
190 newResult.setState(success ? OperationContext.DONT_PROCEED : OperationContext.PROCEED);
191 if (success) {
192 newResult.reportActivity();
193 } else {
194 OperationContext val = nonces.remove(nk);
195 assert val == newResult;
196 }
197 if (newResult.hasWait()) {
198 LOG.debug("Conflict with running op ended: " + nk + ", " + newResult);
199 newResult.notifyAll();
200 }
201 }
202 }
203
204
205
206
207
208
209
210 public void addMvccToOperationContext(long group, long nonce, long mvcc) {
211 if (nonce == HConstants.NO_NONCE) {
212 return;
213 }
214 NonceKey nk = new NonceKey(group, nonce);
215 OperationContext result = nonces.get(nk);
216 assert result != null;
217 synchronized (result) {
218 result.setMvcc(mvcc);
219 }
220 }
221
222
223
224
225
226
227
228 public long getMvccFromOperationContext(long group, long nonce) {
229 if (nonce == HConstants.NO_NONCE) {
230 return Long.MAX_VALUE;
231 }
232 NonceKey nk = new NonceKey(group, nonce);
233 OperationContext result = nonces.get(nk);
234 return result == null ? Long.MAX_VALUE : result.getMvcc();
235 }
236
237
238
239
240
241
242
243 public void reportOperationFromWal(long group, long nonce, long writeTime) {
244 if (nonce == HConstants.NO_NONCE) return;
245
246 long now = EnvironmentEdgeManager.currentTime();
247 if (now > writeTime + (deleteNonceGracePeriod * 1.5)) return;
248 OperationContext newResult = new OperationContext();
249 newResult.setState(OperationContext.DONT_PROCEED);
250 NonceKey nk = new NonceKey(group, nonce);
251 OperationContext oldResult = nonces.putIfAbsent(nk, newResult);
252 if (oldResult != null) {
253
254
255 LOG.warn("Nonce collision during WAL recovery: " + nk
256 + ", " + oldResult + " with " + newResult);
257 }
258 }
259
260
261
262
263
264
265 public ScheduledChore createCleanupScheduledChore(Stoppable stoppable) {
266
267 return new ScheduledChore("nonceCleaner", stoppable, deleteNonceGracePeriod / 5) {
268 @Override
269 protected void chore() {
270 cleanUpOldNonces();
271 }
272 };
273 }
274
275 private void cleanUpOldNonces() {
276 long cutoff = EnvironmentEdgeManager.currentTime() - deleteNonceGracePeriod;
277 for (Map.Entry<NonceKey, OperationContext> entry : nonces.entrySet()) {
278 OperationContext oc = entry.getValue();
279 if (!oc.isExpired(cutoff)) continue;
280 synchronized (oc) {
281 if (oc.getState() == OperationContext.WAIT || !oc.isExpired(cutoff)) continue;
282 nonces.remove(entry.getKey());
283 }
284 }
285 }
286 }