001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hdfs.server.datanode; 020 021import java.io.DataOutputStream; 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.util.Iterator; 025import java.util.LinkedHashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.concurrent.TimeUnit; 029 030import com.google.common.annotations.VisibleForTesting; 031import com.google.common.base.Preconditions; 032import com.google.common.cache.Cache; 033import com.google.common.cache.CacheBuilder; 034import org.apache.hadoop.hdfs.protocol.Block; 035import org.apache.hadoop.hdfs.protocol.ExtendedBlock; 036import org.apache.hadoop.hdfs.server.datanode.BlockScanner.Conf; 037import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeReference; 038import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeSpi.BlockIterator; 039import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeSpi; 040import org.apache.hadoop.hdfs.util.DataTransferThrottler; 041import org.apache.hadoop.io.IOUtils; 042import org.apache.hadoop.util.Time; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046/** 047 * VolumeScanner scans a single volume. Each VolumeScanner has its own thread.<p/> 048 * They are all managed by the DataNode's BlockScanner. 049 */ 050public class VolumeScanner extends Thread { 051 public static final Logger LOG = 052 LoggerFactory.getLogger(VolumeScanner.class); 053 054 /** 055 * Number of seconds in a minute. 056 */ 057 private final static int SECONDS_PER_MINUTE = 60; 058 059 /** 060 * Number of minutes in an hour. 061 */ 062 private final static int MINUTES_PER_HOUR = 60; 063 064 /** 065 * Name of the block iterator used by this scanner. 066 */ 067 private final static String BLOCK_ITERATOR_NAME = "scanner"; 068 069 /** 070 * The configuration. 071 */ 072 private Conf conf; 073 074 @VisibleForTesting 075 void setConf(Conf conf) { 076 this.conf = conf; 077 } 078 079 /** 080 * The DataNode this VolumEscanner is associated with. 081 */ 082 private final DataNode datanode; 083 084 /** 085 * A reference to the volume that we're scanning. 086 */ 087 private final FsVolumeReference ref; 088 089 /** 090 * The volume that we're scanning. 091 */ 092 final FsVolumeSpi volume; 093 094 /** 095 * The number of scanned bytes in each minute of the last hour.<p/> 096 * 097 * This array is managed as a circular buffer. We take the monotonic time and 098 * divide it up into one-minute periods. Each entry in the array represents 099 * how many bytes were scanned during that period. 100 */ 101 private final long scannedBytes[] = new long[MINUTES_PER_HOUR]; 102 103 /** 104 * The sum of all the values of scannedBytes. 105 */ 106 private long scannedBytesSum = 0; 107 108 /** 109 * The throttler to use with BlockSender objects. 110 */ 111 private final DataTransferThrottler throttler = new DataTransferThrottler(1); 112 113 /** 114 * The null output stream to use with BlockSender objects. 115 */ 116 private final DataOutputStream nullStream = 117 new DataOutputStream(new IOUtils.NullOutputStream()); 118 119 /** 120 * The block iterators associated with this VolumeScanner.<p/> 121 * 122 * Each block pool has its own BlockIterator. 123 */ 124 private final List<BlockIterator> blockIters = 125 new LinkedList<BlockIterator>(); 126 127 /** 128 * Blocks which are suspect. 129 * The scanner prioritizes scanning these blocks. 130 */ 131 private final LinkedHashSet<ExtendedBlock> suspectBlocks = 132 new LinkedHashSet<ExtendedBlock>(); 133 134 /** 135 * Blocks which were suspect which we have scanned. 136 * This is used to avoid scanning the same suspect block over and over. 137 */ 138 private final Cache<ExtendedBlock, Boolean> recentSuspectBlocks = 139 CacheBuilder.newBuilder().maximumSize(1000) 140 .expireAfterAccess(10, TimeUnit.MINUTES).build(); 141 142 /** 143 * The current block iterator, or null if there is none. 144 */ 145 private BlockIterator curBlockIter = null; 146 147 /** 148 * True if the thread is stopping.<p/> 149 * Protected by this object's lock. 150 */ 151 private boolean stopping = false; 152 153 /** 154 * The monotonic minute that the volume scanner was started on. 155 */ 156 private long startMinute = 0; 157 158 /** 159 * The current minute, in monotonic terms. 160 */ 161 private long curMinute = 0; 162 163 /** 164 * Handles scan results. 165 */ 166 private final ScanResultHandler resultHandler; 167 168 private final Statistics stats = new Statistics(); 169 170 static class Statistics { 171 long bytesScannedInPastHour = 0; 172 long blocksScannedInCurrentPeriod = 0; 173 long blocksScannedSinceRestart = 0; 174 long scansSinceRestart = 0; 175 long scanErrorsSinceRestart = 0; 176 long nextBlockPoolScanStartMs = -1; 177 long blockPoolPeriodEndsMs = -1; 178 ExtendedBlock lastBlockScanned = null; 179 boolean eof = false; 180 181 Statistics() { 182 } 183 184 Statistics(Statistics other) { 185 this.bytesScannedInPastHour = other.bytesScannedInPastHour; 186 this.blocksScannedInCurrentPeriod = other.blocksScannedInCurrentPeriod; 187 this.blocksScannedSinceRestart = other.blocksScannedSinceRestart; 188 this.scansSinceRestart = other.scansSinceRestart; 189 this.scanErrorsSinceRestart = other.scanErrorsSinceRestart; 190 this.nextBlockPoolScanStartMs = other.nextBlockPoolScanStartMs; 191 this.blockPoolPeriodEndsMs = other.blockPoolPeriodEndsMs; 192 this.lastBlockScanned = other.lastBlockScanned; 193 this.eof = other.eof; 194 } 195 196 @Override 197 public String toString() { 198 return new StringBuilder(). 199 append("Statistics{"). 200 append("bytesScannedInPastHour=").append(bytesScannedInPastHour). 201 append(", blocksScannedInCurrentPeriod="). 202 append(blocksScannedInCurrentPeriod). 203 append(", blocksScannedSinceRestart="). 204 append(blocksScannedSinceRestart). 205 append(", scansSinceRestart=").append(scansSinceRestart). 206 append(", scanErrorsSinceRestart=").append(scanErrorsSinceRestart). 207 append(", nextBlockPoolScanStartMs=").append(nextBlockPoolScanStartMs). 208 append(", blockPoolPeriodEndsMs=").append(blockPoolPeriodEndsMs). 209 append(", lastBlockScanned=").append(lastBlockScanned). 210 append(", eof=").append(eof). 211 append("}").toString(); 212 } 213 } 214 215 private static double positiveMsToHours(long ms) { 216 if (ms <= 0) { 217 return 0; 218 } else { 219 return TimeUnit.HOURS.convert(ms, TimeUnit.MILLISECONDS); 220 } 221 } 222 223 public void printStats(StringBuilder p) { 224 p.append("Block scanner information for volume " + 225 volume.getStorageID() + " with base path " + volume.getBasePath() + 226 "%n"); 227 synchronized (stats) { 228 p.append(String.format("Bytes verified in last hour : %57d%n", 229 stats.bytesScannedInPastHour)); 230 p.append(String.format("Blocks scanned in current period : %57d%n", 231 stats.blocksScannedInCurrentPeriod)); 232 p.append(String.format("Blocks scanned since restart : %57d%n", 233 stats.blocksScannedSinceRestart)); 234 p.append(String.format("Block pool scans since restart : %57d%n", 235 stats.scansSinceRestart)); 236 p.append(String.format("Block scan errors since restart : %57d%n", 237 stats.scanErrorsSinceRestart)); 238 if (stats.nextBlockPoolScanStartMs > 0) { 239 p.append(String.format("Hours until next block pool scan : %57.3f%n", 240 positiveMsToHours(stats.nextBlockPoolScanStartMs - 241 Time.monotonicNow()))); 242 } 243 if (stats.blockPoolPeriodEndsMs > 0) { 244 p.append(String.format("Hours until possible pool rescan : %57.3f%n", 245 positiveMsToHours(stats.blockPoolPeriodEndsMs - 246 Time.now()))); 247 } 248 p.append(String.format("Last block scanned : %57s%n", 249 ((stats.lastBlockScanned == null) ? "none" : 250 stats.lastBlockScanned.toString()))); 251 p.append(String.format("More blocks to scan in period : %57s%n", 252 !stats.eof)); 253 p.append("%n"); 254 } 255 } 256 257 static class ScanResultHandler { 258 private VolumeScanner scanner; 259 260 public void setup(VolumeScanner scanner) { 261 LOG.trace("Starting VolumeScanner {}", 262 scanner.volume.getBasePath()); 263 this.scanner = scanner; 264 } 265 266 public void handle(ExtendedBlock block, IOException e) { 267 FsVolumeSpi volume = scanner.volume; 268 if (e == null) { 269 LOG.trace("Successfully scanned {} on {}", block, volume.getBasePath()); 270 return; 271 } 272 // If the block does not exist anymore, then it's not an error. 273 if (!volume.getDataset().contains(block)) { 274 LOG.debug("Volume {}: block {} is no longer in the dataset.", 275 volume.getBasePath(), block); 276 return; 277 } 278 // If the block exists, the exception may due to a race with write: 279 // The BlockSender got an old block path in rbw. BlockReceiver removed 280 // the rbw block from rbw to finalized but BlockSender tried to open the 281 // file before BlockReceiver updated the VolumeMap. The state of the 282 // block can be changed again now, so ignore this error here. If there 283 // is a block really deleted by mistake, DirectoryScan should catch it. 284 if (e instanceof FileNotFoundException ) { 285 LOG.info("Volume {}: verification failed for {} because of " + 286 "FileNotFoundException. This may be due to a race with write.", 287 volume.getBasePath(), block); 288 return; 289 } 290 LOG.warn("Reporting bad " + block + " with volume " 291 + volume.getBasePath(), e); 292 try { 293 scanner.datanode.reportBadBlocks(block, volume); 294 } catch (IOException ie) { 295 // This is bad, but not bad enough to shut down the scanner. 296 LOG.warn("Cannot report bad block " + block, ie); 297 } 298 } 299 } 300 301 VolumeScanner(Conf conf, DataNode datanode, FsVolumeReference ref) { 302 this.conf = conf; 303 this.datanode = datanode; 304 this.ref = ref; 305 this.volume = ref.getVolume(); 306 ScanResultHandler handler; 307 try { 308 handler = conf.resultHandler.newInstance(); 309 } catch (Throwable e) { 310 LOG.error("unable to instantiate {}", conf.resultHandler, e); 311 handler = new ScanResultHandler(); 312 } 313 this.resultHandler = handler; 314 setName("VolumeScannerThread(" + volume.getBasePath() + ")"); 315 setDaemon(true); 316 } 317 318 private void saveBlockIterator(BlockIterator iter) { 319 try { 320 iter.save(); 321 } catch (IOException e) { 322 LOG.warn("{}: error saving {}.", this, iter, e); 323 } 324 } 325 326 private void expireOldScannedBytesRecords(long monotonicMs) { 327 long newMinute = 328 TimeUnit.MINUTES.convert(monotonicMs, TimeUnit.MILLISECONDS); 329 if (curMinute == newMinute) { 330 return; 331 } 332 // If a minute or more has gone past since we last updated the scannedBytes 333 // array, zero out the slots corresponding to those minutes. 334 for (long m = curMinute + 1; m <= newMinute; m++) { 335 int slotIdx = (int)(m % MINUTES_PER_HOUR); 336 LOG.trace("{}: updateScannedBytes is zeroing out slotIdx {}. " + 337 "curMinute = {}; newMinute = {}", this, slotIdx, 338 curMinute, newMinute); 339 scannedBytesSum -= scannedBytes[slotIdx]; 340 scannedBytes[slotIdx] = 0; 341 } 342 curMinute = newMinute; 343 } 344 345 /** 346 * Find a usable block iterator.<p/> 347 * 348 * We will consider available block iterators in order. This property is 349 * important so that we don't keep rescanning the same block pool id over 350 * and over, while other block pools stay unscanned.<p/> 351 * 352 * A block pool is always ready to scan if the iterator is not at EOF. If 353 * the iterator is at EOF, the block pool will be ready to scan when 354 * conf.scanPeriodMs milliseconds have elapsed since the iterator was last 355 * rewound.<p/> 356 * 357 * @return 0 if we found a usable block iterator; the 358 * length of time we should delay before 359 * checking again otherwise. 360 */ 361 private synchronized long findNextUsableBlockIter() { 362 int numBlockIters = blockIters.size(); 363 if (numBlockIters == 0) { 364 LOG.debug("{}: no block pools are registered.", this); 365 return Long.MAX_VALUE; 366 } 367 int curIdx; 368 if (curBlockIter == null) { 369 curIdx = 0; 370 } else { 371 curIdx = blockIters.indexOf(curBlockIter); 372 Preconditions.checkState(curIdx >= 0); 373 } 374 // Note that this has to be wall-clock time, not monotonic time. This is 375 // because the time saved in the cursor file is a wall-clock time. We do 376 // not want to save a monotonic time in the cursor file, because it resets 377 // every time the machine reboots (on most platforms). 378 long nowMs = Time.now(); 379 long minTimeoutMs = Long.MAX_VALUE; 380 for (int i = 0; i < numBlockIters; i++) { 381 int idx = (curIdx + i + 1) % numBlockIters; 382 BlockIterator iter = blockIters.get(idx); 383 if (!iter.atEnd()) { 384 LOG.info("Now scanning bpid {} on volume {}", 385 iter.getBlockPoolId(), volume.getBasePath()); 386 curBlockIter = iter; 387 return 0L; 388 } 389 long iterStartMs = iter.getIterStartMs(); 390 long waitMs = (iterStartMs + conf.scanPeriodMs) - nowMs; 391 if (waitMs <= 0) { 392 iter.rewind(); 393 LOG.info("Now rescanning bpid {} on volume {}, after more than " + 394 "{} hour(s)", iter.getBlockPoolId(), volume.getBasePath(), 395 TimeUnit.HOURS.convert(conf.scanPeriodMs, TimeUnit.MILLISECONDS)); 396 curBlockIter = iter; 397 return 0L; 398 } 399 minTimeoutMs = Math.min(minTimeoutMs, waitMs); 400 } 401 LOG.info("{}: no suitable block pools found to scan. Waiting {} ms.", 402 this, minTimeoutMs); 403 return minTimeoutMs; 404 } 405 406 /** 407 * Scan a block. 408 * 409 * @param cblock The block to scan. 410 * @param bytesPerSec The bytes per second to scan at. 411 * 412 * @return The length of the block that was scanned, or 413 * -1 if the block could not be scanned. 414 */ 415 private long scanBlock(ExtendedBlock cblock, long bytesPerSec) { 416 // 'cblock' has a valid blockId and block pool id, but we don't yet know the 417 // genstamp the block is supposed to have. Ask the FsDatasetImpl for this 418 // information. 419 ExtendedBlock block = null; 420 try { 421 Block b = volume.getDataset().getStoredBlock( 422 cblock.getBlockPoolId(), cblock.getBlockId()); 423 if (b == null) { 424 LOG.info("FileNotFound while finding block {} on volume {}", 425 cblock, volume.getBasePath()); 426 } else { 427 block = new ExtendedBlock(cblock.getBlockPoolId(), b); 428 } 429 } catch (FileNotFoundException e) { 430 LOG.info("FileNotFoundException while finding block {} on volume {}", 431 cblock, volume.getBasePath()); 432 } catch (IOException e) { 433 LOG.warn("I/O error while finding block {} on volume {}", 434 cblock, volume.getBasePath()); 435 } 436 if (block == null) { 437 return -1; // block not found. 438 } 439 LOG.debug("start scanning block {}", block); 440 BlockSender blockSender = null; 441 try { 442 blockSender = new BlockSender(block, 0, -1, 443 false, true, true, datanode, null, 444 CachingStrategy.newDropBehind()); 445 throttler.setBandwidth(bytesPerSec); 446 long bytesRead = blockSender.sendBlock(nullStream, null, throttler); 447 resultHandler.handle(block, null); 448 return bytesRead; 449 } catch (IOException e) { 450 resultHandler.handle(block, e); 451 } finally { 452 IOUtils.cleanup(null, blockSender); 453 } 454 return -1; 455 } 456 457 @VisibleForTesting 458 static boolean calculateShouldScan(String storageId, long targetBytesPerSec, 459 long scannedBytesSum, long startMinute, long curMinute) { 460 long runMinutes = curMinute - startMinute; 461 long effectiveBytesPerSec; 462 if (runMinutes <= 0) { 463 // avoid division by zero 464 effectiveBytesPerSec = scannedBytesSum; 465 } else { 466 if (runMinutes > MINUTES_PER_HOUR) { 467 // we only keep an hour's worth of rate information 468 runMinutes = MINUTES_PER_HOUR; 469 } 470 effectiveBytesPerSec = scannedBytesSum / 471 (SECONDS_PER_MINUTE * runMinutes); 472 } 473 474 boolean shouldScan = effectiveBytesPerSec <= targetBytesPerSec; 475 LOG.trace("{}: calculateShouldScan: effectiveBytesPerSec = {}, and " + 476 "targetBytesPerSec = {}. startMinute = {}, curMinute = {}, " + 477 "shouldScan = {}", 478 storageId, effectiveBytesPerSec, targetBytesPerSec, 479 startMinute, curMinute, shouldScan); 480 return shouldScan; 481 } 482 483 /** 484 * Run an iteration of the VolumeScanner loop. 485 * 486 * @param suspectBlock A suspect block which we should scan, or null to 487 * scan the next regularly scheduled block. 488 * 489 * @return The number of milliseconds to delay before running the loop 490 * again, or 0 to re-run the loop immediately. 491 */ 492 private long runLoop(ExtendedBlock suspectBlock) { 493 long bytesScanned = -1; 494 boolean scanError = false; 495 ExtendedBlock block = null; 496 try { 497 long monotonicMs = Time.monotonicNow(); 498 expireOldScannedBytesRecords(monotonicMs); 499 500 if (!calculateShouldScan(volume.getStorageID(), conf.targetBytesPerSec, 501 scannedBytesSum, startMinute, curMinute)) { 502 // If neededBytesPerSec is too low, then wait few seconds for some old 503 // scannedBytes records to expire. 504 return 30000L; 505 } 506 507 // Find a usable block pool to scan. 508 if (suspectBlock != null) { 509 block = suspectBlock; 510 } else { 511 if ((curBlockIter == null) || curBlockIter.atEnd()) { 512 long timeout = findNextUsableBlockIter(); 513 if (timeout > 0) { 514 LOG.trace("{}: no block pools are ready to scan yet. Waiting " + 515 "{} ms.", this, timeout); 516 synchronized (stats) { 517 stats.nextBlockPoolScanStartMs = Time.monotonicNow() + timeout; 518 } 519 return timeout; 520 } 521 synchronized (stats) { 522 stats.scansSinceRestart++; 523 stats.blocksScannedInCurrentPeriod = 0; 524 stats.nextBlockPoolScanStartMs = -1; 525 } 526 return 0L; 527 } 528 try { 529 block = curBlockIter.nextBlock(); 530 } catch (IOException e) { 531 // There was an error listing the next block in the volume. This is a 532 // serious issue. 533 LOG.warn("{}: nextBlock error on {}", this, curBlockIter); 534 // On the next loop iteration, curBlockIter#eof will be set to true, and 535 // we will pick a different block iterator. 536 return 0L; 537 } 538 if (block == null) { 539 // The BlockIterator is at EOF. 540 LOG.info("{}: finished scanning block pool {}", 541 this, curBlockIter.getBlockPoolId()); 542 saveBlockIterator(curBlockIter); 543 return 0; 544 } 545 } 546 if (curBlockIter != null) { 547 long saveDelta = monotonicMs - curBlockIter.getLastSavedMs(); 548 if (saveDelta >= conf.cursorSaveMs) { 549 LOG.debug("{}: saving block iterator {} after {} ms.", 550 this, curBlockIter, saveDelta); 551 saveBlockIterator(curBlockIter); 552 } 553 } 554 bytesScanned = scanBlock(block, conf.targetBytesPerSec); 555 if (bytesScanned >= 0) { 556 scannedBytesSum += bytesScanned; 557 scannedBytes[(int)(curMinute % MINUTES_PER_HOUR)] += bytesScanned; 558 } else { 559 scanError = true; 560 } 561 return 0L; 562 } finally { 563 synchronized (stats) { 564 stats.bytesScannedInPastHour = scannedBytesSum; 565 if (bytesScanned > 0) { 566 stats.blocksScannedInCurrentPeriod++; 567 stats.blocksScannedSinceRestart++; 568 } 569 if (scanError) { 570 stats.scanErrorsSinceRestart++; 571 } 572 if (block != null) { 573 stats.lastBlockScanned = block; 574 } 575 if (curBlockIter == null) { 576 stats.eof = true; 577 stats.blockPoolPeriodEndsMs = -1; 578 } else { 579 stats.eof = curBlockIter.atEnd(); 580 stats.blockPoolPeriodEndsMs = 581 curBlockIter.getIterStartMs() + conf.scanPeriodMs; 582 } 583 } 584 } 585 } 586 587 /** 588 * If there are elements in the suspectBlocks list, removes 589 * and returns the first one. Otherwise, returns null. 590 */ 591 private synchronized ExtendedBlock popNextSuspectBlock() { 592 Iterator<ExtendedBlock> iter = suspectBlocks.iterator(); 593 if (!iter.hasNext()) { 594 return null; 595 } 596 ExtendedBlock block = iter.next(); 597 iter.remove(); 598 return block; 599 } 600 601 @Override 602 public void run() { 603 // Record the minute on which the scanner started. 604 this.startMinute = 605 TimeUnit.MINUTES.convert(Time.monotonicNow(), TimeUnit.MILLISECONDS); 606 this.curMinute = startMinute; 607 try { 608 LOG.trace("{}: thread starting.", this); 609 resultHandler.setup(this); 610 try { 611 long timeout = 0; 612 while (true) { 613 ExtendedBlock suspectBlock = null; 614 // Take the lock to check if we should stop, and access the 615 // suspect block list. 616 synchronized (this) { 617 if (stopping) { 618 break; 619 } 620 if (timeout > 0) { 621 LOG.debug("{}: wait for {} milliseconds", this, timeout); 622 wait(timeout); 623 if (stopping) { 624 break; 625 } 626 } 627 suspectBlock = popNextSuspectBlock(); 628 } 629 timeout = runLoop(suspectBlock); 630 } 631 } catch (InterruptedException e) { 632 // We are exiting because of an InterruptedException, 633 // probably sent by VolumeScanner#shutdown. 634 LOG.trace("{} exiting because of InterruptedException.", this); 635 } catch (Throwable e) { 636 LOG.error("{} exiting because of exception ", this, e); 637 } 638 LOG.info("{} exiting.", this); 639 // Save the current position of all block iterators and close them. 640 for (BlockIterator iter : blockIters) { 641 saveBlockIterator(iter); 642 IOUtils.cleanup(null, iter); 643 } 644 } finally { 645 // When the VolumeScanner exits, release the reference we were holding 646 // on the volume. This will allow the volume to be removed later. 647 IOUtils.cleanup(null, ref); 648 } 649 } 650 651 @Override 652 public String toString() { 653 return "VolumeScanner(" + volume.getBasePath() + 654 ", " + volume.getStorageID() + ")"; 655 } 656 657 /** 658 * Shut down this scanner. 659 */ 660 public synchronized void shutdown() { 661 stopping = true; 662 notify(); 663 this.interrupt(); 664 } 665 666 667 public synchronized void markSuspectBlock(ExtendedBlock block) { 668 if (stopping) { 669 LOG.debug("{}: Not scheduling suspect block {} for " + 670 "rescanning, because this volume scanner is stopping.", this, block); 671 return; 672 } 673 Boolean recent = recentSuspectBlocks.getIfPresent(block); 674 if (recent != null) { 675 LOG.debug("{}: Not scheduling suspect block {} for " + 676 "rescanning, because we rescanned it recently.", this, block); 677 return; 678 } 679 if (suspectBlocks.contains(block)) { 680 LOG.debug("{}: suspect block {} is already queued for " + 681 "rescanning.", this, block); 682 return; 683 } 684 suspectBlocks.add(block); 685 recentSuspectBlocks.put(block, true); 686 LOG.debug("{}: Scheduling suspect block {} for rescanning.", this, block); 687 notify(); // wake scanner thread. 688 } 689 690 /** 691 * Allow the scanner to scan the given block pool. 692 * 693 * @param bpid The block pool id. 694 */ 695 public synchronized void enableBlockPoolId(String bpid) { 696 for (BlockIterator iter : blockIters) { 697 if (iter.getBlockPoolId().equals(bpid)) { 698 LOG.warn("{}: already enabled scanning on block pool {}", this, bpid); 699 return; 700 } 701 } 702 BlockIterator iter = null; 703 try { 704 // Load a block iterator for the next block pool on the volume. 705 iter = volume.loadBlockIterator(bpid, BLOCK_ITERATOR_NAME); 706 LOG.trace("{}: loaded block iterator for {}.", this, bpid); 707 } catch (FileNotFoundException e) { 708 LOG.debug("{}: failed to load block iterator: " + e.getMessage(), this); 709 } catch (IOException e) { 710 LOG.warn("{}: failed to load block iterator.", this, e); 711 } 712 if (iter == null) { 713 iter = volume.newBlockIterator(bpid, BLOCK_ITERATOR_NAME); 714 LOG.trace("{}: created new block iterator for {}.", this, bpid); 715 } 716 iter.setMaxStalenessMs(conf.maxStalenessMs); 717 blockIters.add(iter); 718 notify(); 719 } 720 721 /** 722 * Disallow the scanner from scanning the given block pool. 723 * 724 * @param bpid The block pool id. 725 */ 726 public synchronized void disableBlockPoolId(String bpid) { 727 Iterator<BlockIterator> i = blockIters.iterator(); 728 while (i.hasNext()) { 729 BlockIterator iter = i.next(); 730 if (iter.getBlockPoolId().equals(bpid)) { 731 LOG.trace("{}: disabling scanning on block pool {}", this, bpid); 732 i.remove(); 733 IOUtils.cleanup(null, iter); 734 if (curBlockIter == iter) { 735 curBlockIter = null; 736 } 737 notify(); 738 return; 739 } 740 } 741 LOG.warn("{}: can't remove block pool {}, because it was never " + 742 "added.", this, bpid); 743 } 744 745 @VisibleForTesting 746 Statistics getStatistics() { 747 synchronized (stats) { 748 return new Statistics(stats); 749 } 750 } 751}