You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by cu...@apache.org on 2010/10/29 01:21:19 UTC

svn commit: r1028539 - in /avro/trunk: CHANGES.txt lang/java/src/java/org/apache/avro/Schema.java lang/java/src/test/java/org/apache/avro/TestSchema.java

Author: cutting
Date: Thu Oct 28 23:21:18 2010
New Revision: 1028539

URL: http://svn.apache.org/viewvc?rev=1028539&view=rev
Log:
AVRO-685. Java: Fix Schema#equals() and hashCode() to not require exponential time for some recursive schemas.  Contributed by Richard Ahrens.

Modified:
    avro/trunk/CHANGES.txt
    avro/trunk/lang/java/src/java/org/apache/avro/Schema.java
    avro/trunk/lang/java/src/test/java/org/apache/avro/TestSchema.java

Modified: avro/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/avro/trunk/CHANGES.txt?rev=1028539&r1=1028538&r2=1028539&view=diff
==============================================================================
--- avro/trunk/CHANGES.txt (original)
+++ avro/trunk/CHANGES.txt Thu Oct 28 23:21:18 2010
@@ -30,6 +30,10 @@ Avro 1.5.0 (unreleased)
     AVRO-681. IDL: Fix documentation example with illegal syntax.
     (Jingguo Yao via cutting)
 
+    AVRO-685. Java: Fix Schema#equals() and hashCode() to not require
+    exponential time for some recursive schemas.
+    (Richard Ahrens via cutting)
+
 Avro 1.4.1 (13 October 2010)
 
   NEW FEATURES

Modified: avro/trunk/lang/java/src/java/org/apache/avro/Schema.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/Schema.java?rev=1028539&r1=1028538&r2=1028539&view=diff
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/Schema.java (original)
+++ avro/trunk/lang/java/src/java/org/apache/avro/Schema.java Thu Oct 28 23:21:18 2010
@@ -587,21 +587,23 @@ public abstract class Schema {
       Set seen = SEEN_EQUALS.get();
       SeenPair here = new SeenPair(this, o);
       if (seen.contains(here)) return true;       // prevent stack overflow
+      boolean first = seen.isEmpty();
       try {
         seen.add(here);
         return fields.equals(((RecordSchema)o).fields);
       } finally {
-        seen.remove(here);
+        if (first) seen.clear();
       }
     }
     public int hashCode() {
       Map seen = SEEN_HASHCODE.get();
       if (seen.containsKey(this)) return 0;       // prevent stack overflow
+      boolean first = seen.isEmpty();
       try {
         seen.put(this, this);
         return super.hashCode() + fields.hashCode();
       } finally {
-        seen.remove(this);
+        if (first) seen.clear();
       }
     }
     void toJson(Names names, JsonGenerator gen) throws IOException {

Modified: avro/trunk/lang/java/src/test/java/org/apache/avro/TestSchema.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/test/java/org/apache/avro/TestSchema.java?rev=1028539&r1=1028538&r2=1028539&view=diff
==============================================================================
--- avro/trunk/lang/java/src/test/java/org/apache/avro/TestSchema.java (original)
+++ avro/trunk/lang/java/src/test/java/org/apache/avro/TestSchema.java Thu Oct 28 23:21:18 2010
@@ -35,6 +35,7 @@ import java.util.List;
 import java.util.Collection;
 
 import org.apache.avro.Schema.Type;
+import org.apache.avro.Schema.Field;
 import org.apache.avro.generic.GenericData;
 import org.apache.avro.generic.GenericDatumReader;
 import org.apache.avro.generic.GenericDatumWriter;
@@ -233,6 +234,33 @@ public class TestSchema {
   }
 
   @Test
+  /** Test that equals() and hashCode() don't require exponential time on
+   *  certain pathological schemas. */
+  public void testSchemaExplosion() throws Exception {
+    for (int i = 1; i < 15; i++) {                // 15 is big enough to trigger
+      // create a list of records, each with a single field whose type is a
+      // union of all of the records.
+      List<Schema> recs = new ArrayList<Schema>();
+      for (int j = 0; j < i; j++)
+        recs.add(Schema.createRecord(""+(char)('A'+j), null, null, false));
+      for (Schema s : recs) {
+        Schema union = Schema.createUnion(recs);
+        Field f = new Field("x", union, null, null);
+        List<Field> fields = new ArrayList<Field>();
+        fields.add(f);
+        s.setFields(fields);
+      }
+      // check that equals and hashcode are correct and complete in a
+      // reasonable amount of time
+      for (Schema s1 : recs) {
+        Schema s2 = Schema.parse(s1.toString());
+        assertEquals(s1.hashCode(), s2.hashCode()); 
+        assertEquals(s1, s2);
+      }
+    }                 
+  }
+
+  @Test
   public void testLisp() throws Exception {
     check("{\"type\": \"record\", \"name\": \"Lisp\", \"fields\": ["
           +"{\"name\":\"value\", \"type\":[\"null\", \"string\","