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(String.format("Block scanner information for volume %s with base" + 225 " path %s%n", volume.getStorageID(), volume.getBasePath())); 226 synchronized (stats) { 227 p.append(String.format("Bytes verified in last hour : %57d%n", 228 stats.bytesScannedInPastHour)); 229 p.append(String.format("Blocks scanned in current period : %57d%n", 230 stats.blocksScannedInCurrentPeriod)); 231 p.append(String.format("Blocks scanned since restart : %57d%n", 232 stats.blocksScannedSinceRestart)); 233 p.append(String.format("Block pool scans since restart : %57d%n", 234 stats.scansSinceRestart)); 235 p.append(String.format("Block scan errors since restart : %57d%n", 236 stats.scanErrorsSinceRestart)); 237 if (stats.nextBlockPoolScanStartMs > 0) { 238 p.append(String.format("Hours until next block pool scan : %57.3f%n", 239 positiveMsToHours(stats.nextBlockPoolScanStartMs - 240 Time.monotonicNow()))); 241 } 242 if (stats.blockPoolPeriodEndsMs > 0) { 243 p.append(String.format("Hours until possible pool rescan : %57.3f%n", 244 positiveMsToHours(stats.blockPoolPeriodEndsMs - 245 Time.now()))); 246 } 247 p.append(String.format("Last block scanned : %57s%n", 248 ((stats.lastBlockScanned == null) ? "none" : 249 stats.lastBlockScanned.toString()))); 250 p.append(String.format("More blocks to scan in period : %57s%n", 251 !stats.eof)); 252 p.append(System.lineSeparator()); 253 } 254 } 255 256 static class ScanResultHandler { 257 private VolumeScanner scanner; 258 259 public void setup(VolumeScanner scanner) { 260 LOG.trace("Starting VolumeScanner {}", 261 scanner.volume.getBasePath()); 262 this.scanner = scanner; 263 } 264 265 public void handle(ExtendedBlock block, IOException e) { 266 FsVolumeSpi volume = scanner.volume; 267 if (e == null) { 268 LOG.trace("Successfully scanned {} on {}", block, volume.getBasePath()); 269 return; 270 } 271 // If the block does not exist anymore, then it's not an error. 272 if (!volume.getDataset().contains(block)) { 273 LOG.debug("Volume {}: block {} is no longer in the dataset.", 274 volume.getBasePath(), block); 275 return; 276 } 277 // If the block exists, the exception may due to a race with write: 278 // The BlockSender got an old block path in rbw. BlockReceiver removed 279 // the rbw block from rbw to finalized but BlockSender tried to open the 280 // file before BlockReceiver updated the VolumeMap. The state of the 281 // block can be changed again now, so ignore this error here. If there 282 // is a block really deleted by mistake, DirectoryScan should catch it. 283 if (e instanceof FileNotFoundException ) { 284 LOG.info("Volume {}: verification failed for {} because of " + 285 "FileNotFoundException. This may be due to a race with write.", 286 volume.getBasePath(), block); 287 return; 288 } 289 LOG.warn("Reporting bad " + block + " with volume " 290 + volume.getBasePath(), e); 291 try { 292 scanner.datanode.reportBadBlocks(block, volume); 293 } catch (IOException ie) { 294 // This is bad, but not bad enough to shut down the scanner. 295 LOG.warn("Cannot report bad block " + block, ie); 296 } 297 } 298 } 299 300 VolumeScanner(Conf conf, DataNode datanode, FsVolumeReference ref) { 301 this.conf = conf; 302 this.datanode = datanode; 303 this.ref = ref; 304 this.volume = ref.getVolume(); 305 ScanResultHandler handler; 306 try { 307 handler = conf.resultHandler.newInstance(); 308 } catch (Throwable e) { 309 LOG.error("unable to instantiate {}", conf.resultHandler, e); 310 handler = new ScanResultHandler(); 311 } 312 this.resultHandler = handler; 313 setName("VolumeScannerThread(" + volume.getBasePath() + ")"); 314 setDaemon(true); 315 } 316 317 private void saveBlockIterator(BlockIterator iter) { 318 try { 319 iter.save(); 320 } catch (IOException e) { 321 LOG.warn("{}: error saving {}.", this, iter, e); 322 } 323 } 324 325 private void expireOldScannedBytesRecords(long monotonicMs) { 326 long newMinute = 327 TimeUnit.MINUTES.convert(monotonicMs, TimeUnit.MILLISECONDS); 328 if (curMinute == newMinute) { 329 return; 330 } 331 // If a minute or more has gone past since we last updated the scannedBytes 332 // array, zero out the slots corresponding to those minutes. 333 for (long m = curMinute + 1; m <= newMinute; m++) { 334 int slotIdx = (int)(m % MINUTES_PER_HOUR); 335 LOG.trace("{}: updateScannedBytes is zeroing out slotIdx {}. " + 336 "curMinute = {}; newMinute = {}", this, slotIdx, 337 curMinute, newMinute); 338 scannedBytesSum -= scannedBytes[slotIdx]; 339 scannedBytes[slotIdx] = 0; 340 } 341 curMinute = newMinute; 342 } 343 344 /** 345 * Find a usable block iterator.<p/> 346 * 347 * We will consider available block iterators in order. This property is 348 * important so that we don't keep rescanning the same block pool id over 349 * and over, while other block pools stay unscanned.<p/> 350 * 351 * A block pool is always ready to scan if the iterator is not at EOF. If 352 * the iterator is at EOF, the block pool will be ready to scan when 353 * conf.scanPeriodMs milliseconds have elapsed since the iterator was last 354 * rewound.<p/> 355 * 356 * @return 0 if we found a usable block iterator; the 357 * length of time we should delay before 358 * checking again otherwise. 359 */ 360 private synchronized long findNextUsableBlockIter() { 361 int numBlockIters = blockIters.size(); 362 if (numBlockIters == 0) { 363 LOG.debug("{}: no block pools are registered.", this); 364 return Long.MAX_VALUE; 365 } 366 int curIdx; 367 if (curBlockIter == null) { 368 curIdx = 0; 369 } else { 370 curIdx = blockIters.indexOf(curBlockIter); 371 Preconditions.checkState(curIdx >= 0); 372 } 373 // Note that this has to be wall-clock time, not monotonic time. This is 374 // because the time saved in the cursor file is a wall-clock time. We do 375 // not want to save a monotonic time in the cursor file, because it resets 376 // every time the machine reboots (on most platforms). 377 long nowMs = Time.now(); 378 long minTimeoutMs = Long.MAX_VALUE; 379 for (int i = 0; i < numBlockIters; i++) { 380 int idx = (curIdx + i + 1) % numBlockIters; 381 BlockIterator iter = blockIters.get(idx); 382 if (!iter.atEnd()) { 383 LOG.info("Now scanning bpid {} on volume {}", 384 iter.getBlockPoolId(), volume.getBasePath()); 385 curBlockIter = iter; 386 return 0L; 387 } 388 long iterStartMs = iter.getIterStartMs(); 389 long waitMs = (iterStartMs + conf.scanPeriodMs) - nowMs; 390 if (waitMs <= 0) { 391 iter.rewind(); 392 LOG.info("Now rescanning bpid {} on volume {}, after more than " + 393 "{} hour(s)", iter.getBlockPoolId(), volume.getBasePath(), 394 TimeUnit.HOURS.convert(conf.scanPeriodMs, TimeUnit.MILLISECONDS)); 395 curBlockIter = iter; 396 return 0L; 397 } 398 minTimeoutMs = Math.min(minTimeoutMs, waitMs); 399 } 400 LOG.info("{}: no suitable block pools found to scan. Waiting {} ms.", 401 this, minTimeoutMs); 402 return minTimeoutMs; 403 } 404 405 /** 406 * Scan a block. 407 * 408 * @param cblock The block to scan. 409 * @param bytesPerSec The bytes per second to scan at. 410 * 411 * @return The length of the block that was scanned, or 412 * -1 if the block could not be scanned. 413 */ 414 private long scanBlock(ExtendedBlock cblock, long bytesPerSec) { 415 // 'cblock' has a valid blockId and block pool id, but we don't yet know the 416 // genstamp the block is supposed to have. Ask the FsDatasetImpl for this 417 // information. 418 ExtendedBlock block = null; 419 try { 420 Block b = volume.getDataset().getStoredBlock( 421 cblock.getBlockPoolId(), cblock.getBlockId()); 422 if (b == null) { 423 LOG.info("FileNotFound while finding block {} on volume {}", 424 cblock, volume.getBasePath()); 425 } else { 426 block = new ExtendedBlock(cblock.getBlockPoolId(), b); 427 } 428 } catch (FileNotFoundException e) { 429 LOG.info("FileNotFoundException while finding block {} on volume {}", 430 cblock, volume.getBasePath()); 431 } catch (IOException e) { 432 LOG.warn("I/O error while finding block {} on volume {}", 433 cblock, volume.getBasePath()); 434 } 435 if (block == null) { 436 return -1; // block not found. 437 } 438 LOG.debug("start scanning block {}", block); 439 BlockSender blockSender = null; 440 try { 441 blockSender = new BlockSender(block, 0, -1, 442 false, true, true, datanode, null, 443 CachingStrategy.newDropBehind()); 444 throttler.setBandwidth(bytesPerSec); 445 long bytesRead = blockSender.sendBlock(nullStream, null, throttler); 446 resultHandler.handle(block, null); 447 return bytesRead; 448 } catch (IOException e) { 449 resultHandler.handle(block, e); 450 } finally { 451 IOUtils.cleanup(null, blockSender); 452 } 453 return -1; 454 } 455 456 @VisibleForTesting 457 static boolean calculateShouldScan(String storageId, long targetBytesPerSec, 458 long scannedBytesSum, long startMinute, long curMinute) { 459 long runMinutes = curMinute - startMinute; 460 long effectiveBytesPerSec; 461 if (runMinutes <= 0) { 462 // avoid division by zero 463 effectiveBytesPerSec = scannedBytesSum; 464 } else { 465 if (runMinutes > MINUTES_PER_HOUR) { 466 // we only keep an hour's worth of rate information 467 runMinutes = MINUTES_PER_HOUR; 468 } 469 effectiveBytesPerSec = scannedBytesSum / 470 (SECONDS_PER_MINUTE * runMinutes); 471 } 472 473 boolean shouldScan = effectiveBytesPerSec <= targetBytesPerSec; 474 LOG.trace("{}: calculateShouldScan: effectiveBytesPerSec = {}, and " + 475 "targetBytesPerSec = {}. startMinute = {}, curMinute = {}, " + 476 "shouldScan = {}", 477 storageId, effectiveBytesPerSec, targetBytesPerSec, 478 startMinute, curMinute, shouldScan); 479 return shouldScan; 480 } 481 482 /** 483 * Run an iteration of the VolumeScanner loop. 484 * 485 * @param suspectBlock A suspect block which we should scan, or null to 486 * scan the next regularly scheduled block. 487 * 488 * @return The number of milliseconds to delay before running the loop 489 * again, or 0 to re-run the loop immediately. 490 */ 491 private long runLoop(ExtendedBlock suspectBlock) { 492 long bytesScanned = -1; 493 boolean scanError = false; 494 ExtendedBlock block = null; 495 try { 496 long monotonicMs = Time.monotonicNow(); 497 expireOldScannedBytesRecords(monotonicMs); 498 499 if (!calculateShouldScan(volume.getStorageID(), conf.targetBytesPerSec, 500 scannedBytesSum, startMinute, curMinute)) { 501 // If neededBytesPerSec is too low, then wait few seconds for some old 502 // scannedBytes records to expire. 503 return 30000L; 504 } 505 506 // Find a usable block pool to scan. 507 if (suspectBlock != null) { 508 block = suspectBlock; 509 } else { 510 if ((curBlockIter == null) || curBlockIter.atEnd()) { 511 long timeout = findNextUsableBlockIter(); 512 if (timeout > 0) { 513 LOG.trace("{}: no block pools are ready to scan yet. Waiting " + 514 "{} ms.", this, timeout); 515 synchronized (stats) { 516 stats.nextBlockPoolScanStartMs = Time.monotonicNow() + timeout; 517 } 518 return timeout; 519 } 520 synchronized (stats) { 521 stats.scansSinceRestart++; 522 stats.blocksScannedInCurrentPeriod = 0; 523 stats.nextBlockPoolScanStartMs = -1; 524 } 525 return 0L; 526 } 527 try { 528 block = curBlockIter.nextBlock(); 529 } catch (IOException e) { 530 // There was an error listing the next block in the volume. This is a 531 // serious issue. 532 LOG.warn("{}: nextBlock error on {}", this, curBlockIter); 533 // On the next loop iteration, curBlockIter#eof will be set to true, and 534 // we will pick a different block iterator. 535 return 0L; 536 } 537 if (block == null) { 538 // The BlockIterator is at EOF. 539 LOG.info("{}: finished scanning block pool {}", 540 this, curBlockIter.getBlockPoolId()); 541 saveBlockIterator(curBlockIter); 542 return 0; 543 } 544 } 545 if (curBlockIter != null) { 546 long saveDelta = monotonicMs - curBlockIter.getLastSavedMs(); 547 if (saveDelta >= conf.cursorSaveMs) { 548 LOG.debug("{}: saving block iterator {} after {} ms.", 549 this, curBlockIter, saveDelta); 550 saveBlockIterator(curBlockIter); 551 } 552 } 553 bytesScanned = scanBlock(block, conf.targetBytesPerSec); 554 if (bytesScanned >= 0) { 555 scannedBytesSum += bytesScanned; 556 scannedBytes[(int)(curMinute % MINUTES_PER_HOUR)] += bytesScanned; 557 } else { 558 scanError = true; 559 } 560 return 0L; 561 } finally { 562 synchronized (stats) { 563 stats.bytesScannedInPastHour = scannedBytesSum; 564 if (bytesScanned > 0) { 565 stats.blocksScannedInCurrentPeriod++; 566 stats.blocksScannedSinceRestart++; 567 } 568 if (scanError) { 569 stats.scanErrorsSinceRestart++; 570 } 571 if (block != null) { 572 stats.lastBlockScanned = block; 573 } 574 if (curBlockIter == null) { 575 stats.eof = true; 576 stats.blockPoolPeriodEndsMs = -1; 577 } else { 578 stats.eof = curBlockIter.atEnd(); 579 stats.blockPoolPeriodEndsMs = 580 curBlockIter.getIterStartMs() + conf.scanPeriodMs; 581 } 582 } 583 } 584 } 585 586 /** 587 * If there are elements in the suspectBlocks list, removes 588 * and returns the first one. Otherwise, returns null. 589 */ 590 private synchronized ExtendedBlock popNextSuspectBlock() { 591 Iterator<ExtendedBlock> iter = suspectBlocks.iterator(); 592 if (!iter.hasNext()) { 593 return null; 594 } 595 ExtendedBlock block = iter.next(); 596 iter.remove(); 597 return block; 598 } 599 600 @Override 601 public void run() { 602 // Record the minute on which the scanner started. 603 this.startMinute = 604 TimeUnit.MINUTES.convert(Time.monotonicNow(), TimeUnit.MILLISECONDS); 605 this.curMinute = startMinute; 606 try { 607 LOG.trace("{}: thread starting.", this); 608 resultHandler.setup(this); 609 try { 610 long timeout = 0; 611 while (true) { 612 ExtendedBlock suspectBlock = null; 613 // Take the lock to check if we should stop, and access the 614 // suspect block list. 615 synchronized (this) { 616 if (stopping) { 617 break; 618 } 619 if (timeout > 0) { 620 LOG.debug("{}: wait for {} milliseconds", this, timeout); 621 wait(timeout); 622 if (stopping) { 623 break; 624 } 625 } 626 suspectBlock = popNextSuspectBlock(); 627 } 628 timeout = runLoop(suspectBlock); 629 } 630 } catch (InterruptedException e) { 631 // We are exiting because of an InterruptedException, 632 // probably sent by VolumeScanner#shutdown. 633 LOG.trace("{} exiting because of InterruptedException.", this); 634 } catch (Throwable e) { 635 LOG.error("{} exiting because of exception ", this, e); 636 } 637 LOG.info("{} exiting.", this); 638 // Save the current position of all block iterators and close them. 639 for (BlockIterator iter : blockIters) { 640 saveBlockIterator(iter); 641 IOUtils.cleanup(null, iter); 642 } 643 } finally { 644 // When the VolumeScanner exits, release the reference we were holding 645 // on the volume. This will allow the volume to be removed later. 646 IOUtils.cleanup(null, ref); 647 } 648 } 649 650 @Override 651 public String toString() { 652 return "VolumeScanner(" + volume.getBasePath() + 653 ", " + volume.getStorageID() + ")"; 654 } 655 656 /** 657 * Shut down this scanner. 658 */ 659 public synchronized void shutdown() { 660 stopping = true; 661 notify(); 662 this.interrupt(); 663 } 664 665 666 public synchronized void markSuspectBlock(ExtendedBlock block) { 667 if (stopping) { 668 LOG.debug("{}: Not scheduling suspect block {} for " + 669 "rescanning, because this volume scanner is stopping.", this, block); 670 return; 671 } 672 Boolean recent = recentSuspectBlocks.getIfPresent(block); 673 if (recent != null) { 674 LOG.debug("{}: Not scheduling suspect block {} for " + 675 "rescanning, because we rescanned it recently.", this, block); 676 return; 677 } 678 if (suspectBlocks.contains(block)) { 679 LOG.debug("{}: suspect block {} is already queued for " + 680 "rescanning.", this, block); 681 return; 682 } 683 suspectBlocks.add(block); 684 recentSuspectBlocks.put(block, true); 685 LOG.debug("{}: Scheduling suspect block {} for rescanning.", this, block); 686 notify(); // wake scanner thread. 687 } 688 689 /** 690 * Allow the scanner to scan the given block pool. 691 * 692 * @param bpid The block pool id. 693 */ 694 public synchronized void enableBlockPoolId(String bpid) { 695 for (BlockIterator iter : blockIters) { 696 if (iter.getBlockPoolId().equals(bpid)) { 697 LOG.warn("{}: already enabled scanning on block pool {}", this, bpid); 698 return; 699 } 700 } 701 BlockIterator iter = null; 702 try { 703 // Load a block iterator for the next block pool on the volume. 704 iter = volume.loadBlockIterator(bpid, BLOCK_ITERATOR_NAME); 705 LOG.trace("{}: loaded block iterator for {}.", this, bpid); 706 } catch (FileNotFoundException e) { 707 LOG.debug("{}: failed to load block iterator: " + e.getMessage(), this); 708 } catch (IOException e) { 709 LOG.warn("{}: failed to load block iterator.", this, e); 710 } 711 if (iter == null) { 712 iter = volume.newBlockIterator(bpid, BLOCK_ITERATOR_NAME); 713 LOG.trace("{}: created new block iterator for {}.", this, bpid); 714 } 715 iter.setMaxStalenessMs(conf.maxStalenessMs); 716 blockIters.add(iter); 717 notify(); 718 } 719 720 /** 721 * Disallow the scanner from scanning the given block pool. 722 * 723 * @param bpid The block pool id. 724 */ 725 public synchronized void disableBlockPoolId(String bpid) { 726 Iterator<BlockIterator> i = blockIters.iterator(); 727 while (i.hasNext()) { 728 BlockIterator iter = i.next(); 729 if (iter.getBlockPoolId().equals(bpid)) { 730 LOG.trace("{}: disabling scanning on block pool {}", this, bpid); 731 i.remove(); 732 IOUtils.cleanup(null, iter); 733 if (curBlockIter == iter) { 734 curBlockIter = null; 735 } 736 notify(); 737 return; 738 } 739 } 740 LOG.warn("{}: can't remove block pool {}, because it was never " + 741 "added.", this, bpid); 742 } 743 744 @VisibleForTesting 745 Statistics getStatistics() { 746 synchronized (stats) { 747 return new Statistics(stats); 748 } 749 } 750}