View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.regionserver;
19  
20  import java.util.List;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
25  import org.apache.hadoop.hbase.classification.InterfaceAudience;
26  import org.apache.hadoop.hbase.classification.InterfaceStability;
27  
28  /**
29   * ScannerContext instances encapsulate limit tracking AND progress towards those limits during
30   * invocations of {@link InternalScanner#next(java.util.List)} and
31   * {@link RegionScanner#next(java.util.List)}.
32   * <p>
33   * A ScannerContext instance should be updated periodically throughout execution whenever progress
34   * towards a limit has been made. Each limit can be checked via the appropriate checkLimit method.
35   * <p>
36   * Once a limit has been reached, the scan will stop. The invoker of
37   * {@link InternalScanner#next(java.util.List)} or {@link RegionScanner#next(java.util.List)} can
38   * use the appropriate check*Limit methods to see exactly which limits have been reached.
39   * Alternatively, {@link #checkAnyLimitReached(LimitScope)} is provided to see if ANY limit was
40   * reached
41   * <p>
42   * {@link NoLimitScannerContext#NO_LIMIT} is an immutable static definition that can be used
43   * whenever a {@link ScannerContext} is needed but limits do not need to be enforced.
44   * <p>
45   * NOTE: It is important that this class only ever expose setter methods that can be safely skipped
46   * when limits should be NOT enforced. This is because of the necessary immutability of the class
47   * {@link NoLimitScannerContext}. If a setter cannot be safely skipped, the immutable nature of
48   * {@link NoLimitScannerContext} will lead to incorrect behavior.
49   */
50  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
51  @InterfaceStability.Evolving
52  public class ScannerContext {
53    private final Log LOG = LogFactory.getLog(this.getClass());
54  
55    /**
56     * Two sets of the same fields. One for the limits, another for the progress towards those limits
57     */
58    LimitFields limits;
59    LimitFields progress;
60  
61    /**
62     * The state of the scanner after the invocation of {@link InternalScanner#next(java.util.List)}
63     * or {@link RegionScanner#next(java.util.List)}.
64     */
65    NextState scannerState;
66    private static final NextState DEFAULT_STATE = NextState.MORE_VALUES;
67  
68    /**
69     * Used as an indication to invocations of {@link InternalScanner#next(java.util.List)} and
70     * {@link RegionScanner#next(java.util.List)} that, if true, the progress tracked within this
71     * {@link ScannerContext} instance should be considered while evaluating the limits. Useful for
72     * enforcing a set of limits across multiple calls (i.e. the limit may not be reached in a single
73     * invocation, but any progress made should be considered in future invocations)
74     * <p>
75     * Defaulting this value to false means that, by default, any tracked progress will be wiped clean
76     * on invocations to {@link InternalScanner#next(java.util.List)} and
77     * {@link RegionScanner#next(java.util.List)} and the call will be treated as though no progress
78     * has been made towards the limits so far.
79     * <p>
80     * This is an important mechanism. Users of Internal/Region scanners expect that they can define
81     * some limits and then repeatedly invoke {@link InternalScanner#next(List)} or
82     * {@link RegionScanner#next(List)} where each invocation respects these limits separately.
83     * <p>
84     * For example: <code><pre>
85     * ScannerContext context = new ScannerContext.newBuilder().setBatchLimit(5).build();
86     * RegionScanner scanner = ...
87     * List<Cell> results = new ArrayList<Cell>();
88     * while(scanner.next(results, context)) {
89     *   // Do something with a batch of 5 cells
90     * }
91     * </pre></code> However, in the case of RPCs, the server wants to be able to define a set of
92     * limits for a particular RPC request and have those limits respected across multiple
93     * invocations. This means that the progress made towards the limits in earlier calls will be
94     * saved and considered in future invocations
95     */
96    boolean keepProgress;
97    private static boolean DEFAULT_KEEP_PROGRESS = false;
98  
99    ScannerContext(boolean keepProgress, LimitFields limitsToCopy) {
100     this.limits = new LimitFields();
101     if (limitsToCopy != null) this.limits.copy(limitsToCopy);
102 
103     // Progress fields are initialized to 0
104     progress = new LimitFields(0, LimitFields.DEFAULT_SCOPE, 0, LimitFields.DEFAULT_SCOPE, 0);
105 
106     this.keepProgress = keepProgress;
107     this.scannerState = DEFAULT_STATE;
108   }
109 
110   /**
111    * @return true if the progress tracked so far in this instance will be considered during an
112    *         invocation of {@link InternalScanner#next(java.util.List)} or
113    *         {@link RegionScanner#next(java.util.List)}. false when the progress tracked so far
114    *         should not be considered and should instead be wiped away via {@link #clearProgress()}
115    */
116   boolean getKeepProgress() {
117     return keepProgress;
118   }
119 
120   void setKeepProgress(boolean keepProgress) {
121     this.keepProgress = keepProgress;
122   }
123 
124   /**
125    * Progress towards the batch limit has been made. Increment internal tracking of batch progress
126    */
127   void incrementBatchProgress(int batch) {
128     int currentBatch = progress.getBatch();
129     progress.setBatch(currentBatch + batch);
130   }
131 
132   /**
133    * Progress towards the size limit has been made. Increment internal tracking of size progress
134    */
135   void incrementSizeProgress(long size) {
136     long currentSize = progress.getSize();
137     progress.setSize(currentSize + size);
138   }
139 
140   /**
141    * Update the time progress with {@link System#currentTimeMillis()}
142    */
143   void updateTimeProgress() {
144     progress.setTime(System.currentTimeMillis());
145   }
146 
147   int getBatchProgress() {
148     return progress.getBatch();
149   }
150 
151   long getSizeProgress() {
152     return progress.getSize();
153   }
154 
155   long getTimeProgress() {
156     return progress.getTime();
157   }
158 
159   void setProgress(int batchProgress, long sizeProgress, long timeProgress) {
160     setBatchProgress(batchProgress);
161     setSizeProgress(sizeProgress);
162     setTimeProgress(timeProgress);
163   }
164 
165   void setSizeProgress(long sizeProgress) {
166     progress.setSize(sizeProgress);
167   }
168 
169   void setBatchProgress(int batchProgress) {
170     progress.setBatch(batchProgress);
171   }
172 
173   void setTimeProgress(long timeProgress) {
174     progress.setTime(timeProgress);
175   }
176 
177   /**
178    * Clear away any progress that has been made so far. All progress fields are reset to initial
179    * values
180    */
181   void clearProgress() {
182     progress.setFields(0, LimitFields.DEFAULT_SCOPE, 0, LimitFields.DEFAULT_SCOPE, 0);
183   }
184 
185   /**
186    * Note that this is not a typical setter. This setter returns the {@link NextState} that was
187    * passed in so that methods can be invoked against the new state. Furthermore, this pattern
188    * allows the {@link NoLimitScannerContext} to cleanly override this setter and simply return the
189    * new state, thus preserving the immutability of {@link NoLimitScannerContext}
190    * @param state
191    * @return The state that was passed in.
192    */
193   NextState setScannerState(NextState state) {
194     if (!NextState.isValidState(state)) {
195       throw new IllegalArgumentException("Cannot set to invalid state: " + state);
196     }
197 
198     this.scannerState = state;
199     return state;
200   }
201 
202   /**
203    * @return true when a partial result is formed. A partial result is formed when a limit is
204    *         reached in the middle of a row.
205    */
206   boolean partialResultFormed() {
207     return scannerState == NextState.SIZE_LIMIT_REACHED_MID_ROW
208         || scannerState == NextState.TIME_LIMIT_REACHED_MID_ROW;
209   }
210 
211   /**
212    * @return true when a mid-row result is formed.
213    */
214   boolean midRowResultFormed() {
215     return scannerState == NextState.SIZE_LIMIT_REACHED_MID_ROW
216         || scannerState == NextState.TIME_LIMIT_REACHED_MID_ROW
217         || scannerState == NextState.BATCH_LIMIT_REACHED;
218   }
219 
220   /**
221    * @param checkerScope
222    * @return true if the batch limit can be enforced in the checker's scope
223    */
224   boolean hasBatchLimit(LimitScope checkerScope) {
225     return limits.canEnforceBatchLimitFromScope(checkerScope) && limits.getBatch() > 0;
226   }
227 
228   /**
229    * @param checkerScope
230    * @return true if the size limit can be enforced in the checker's scope
231    */
232   boolean hasSizeLimit(LimitScope checkerScope) {
233     return limits.canEnforceSizeLimitFromScope(checkerScope) && limits.getSize() > 0;
234   }
235 
236   /**
237    * @param checkerScope
238    * @return true if the time limit can be enforced in the checker's scope
239    */
240   boolean hasTimeLimit(LimitScope checkerScope) {
241     return limits.canEnforceTimeLimitFromScope(checkerScope) && limits.getTime() > 0;
242   }
243 
244   /**
245    * @param checkerScope
246    * @return true if any limit can be enforced within the checker's scope
247    */
248   boolean hasAnyLimit(LimitScope checkerScope) {
249     return hasBatchLimit(checkerScope) || hasSizeLimit(checkerScope) || hasTimeLimit(checkerScope);
250   }
251 
252   /**
253    * @param scope The scope in which the size limit will be enforced
254    */
255   void setSizeLimitScope(LimitScope scope) {
256     limits.setSizeScope(scope);
257   }
258 
259   /**
260    * @param scope The scope in which the time limit will be enforced
261    */
262   void setTimeLimitScope(LimitScope scope) {
263     limits.setTimeScope(scope);
264   }
265 
266   int getBatchLimit() {
267     return limits.getBatch();
268   }
269 
270   long getSizeLimit() {
271     return limits.getSize();
272   }
273 
274   long getTimeLimit() {
275     return limits.getTime();
276   }
277 
278   /**
279    * @param checkerScope The scope that the limit is being checked from
280    * @return true when the limit is enforceable from the checker's scope and it has been reached
281    */
282   boolean checkBatchLimit(LimitScope checkerScope) {
283     return hasBatchLimit(checkerScope) && progress.getBatch() >= limits.getBatch();
284   }
285 
286   /**
287    * @param checkerScope The scope that the limit is being checked from
288    * @return true when the limit is enforceable from the checker's scope and it has been reached
289    */
290   boolean checkSizeLimit(LimitScope checkerScope) {
291     return hasSizeLimit(checkerScope) && progress.getSize() >= limits.getSize();
292   }
293 
294   /**
295    * @param checkerScope The scope that the limit is being checked from. The time limit is always
296    *          checked against {@link System#currentTimeMillis()}
297    * @return true when the limit is enforceable from the checker's scope and it has been reached
298    */
299   boolean checkTimeLimit(LimitScope checkerScope) {
300     return hasTimeLimit(checkerScope) && progress.getTime() >= limits.getTime();
301   }
302 
303   /**
304    * @param checkerScope The scope that the limits are being checked from
305    * @return true when some limit is enforceable from the checker's scope and it has been reached
306    */
307   boolean checkAnyLimitReached(LimitScope checkerScope) {
308     return checkSizeLimit(checkerScope) || checkBatchLimit(checkerScope)
309         || checkTimeLimit(checkerScope);
310   }
311 
312   @Override
313   public String toString() {
314     StringBuilder sb = new StringBuilder();
315     sb.append("{");
316 
317     sb.append("limits:");
318     sb.append(limits);
319 
320     sb.append(", progress:");
321     sb.append(progress);
322 
323     sb.append(", keepProgress:");
324     sb.append(keepProgress);
325 
326     sb.append(", state:");
327     sb.append(scannerState);
328 
329     sb.append("}");
330     return sb.toString();
331   }
332 
333   public static Builder newBuilder() {
334     return new Builder();
335   }
336 
337   public static Builder newBuilder(boolean keepProgress) {
338     return new Builder(keepProgress);
339   }
340 
341   public static final class Builder {
342     boolean keepProgress = DEFAULT_KEEP_PROGRESS;
343     LimitFields limits = new LimitFields();
344 
345     private Builder() {
346     }
347 
348     private Builder(boolean keepProgress) {
349       this.keepProgress = keepProgress;
350     }
351 
352     public Builder setKeepProgress(boolean keepProgress) {
353       this.keepProgress = keepProgress;
354       return this;
355     }
356 
357     public Builder setSizeLimit(LimitScope sizeScope, long sizeLimit) {
358       limits.setSize(sizeLimit);
359       limits.setSizeScope(sizeScope);
360       return this;
361     }
362 
363     public Builder setTimeLimit(LimitScope timeScope, long timeLimit) {
364       limits.setTime(timeLimit);
365       limits.setTimeScope(timeScope);
366       return this;
367     }
368 
369     public Builder setBatchLimit(int batchLimit) {
370       limits.setBatch(batchLimit);
371       return this;
372     }
373 
374     public ScannerContext build() {
375       return new ScannerContext(keepProgress, limits);
376     }
377   }
378 
379   /**
380    * The possible states a scanner may be in following a call to {@link InternalScanner#next(List)}
381    */
382   public enum NextState {
383     MORE_VALUES(true, false),
384     NO_MORE_VALUES(false, false),
385     SIZE_LIMIT_REACHED(true, true),
386 
387     /**
388      * Special case of size limit reached to indicate that the size limit was reached in the middle
389      * of a row and thus a partial results was formed
390      */
391     SIZE_LIMIT_REACHED_MID_ROW(true, true),
392     TIME_LIMIT_REACHED(true, true),
393 
394     /**
395      * Special case of time limit reached to indicate that the time limit was reached in the middle
396      * of a row and thus a partial results was formed
397      */
398     TIME_LIMIT_REACHED_MID_ROW(true, true),
399     BATCH_LIMIT_REACHED(true, true);
400 
401     private boolean moreValues;
402     private boolean limitReached;
403 
404     private NextState(boolean moreValues, boolean limitReached) {
405       this.moreValues = moreValues;
406       this.limitReached = limitReached;
407     }
408 
409     /**
410      * @return true when the state indicates that more values may follow those that have been
411      *         returned
412      */
413     public boolean hasMoreValues() {
414       return this.moreValues;
415     }
416 
417     /**
418      * @return true when the state indicates that a limit has been reached and scan should stop
419      */
420     public boolean limitReached() {
421       return this.limitReached;
422     }
423 
424     public static boolean isValidState(NextState state) {
425       return state != null;
426     }
427 
428     public static boolean hasMoreValues(NextState state) {
429       return isValidState(state) && state.hasMoreValues();
430     }
431   }
432 
433   /**
434    * The various scopes where a limit can be enforced. Used to differentiate when a limit should be
435    * enforced or not.
436    */
437   public enum LimitScope {
438     /**
439      * Enforcing a limit between rows means that the limit will not be considered until all the
440      * cells for a particular row have been retrieved
441      */
442     BETWEEN_ROWS(0),
443 
444     /**
445      * Enforcing a limit between cells means that the limit will be considered after each full cell
446      * has been retrieved
447      */
448     BETWEEN_CELLS(1);
449 
450     /**
451      * When enforcing a limit, we must check that the scope is appropriate for enforcement.
452      * <p>
453      * To communicate this concept, each scope has a depth. A limit will be enforced if the depth of
454      * the checker's scope is less than or equal to the limit's scope. This means that when checking
455      * limits, the checker must know their own scope (i.e. are they checking the limits between
456      * rows, between cells, etc...)
457      */
458     int depth;
459 
460     LimitScope(int depth) {
461       this.depth = depth;
462     }
463 
464     int depth() {
465       return depth;
466     }
467 
468     /**
469      * @param checkerScope The scope in which the limit is being checked
470      * @return true when the checker is in a scope that indicates the limit can be enforced. Limits
471      *         can be enforced from "higher or equal" scopes (i.e. the checker's scope is at a
472      *         lesser depth than the limit)
473      */
474     boolean canEnforceLimitFromScope(LimitScope checkerScope) {
475       return checkerScope != null && checkerScope.depth() <= depth;
476     }
477   }
478 
479   /**
480    * The different fields that can be used as limits in calls to
481    * {@link InternalScanner#next(java.util.List)} and {@link RegionScanner#next(java.util.List)}
482    */
483   private static class LimitFields {
484     /**
485      * Default values of the limit fields. Defined such that if a field does NOT change from its
486      * default, it will not be enforced
487      */
488     private static int DEFAULT_BATCH = -1;
489     private static long DEFAULT_SIZE = -1L;
490     private static long DEFAULT_TIME = -1L;
491 
492     /**
493      * Default scope that is assigned to a limit if a scope is not specified.
494      */
495     private static final LimitScope DEFAULT_SCOPE = LimitScope.BETWEEN_ROWS;
496 
497     // The batch limit will always be enforced between cells, thus, there isn't a field to hold the
498     // batch scope
499     int batch = DEFAULT_BATCH;
500 
501     LimitScope sizeScope = DEFAULT_SCOPE;
502     long size = DEFAULT_SIZE;
503 
504     LimitScope timeScope = DEFAULT_SCOPE;
505     long time = DEFAULT_TIME;
506 
507     /**
508      * Fields keep their default values.
509      */
510     LimitFields() {
511     }
512 
513     LimitFields(int batch, LimitScope sizeScope, long size, LimitScope timeScope, long time) {
514       setFields(batch, sizeScope, size, timeScope, time);
515     }
516 
517     void copy(LimitFields limitsToCopy) {
518       if (limitsToCopy != null) {
519         setFields(limitsToCopy.getBatch(), limitsToCopy.getSizeScope(), limitsToCopy.getSize(),
520           limitsToCopy.getTimeScope(), limitsToCopy.getTime());
521       }
522     }
523 
524     /**
525      * Set all fields together.
526      * @param batch
527      * @param sizeScope
528      * @param size
529      */
530     void setFields(int batch, LimitScope sizeScope, long size, LimitScope timeScope, long time) {
531       setBatch(batch);
532       setSizeScope(sizeScope);
533       setSize(size);
534       setTimeScope(timeScope);
535       setTime(time);
536     }
537 
538     int getBatch() {
539       return this.batch;
540     }
541 
542     void setBatch(int batch) {
543       this.batch = batch;
544     }
545 
546     /**
547      * @param checkerScope
548      * @return true when the limit can be enforced from the scope of the checker
549      */
550     boolean canEnforceBatchLimitFromScope(LimitScope checkerScope) {
551       return LimitScope.BETWEEN_CELLS.canEnforceLimitFromScope(checkerScope);
552     }
553 
554     long getSize() {
555       return this.size;
556     }
557 
558     void setSize(long size) {
559       this.size = size;
560     }
561 
562     /**
563      * @return {@link LimitScope} indicating scope in which the size limit is enforced
564      */
565     LimitScope getSizeScope() {
566       return this.sizeScope;
567     }
568 
569     /**
570      * Change the scope in which the size limit is enforced
571      */
572     void setSizeScope(LimitScope scope) {
573       this.sizeScope = scope;
574     }
575 
576     /**
577      * @param checkerScope
578      * @return true when the limit can be enforced from the scope of the checker
579      */
580     boolean canEnforceSizeLimitFromScope(LimitScope checkerScope) {
581       return this.sizeScope.canEnforceLimitFromScope(checkerScope);
582     }
583 
584     long getTime() {
585       return this.time;
586     }
587 
588     void setTime(long time) {
589       this.time = time;
590     }
591 
592     /**
593      * @return {@link LimitScope} indicating scope in which the time limit is enforced
594      */
595     LimitScope getTimeScope() {
596       return this.timeScope;
597     }
598 
599     /**
600      * Change the scope in which the time limit is enforced
601      */
602     void setTimeScope(LimitScope scope) {
603       this.timeScope = scope;
604     }
605 
606     /**
607      * @param checkerScope
608      * @return true when the limit can be enforced from the scope of the checker
609      */
610     boolean canEnforceTimeLimitFromScope(LimitScope checkerScope) {
611       return this.timeScope.canEnforceLimitFromScope(checkerScope);
612     }
613 
614     @Override
615     public String toString() {
616       StringBuilder sb = new StringBuilder();
617       sb.append("{");
618 
619       sb.append("batch:");
620       sb.append(batch);
621 
622       sb.append(", size:");
623       sb.append(size);
624 
625       sb.append(", sizeScope:");
626       sb.append(sizeScope);
627 
628       sb.append(", time:");
629       sb.append(time);
630 
631       sb.append(", timeScope:");
632       sb.append(timeScope);
633 
634       sb.append("}");
635       return sb.toString();
636     }
637   }
638 }