diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index 50e007f9c3d..5d34a8ee076 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -768,8 +768,11 @@ public class Configuration implements Iterable>, */ @SuppressWarnings("unchecked") public Configuration(Configuration other) { - this.resources = (ArrayList) other.resources.clone(); synchronized(other) { + // Make sure we clone a finalized state + // Resources like input streams can be processed only once + other.getProps(); + this.resources = (ArrayList) other.resources.clone(); if (other.properties != null) { this.properties = (Properties)other.properties.clone(); } diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java index d491a52b6c5..50bf56cc252 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java @@ -2142,4 +2142,34 @@ public class TestConfiguration extends TestCase { assertEquals(" prefix >cdata\nsuffix ", conf.get("cdata-whitespace")); return conf; } + + /** + * Test race conditions between clone() and getProps(). + * Test for race conditions in the way Hadoop handles the Configuration + * class. The scenario is the following. Let's assume that there are two + * threads sharing the same Configuration class. One adds some resources + * to the configuration, while the other one clones it. Resources are + * loaded lazily in a deferred call to loadResources(). If the cloning + * happens after adding the resources but before parsing them, some temporary + * resources like input stream pointers are cloned. Eventually both copies + * will load the same input stream resources. + * One parses the input stream XML and closes it updating it's own copy of + * the resource. The other one has another pointer to the same input stream. + * When it tries to load it, it will crash with a stream closed exception. + */ + @Test + public void testResourceRace() { + InputStream is = + new BufferedInputStream(new ByteArrayInputStream( + "".getBytes())); + Configuration config = new Configuration(); + // Thread 1 + config.addResource(is); + // Thread 2 + Configuration confClone = new Configuration(conf); + // Thread 2 + confClone.get("firstParse"); + // Thread 1 + config.get("secondParse"); + } }