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