Explorar o código

svn merge -c 1330552 FIXES: MAPREDUCE-4194. ConcurrentModificationError in DirectoryCollection (Jonathan Eagles via bobby)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.23@1330556 13f79535-47bb-0310-9956-ffa450edef68
Robert Joseph Evans %!s(int64=13) %!d(string=hai) anos
pai
achega
44beeb076b

+ 3 - 0
hadoop-mapreduce-project/CHANGES.txt

@@ -143,6 +143,9 @@ Release 0.23.3 - UNRELEASED
 
     MAPREDUCE-4133. MR over viewfs is broken (John George via bobby)
 
+    MAPREDUCE-4194. ConcurrentModificationError in DirectoryCollection
+    (Jonathan Eagles via bobby)
+
 Release 0.23.2 - UNRELEASED
 
   INCOMPATIBLE CHANGES

+ 9 - 16
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/DirectoryCollection.java

@@ -19,10 +19,9 @@
 package org.apache.hadoop.yarn.server.nodemanager;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -41,23 +40,22 @@ class DirectoryCollection {
   private int numFailures;
 
   public DirectoryCollection(String[] dirs) {
-    localDirs = new ArrayList<String>();
-    localDirs.addAll(Arrays.asList(dirs));
-    failedDirs = new ArrayList<String>();
+    localDirs = new CopyOnWriteArrayList<String>(dirs);
+    failedDirs = new CopyOnWriteArrayList<String>();
   }
 
   /**
    * @return the current valid directories 
    */
   synchronized List<String> getGoodDirs() {
-    return localDirs;
+    return Collections.unmodifiableList(localDirs);
   }
 
   /**
    * @return the failed directories
    */
   synchronized List<String> getFailedDirs() {
-    return failedDirs;
+    return Collections.unmodifiableList(failedDirs);
   }
 
   /**
@@ -75,22 +73,17 @@ class DirectoryCollection {
    */
   synchronized boolean checkDirs() {
     int oldNumFailures = numFailures;
-    ListIterator<String> it = localDirs.listIterator();
-    while (it.hasNext()) {
-      final String dir = it.next();
+    for (final String dir : localDirs) {
       try {
         DiskChecker.checkDir(new File(dir));
       } catch (DiskErrorException de) {
         LOG.warn("Directory " + dir + " error " +
             de.getMessage() + ", removing from the list of valid directories.");
-        it.remove();
+        localDirs.remove(dir);
         failedDirs.add(dir);
         numFailures++;
       }
     }
-    if (numFailures > oldNumFailures) {
-      return true;
-    }
-    return false;
+    return numFailures > oldNumFailures;
   }
 }

+ 68 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestDirectoryCollection.java

@@ -0,0 +1,68 @@
+/**
+* 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.yarn.server.nodemanager;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.hadoop.fs.FileUtil;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestDirectoryCollection {
+
+  private static final File testDir = new File("target",
+      TestDirectoryCollection.class.getName()).getAbsoluteFile();
+  private static final File testFile = new File(testDir, "testfile");
+
+  @BeforeClass
+  public static void setup() throws IOException {
+    testDir.mkdirs();
+    testFile.createNewFile();
+  }
+
+  @AfterClass
+  public static void teardown() {
+    FileUtil.fullyDelete(testDir);
+  }
+
+  @Test
+  public void testConcurrentAccess() throws IOException {
+    // Initialize DirectoryCollection with a file instead of a directory
+    String[] dirs = {testFile.getPath()};
+    DirectoryCollection dc = new DirectoryCollection(dirs);
+
+    // Create an iterator before checkDirs is called to reliable test case
+    List<String> list = dc.getGoodDirs();
+    ListIterator<String> li = list.listIterator();
+
+    // DiskErrorException will invalidate iterator of non-concurrent
+    // collections. ConcurrentModificationException will be thrown upon next
+    // use of the iterator.
+    Assert.assertTrue("checkDirs did not remove test file from directory list",
+        dc.checkDirs());
+
+    // Verify no ConcurrentModification is thrown
+    li.next();
+  }
+}