View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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   * Manages the read/write consistency. This provides an interface for readers to determine what
36   * entries to ignore, and a mechanism for writers to obtain new write numbers, then "commit"
37   * the new writes for readers to read (thus forming atomic transactions).
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     * Represents no value, or not set.
49     */
50    public static final long NONE = -1;
51  
52    // This is the pending queue of writes.
53    //
54    // TODO(eclark): Should this be an array of fixed size to
55    // reduce the number of allocations on the write path?
56    // This could be equal to the number of handlers + a small number.
57    // TODO: St.Ack 20150903 Sounds good to me.
58    private final LinkedList<WriteEntry> writeQueue = new LinkedList<WriteEntry>();
59  
60    public MultiVersionConcurrencyControl() {
61      super();
62    }
63  
64    /**
65     * Construct and set read point. Write point is uninitialized.
66     */
67    public MultiVersionConcurrencyControl(long startPoint) {
68      tryAdvanceTo(startPoint, NONE);
69    }
70  
71    /**
72     * Step the MVCC forward on to a new read/write basis.
73     * @param newStartPoint
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     * Step the MVCC forward on to a new read/write basis.
85     * @param newStartPoint Point to move read and write points to.
86     * @param expected If not -1 (#NONE)
87     * @return Returns false if <code>expected</code> is not equal to the
88     * current <code>readPoint</code> or if <code>startPoint</code> is less than current
89     * <code>readPoint</code>
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    * Call {@link #begin(Runnable)} with an empty {@link Runnable}.
115    */
116   public WriteEntry begin() {
117     return begin(new Runnable() {
118       @Override public void run() {
119 
120       }
121     });
122   }
123 
124   /**
125    * Start a write transaction. Create a new {@link WriteEntry} with a new write number and add it
126    * to our queue of ongoing writes. Return this WriteEntry instance. To complete the write
127    * transaction and wait for it to be visible, call {@link #completeAndWait(WriteEntry)}. If the
128    * write failed, call {@link #complete(WriteEntry)} so we can clean up AFTER removing ALL trace of
129    * the failed write transaction.
130    * <p>
131    * The {@code action} will be executed under the lock which means it can keep the same order with
132    * mvcc.
133    * @see #complete(WriteEntry)
134    * @see #completeAndWait(WriteEntry)
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    * Wait until the read point catches up to the write point; i.e. wait on all outstanding mvccs
148    * to complete.
149    */
150   public void await() {
151     // Add a write and then wait on reads to catch up to it.
152     completeAndWait(begin());
153   }
154 
155   /**
156    * Complete a {@link WriteEntry} that was created by {@link #begin()} then wait until the
157    * read point catches up to our write.
158    *
159    * At the end of this call, the global read point is at least as large as the write point
160    * of the passed in WriteEntry.  Thus, the write is visible to MVCC readers.
161    */
162   public void completeAndWait(WriteEntry e) {
163     if (!complete(e)) {
164       waitForRead(e);
165     }
166   }
167 
168   /**
169    * Mark the {@link WriteEntry} as complete and advance the read point as much as possible.
170    * Call this even if the write has FAILED (AFTER backing out the write transaction
171    * changes completely) so we can clean up the outstanding transaction.
172    *
173    * How much is the read point advanced?
174    *
175    * Let S be the set of all write numbers that are completed. Set the read point to the highest
176    * numbered write of S.
177    *
178    * @param writeEntry
179    *
180    * @return true if e is visible to MVCC readers (that is, readpoint >= e.writeNumber)
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    * Wait for the global readPoint to advance up to the passed in write entry number.
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           // We were interrupted... finish the loop -- i.e. cleanup --and then
237           // on our way out, reset the interrupt flag.
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    * Write number and whether write has completed given out at start of a write transaction.
266    * Every created WriteEntry must be completed by calling mvcc#complete or #completeAndWait.
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 }