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}