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 com.google.common.annotations.VisibleForTesting;
22 import com.google.common.base.Objects;
23
24 import java.util.LinkedList;
25 import java.util.concurrent.atomic.AtomicLong;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.hadoop.hbase.classification.InterfaceAudience;
30 import org.apache.hadoop.hbase.util.Bytes;
31 import org.apache.hadoop.hbase.util.ClassSize;
32
33
34
35
36
37
38
39 @InterfaceAudience.Private
40 public class MultiVersionConcurrencyControl {
41 private static final Log LOG = LogFactory.getLog(MultiVersionConcurrencyControl.class);
42 static final long NO_WRITE_NUMBER = 0;
43
44 final AtomicLong readPoint = new AtomicLong(0);
45 final AtomicLong writePoint = new AtomicLong(0);
46 private final Object readWaiters = new Object();
47
48
49
50 public static final long NONE = -1;
51
52
53
54
55
56
57
58 private final LinkedList<WriteEntry> writeQueue = new LinkedList<WriteEntry>();
59
60 public MultiVersionConcurrencyControl() {
61 super();
62 }
63
64
65
66
67 public MultiVersionConcurrencyControl(long startPoint) {
68 tryAdvanceTo(startPoint, NONE);
69 }
70
71
72
73
74
75 public void advanceTo(long newStartPoint) {
76 while (true) {
77 long seqId = this.getWritePoint();
78 if (seqId >= newStartPoint) break;
79 if (this.tryAdvanceTo(newStartPoint, seqId)) break;
80 }
81 }
82
83
84
85
86
87
88
89
90
91 boolean tryAdvanceTo(long newStartPoint, long expected) {
92 synchronized (writeQueue) {
93 long currentRead = this.readPoint.get();
94 long currentWrite = this.writePoint.get();
95 if (currentRead != currentWrite) {
96 throw new RuntimeException("Already used this mvcc; currentRead=" + currentRead +
97 ", currentWrite=" + currentWrite + "; too late to tryAdvanceTo");
98 }
99 if (expected != NONE && expected != currentRead) {
100 return false;
101 }
102
103 if (newStartPoint < currentRead) {
104 return false;
105 }
106
107 readPoint.set(newStartPoint);
108 writePoint.set(newStartPoint);
109 }
110 return true;
111 }
112
113
114
115
116 public WriteEntry begin() {
117 return begin(new Runnable() {
118 @Override public void run() {
119
120 }
121 });
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public WriteEntry begin(Runnable action) {
137 synchronized (writeQueue) {
138 long nextWriteNumber = writePoint.incrementAndGet();
139 WriteEntry e = new WriteEntry(nextWriteNumber);
140 writeQueue.add(e);
141 action.run();
142 return e;
143 }
144 }
145
146
147
148
149
150 public void await() {
151
152 completeAndWait(begin());
153 }
154
155
156
157
158
159
160
161
162 public void completeAndWait(WriteEntry e) {
163 if (!complete(e)) {
164 waitForRead(e);
165 }
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182 public boolean complete(WriteEntry writeEntry) {
183 synchronized (writeQueue) {
184 writeEntry.markCompleted();
185
186 long nextReadValue = NONE;
187 boolean ranOnce = false;
188 while (!writeQueue.isEmpty()) {
189 ranOnce = true;
190 WriteEntry queueFirst = writeQueue.getFirst();
191
192 if (nextReadValue > 0) {
193 if (nextReadValue + 1 != queueFirst.getWriteNumber()) {
194 throw new RuntimeException("Invariant in complete violated, nextReadValue="
195 + nextReadValue + ", writeNumber=" + queueFirst.getWriteNumber());
196 }
197 }
198
199 if (queueFirst.isCompleted()) {
200 nextReadValue = queueFirst.getWriteNumber();
201 writeQueue.removeFirst();
202 } else {
203 break;
204 }
205 }
206
207 if (!ranOnce) {
208 throw new RuntimeException("There is no first!");
209 }
210
211 if (nextReadValue > 0) {
212 synchronized (readWaiters) {
213 readPoint.set(nextReadValue);
214 readWaiters.notifyAll();
215 }
216 }
217 return readPoint.get() >= writeEntry.getWriteNumber();
218 }
219 }
220
221
222
223
224 void waitForRead(WriteEntry e) {
225 boolean interrupted = false;
226 int count = 0;
227 synchronized (readWaiters) {
228 while (readPoint.get() < e.getWriteNumber()) {
229 if (count % 100 == 0 && count > 0) {
230 LOG.warn("STUCK: " + this);
231 }
232 count++;
233 try {
234 readWaiters.wait(10);
235 } catch (InterruptedException ie) {
236
237
238 interrupted = true;
239 }
240 }
241 }
242 if (interrupted) {
243 Thread.currentThread().interrupt();
244 }
245 }
246
247 @VisibleForTesting
248 @Override
249 public String toString() {
250 return Objects.toStringHelper(this)
251 .add("readPoint", readPoint)
252 .add("writePoint", writePoint).toString();
253 }
254
255 public long getReadPoint() {
256 return readPoint.get();
257 }
258
259 @VisibleForTesting
260 public long getWritePoint() {
261 return writePoint.get();
262 }
263
264
265
266
267
268 @InterfaceAudience.Private
269 public static class WriteEntry {
270 private final long writeNumber;
271 private boolean completed = false;
272
273 WriteEntry(long writeNumber) {
274 this.writeNumber = writeNumber;
275 }
276
277 void markCompleted() {
278 this.completed = true;
279 }
280
281 boolean isCompleted() {
282 return this.completed;
283 }
284
285 public long getWriteNumber() {
286 return this.writeNumber;
287 }
288
289 @Override
290 public String toString() {
291 return this.writeNumber + ", " + this.completed;
292 }
293 }
294
295 public static final long FIXED_SIZE = ClassSize.align(
296 ClassSize.OBJECT +
297 2 * Bytes.SIZEOF_LONG +
298 2 * ClassSize.REFERENCE);
299 }