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}