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}