|
@@ -0,0 +1,143 @@
|
|
|
+/**
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
+ * distributed with this work for additional information
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+package org.apache.hadoop.hdfs.server.blockmanagement;
|
|
|
+
|
|
|
+import com.google.common.annotations.VisibleForTesting;
|
|
|
+import org.apache.hadoop.hdfs.util.LightWeightHashSet;
|
|
|
+import org.apache.hadoop.util.Time;
|
|
|
+import org.slf4j.Logger;
|
|
|
+
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * PendingRecoveryBlocks tracks recovery attempts for each block and their
|
|
|
+ * timeouts to ensure we do not have multiple recoveries at the same time
|
|
|
+ * and retry only after the timeout for a recovery has expired.
|
|
|
+ */
|
|
|
+class PendingRecoveryBlocks {
|
|
|
+ private static final Logger LOG = BlockManager.LOG;
|
|
|
+
|
|
|
+ /** List of recovery attempts per block and the time they expire. */
|
|
|
+ private final LightWeightHashSet<BlockRecoveryAttempt> recoveryTimeouts =
|
|
|
+ new LightWeightHashSet<>();
|
|
|
+
|
|
|
+ /** The timeout for issuing a block recovery again.
|
|
|
+ * (it should be larger than the time to recover a block)
|
|
|
+ */
|
|
|
+ private long recoveryTimeoutInterval;
|
|
|
+
|
|
|
+ PendingRecoveryBlocks(long timeout) {
|
|
|
+ this.recoveryTimeoutInterval = timeout;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Remove recovery attempt for the given block.
|
|
|
+ * @param block whose recovery attempt to remove.
|
|
|
+ */
|
|
|
+ synchronized void remove(BlockInfo block) {
|
|
|
+ recoveryTimeouts.remove(new BlockRecoveryAttempt(block));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks whether a recovery attempt has been made for the given block.
|
|
|
+ * If so, checks whether that attempt has timed out.
|
|
|
+ * @param block block for which recovery is being attempted
|
|
|
+ * @return true if no recovery attempt has been made or
|
|
|
+ * the previous attempt timed out
|
|
|
+ */
|
|
|
+ synchronized boolean add(BlockInfo block) {
|
|
|
+ boolean added = false;
|
|
|
+ long curTime = getTime();
|
|
|
+ BlockRecoveryAttempt recoveryAttempt =
|
|
|
+ recoveryTimeouts.getElement(new BlockRecoveryAttempt(block));
|
|
|
+
|
|
|
+ if (recoveryAttempt == null) {
|
|
|
+ BlockRecoveryAttempt newAttempt = new BlockRecoveryAttempt(
|
|
|
+ block, curTime + recoveryTimeoutInterval);
|
|
|
+ added = recoveryTimeouts.add(newAttempt);
|
|
|
+ } else if (recoveryAttempt.hasTimedOut(curTime)) {
|
|
|
+ // Previous attempt timed out, reset the timeout
|
|
|
+ recoveryAttempt.setTimeout(curTime + recoveryTimeoutInterval);
|
|
|
+ added = true;
|
|
|
+ } else {
|
|
|
+ long timeoutIn = TimeUnit.MILLISECONDS.toSeconds(
|
|
|
+ recoveryAttempt.timeoutAt - curTime);
|
|
|
+ LOG.info("Block recovery attempt for " + block + " rejected, as the " +
|
|
|
+ "previous attempt times out in " + timeoutIn + " seconds.");
|
|
|
+ }
|
|
|
+ return added;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check whether the given block is under recovery.
|
|
|
+ * @param b block for which to check
|
|
|
+ * @return true if the given block is being recovered
|
|
|
+ */
|
|
|
+ synchronized boolean isUnderRecovery(BlockInfo b) {
|
|
|
+ BlockRecoveryAttempt recoveryAttempt =
|
|
|
+ recoveryTimeouts.getElement(new BlockRecoveryAttempt(b));
|
|
|
+ return recoveryAttempt != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ long getTime() {
|
|
|
+ return Time.monotonicNow();
|
|
|
+ }
|
|
|
+
|
|
|
+ @VisibleForTesting
|
|
|
+ synchronized void setRecoveryTimeoutInterval(long recoveryTimeoutInterval) {
|
|
|
+ this.recoveryTimeoutInterval = recoveryTimeoutInterval;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tracks timeout for block recovery attempt of a given block.
|
|
|
+ */
|
|
|
+ private static class BlockRecoveryAttempt {
|
|
|
+ private final BlockInfo blockInfo;
|
|
|
+ private long timeoutAt;
|
|
|
+
|
|
|
+ private BlockRecoveryAttempt(BlockInfo blockInfo) {
|
|
|
+ this(blockInfo, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ BlockRecoveryAttempt(BlockInfo blockInfo, long timeoutAt) {
|
|
|
+ this.blockInfo = blockInfo;
|
|
|
+ this.timeoutAt = timeoutAt;
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean hasTimedOut(long currentTime) {
|
|
|
+ return currentTime > timeoutAt;
|
|
|
+ }
|
|
|
+
|
|
|
+ void setTimeout(long newTimeoutAt) {
|
|
|
+ this.timeoutAt = newTimeoutAt;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int hashCode() {
|
|
|
+ return blockInfo.hashCode();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean equals(Object obj) {
|
|
|
+ if (obj instanceof BlockRecoveryAttempt) {
|
|
|
+ return this.blockInfo.equals(((BlockRecoveryAttempt) obj).blockInfo);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|