1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.client;
20
21 import static org.apache.hadoop.hbase.client.ClientScanner.createClosestRowBefore;
22
23 import java.io.IOException;
24 import java.io.InterruptedIOException;
25 import java.util.HashSet;
26 import java.util.Set;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.CancellationException;
29 import java.util.concurrent.ExecutionException;
30 import java.util.concurrent.ExecutorService;
31 import java.util.concurrent.Future;
32 import java.util.concurrent.TimeUnit;
33 import java.util.concurrent.atomic.AtomicBoolean;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.hadoop.hbase.classification.InterfaceAudience;
38 import org.apache.hadoop.conf.Configuration;
39 import org.apache.hadoop.hbase.HRegionInfo;
40 import org.apache.hadoop.hbase.RegionLocations;
41 import org.apache.hadoop.hbase.TableName;
42 import org.apache.hadoop.hbase.util.Bytes;
43 import org.apache.hadoop.hbase.util.Pair;
44
45 import com.google.common.annotations.VisibleForTesting;
46
47
48
49
50
51
52
53
54
55
56
57
58
59 @InterfaceAudience.Private
60 class ScannerCallableWithReplicas implements RetryingCallable<Result[]> {
61 private final Log LOG = LogFactory.getLog(this.getClass());
62 volatile ScannerCallable currentScannerCallable;
63 AtomicBoolean replicaSwitched = new AtomicBoolean(false);
64 final ClusterConnection cConnection;
65 protected final ExecutorService pool;
66 protected final int timeBeforeReplicas;
67 private final Scan scan;
68 private final int retries;
69 private Result lastResult;
70 private final RpcRetryingCaller<Result[]> caller;
71 private final TableName tableName;
72 private Configuration conf;
73 private int scannerTimeout;
74 private Set<ScannerCallable> outstandingCallables = new HashSet<ScannerCallable>();
75 private boolean someRPCcancelled = false;
76
77 public ScannerCallableWithReplicas(TableName tableName, ClusterConnection cConnection,
78 ScannerCallable baseCallable, ExecutorService pool, int timeBeforeReplicas, Scan scan,
79 int retries, int scannerTimeout, int caching, Configuration conf,
80 RpcRetryingCaller<Result []> caller) {
81 this.currentScannerCallable = baseCallable;
82 this.cConnection = cConnection;
83 this.pool = pool;
84 if (timeBeforeReplicas < 0) {
85 throw new IllegalArgumentException("Invalid value of operation timeout on the primary");
86 }
87 this.timeBeforeReplicas = timeBeforeReplicas;
88 this.scan = scan;
89 this.retries = retries;
90 this.tableName = tableName;
91 this.conf = conf;
92 this.scannerTimeout = scannerTimeout;
93 this.caller = caller;
94 }
95
96 public void setClose() {
97 currentScannerCallable.setClose();
98 }
99
100 public void setRenew(boolean val) {
101 currentScannerCallable.setRenew(val);
102 }
103
104 public void setCaching(int caching) {
105 currentScannerCallable.setCaching(caching);
106 }
107
108 public int getCaching() {
109 return currentScannerCallable.getCaching();
110 }
111
112 public HRegionInfo getHRegionInfo() {
113 return currentScannerCallable.getHRegionInfo();
114 }
115
116 public boolean getServerHasMoreResults() {
117 return currentScannerCallable.getServerHasMoreResults();
118 }
119
120 public void setServerHasMoreResults(boolean serverHasMoreResults) {
121 currentScannerCallable.setServerHasMoreResults(serverHasMoreResults);
122 }
123
124 public boolean hasMoreResultsContext() {
125 return currentScannerCallable.hasMoreResultsContext();
126 }
127
128 public void setHasMoreResultsContext(boolean serverHasMoreResultsContext) {
129 currentScannerCallable.setHasMoreResultsContext(serverHasMoreResultsContext);
130 }
131
132 @Override
133 public Result [] call(int timeout) throws IOException {
134
135
136
137
138 if (currentScannerCallable != null && currentScannerCallable.closed) {
139
140
141 if (LOG.isTraceEnabled()) {
142 LOG.trace("Closing scanner id=" + currentScannerCallable.scannerId);
143 }
144 Result[] r = currentScannerCallable.call(timeout);
145 currentScannerCallable = null;
146 return r;
147 }
148
149
150
151
152
153
154
155 RegionLocations rl = RpcRetryingCallerWithReadReplicas.getRegionLocations(true,
156 RegionReplicaUtil.DEFAULT_REPLICA_ID, cConnection, tableName,
157 currentScannerCallable.getRow());
158
159
160
161 ResultBoundedCompletionService<Pair<Result[], ScannerCallable>> cs =
162 new ResultBoundedCompletionService<Pair<Result[], ScannerCallable>>(
163 RpcRetryingCallerFactory.instantiate(ScannerCallableWithReplicas.this.conf), pool,
164 rl.size() * 5);
165
166 AtomicBoolean done = new AtomicBoolean(false);
167 replicaSwitched.set(false);
168
169 addCallsForCurrentReplica(cs, rl);
170 try {
171
172 Future<Pair<Result[], ScannerCallable>> f = cs.poll(timeBeforeReplicas,
173 TimeUnit.MICROSECONDS);
174 if (f != null) {
175 Pair<Result[], ScannerCallable> r = f.get();
176 if (r != null && r.getSecond() != null) {
177 updateCurrentlyServingReplica(r.getSecond(), r.getFirst(), done, pool);
178 }
179 return r == null ? null : r.getFirst();
180 }
181 } catch (ExecutionException e) {
182 RpcRetryingCallerWithReadReplicas.throwEnrichedException(e, retries);
183 } catch (CancellationException e) {
184 throw new InterruptedIOException(e.getMessage());
185 } catch (InterruptedException e) {
186 throw new InterruptedIOException(e.getMessage());
187 }
188
189
190 addCallsForOtherReplicas(cs, rl, 0, rl.size() - 1);
191 try {
192 Future<Pair<Result[], ScannerCallable>> f = cs.take();
193 Pair<Result[], ScannerCallable> r = f.get();
194 if (r != null && r.getSecond() != null) {
195 updateCurrentlyServingReplica(r.getSecond(), r.getFirst(), done, pool);
196 }
197 return r == null ? null : r.getFirst();
198 } catch (ExecutionException e) {
199 RpcRetryingCallerWithReadReplicas.throwEnrichedException(e, retries);
200 } catch (CancellationException e) {
201 throw new InterruptedIOException(e.getMessage());
202 } catch (InterruptedException e) {
203 throw new InterruptedIOException(e.getMessage());
204 } finally {
205
206
207 cs.cancelAll();
208 }
209 return null;
210 }
211
212 private void updateCurrentlyServingReplica(ScannerCallable scanner, Result[] result,
213 AtomicBoolean done, ExecutorService pool) {
214 if (done.compareAndSet(false, true)) {
215 if (currentScannerCallable != scanner) replicaSwitched.set(true);
216 currentScannerCallable = scanner;
217
218 if (result != null && result.length != 0) this.lastResult = result[result.length - 1];
219 if (LOG.isTraceEnabled()) {
220 LOG.trace("Setting current scanner as id=" + currentScannerCallable.scannerId +
221 " associated with replica=" + currentScannerCallable.getHRegionInfo().getReplicaId());
222 }
223
224 outstandingCallables.remove(scanner);
225 for (ScannerCallable s : outstandingCallables) {
226 if (LOG.isTraceEnabled()) {
227 LOG.trace("Closing scanner id=" + s.scannerId +
228 ", replica=" + s.getHRegionInfo().getRegionId() +
229 " because slow and replica=" +
230 this.currentScannerCallable.getHRegionInfo().getReplicaId() + " succeeded");
231 }
232
233
234
235 s.setClose();
236 final RetryingRPC r = new RetryingRPC(s);
237 pool.submit(new Callable<Void>(){
238 @Override
239 public Void call() throws Exception {
240 r.call(scannerTimeout);
241 return null;
242 }
243 });
244 }
245
246 outstandingCallables.clear();
247 }
248 }
249
250
251
252
253
254
255 public boolean switchedToADifferentReplica() {
256 return replicaSwitched.get();
257 }
258
259
260
261
262
263
264
265 public boolean isHeartbeatMessage() {
266 return currentScannerCallable != null && currentScannerCallable.isHeartbeatMessage();
267 }
268
269 private void addCallsForCurrentReplica(
270 ResultBoundedCompletionService<Pair<Result[], ScannerCallable>> cs, RegionLocations rl) {
271 RetryingRPC retryingOnReplica = new RetryingRPC(currentScannerCallable);
272 outstandingCallables.add(currentScannerCallable);
273 cs.submit(retryingOnReplica, scannerTimeout, currentScannerCallable.id);
274 }
275
276 private void addCallsForOtherReplicas(
277 ResultBoundedCompletionService<Pair<Result[], ScannerCallable>> cs, RegionLocations rl,
278 int min, int max) {
279 if (scan.getConsistency() == Consistency.STRONG) {
280 return;
281 }
282 for (int id = min; id <= max; id++) {
283 if (currentScannerCallable.id == id) {
284 continue;
285 }
286 ScannerCallable s = currentScannerCallable.getScannerCallableForReplica(id);
287 setStartRowForReplicaCallable(s);
288 outstandingCallables.add(s);
289 RetryingRPC retryingOnReplica = new RetryingRPC(s);
290 cs.submit(retryingOnReplica, scannerTimeout, id);
291 }
292 }
293
294
295
296
297
298 private void setStartRowForReplicaCallable(ScannerCallable callable) {
299 if (this.lastResult == null || callable == null) return;
300
301 if (this.lastResult.isPartial()) {
302
303
304
305
306 callable.getScan().setStartRow(this.lastResult.getRow());
307 } else {
308
309
310
311 if (callable.getScan().isReversed()) {
312 callable.getScan().setStartRow(createClosestRowBefore(this.lastResult.getRow()));
313 } else {
314 callable.getScan().setStartRow(Bytes.add(this.lastResult.getRow(), new byte[1]));
315 }
316 }
317 }
318
319 @VisibleForTesting
320 boolean isAnyRPCcancelled() {
321 return someRPCcancelled;
322 }
323
324 class RetryingRPC implements RetryingCallable<Pair<Result[], ScannerCallable>>, Cancellable {
325 final ScannerCallable callable;
326 RpcRetryingCaller<Result[]> caller;
327 private volatile boolean cancelled = false;
328
329 RetryingRPC(ScannerCallable callable) {
330 this.callable = callable;
331
332
333
334
335
336 this.caller = ScannerCallableWithReplicas.this.caller;
337 if (scan.getConsistency() == Consistency.TIMELINE) {
338 this.caller = RpcRetryingCallerFactory.instantiate(ScannerCallableWithReplicas.this.conf)
339 .<Result[]>newCaller();
340 }
341 }
342
343 @Override
344 public Pair<Result[], ScannerCallable> call(int callTimeout) throws IOException {
345
346
347 if (cancelled) {
348 return null;
349 }
350 Result[] res = this.caller.callWithoutRetries(this.callable, callTimeout);
351 return new Pair<Result[], ScannerCallable>(res, this.callable);
352 }
353
354 @Override
355 public void prepare(boolean reload) throws IOException {
356 if (cancelled) return;
357
358 if (Thread.interrupted()) {
359 throw new InterruptedIOException();
360 }
361
362 callable.prepare(reload);
363 }
364
365 @Override
366 public void throwable(Throwable t, boolean retrying) {
367 callable.throwable(t, retrying);
368 }
369
370 @Override
371 public String getExceptionMessageAdditionalDetail() {
372 return callable.getExceptionMessageAdditionalDetail();
373 }
374
375 @Override
376 public long sleep(long pause, int tries) {
377 return callable.sleep(pause, tries);
378 }
379
380 @Override
381 public void cancel() {
382 cancelled = true;
383 caller.cancel();
384 if (callable.getController() != null) {
385 callable.getController().startCancel();
386 }
387 someRPCcancelled = true;
388 }
389
390 @Override
391 public boolean isCancelled() {
392 return cancelled;
393 }
394 }
395
396 @Override
397 public void prepare(boolean reload) throws IOException {
398 }
399
400 @Override
401 public void throwable(Throwable t, boolean retrying) {
402 currentScannerCallable.throwable(t, retrying);
403 }
404
405 @Override
406 public String getExceptionMessageAdditionalDetail() {
407 return currentScannerCallable.getExceptionMessageAdditionalDetail();
408 }
409
410 @Override
411 public long sleep(long pause, int tries) {
412 return currentScannerCallable.sleep(pause, tries);
413 }
414 }