Nick Sieger, Michael Koziarski and I were talking about ObjectSpace on IRC this evening. After some discussion, it became apparent that most remaining uses of ObjectSpace within Rails were only using the typical ObjectSpace.each_object(Class) pattern to find descendants of a specific class. The use of Class#inherited came up as a possible remedy, but obviously the reason ObjectSpace.each_object(Class) is used in the first place is to avoid having to track information by hand. Note also that test/unit uses this to find TestCase subclasses, which is the main reason we don't have ObjectSpace disabled all the time.

So the next obvious step is the possibility of implementation/VM changes.

Since it's pretty simple to track subclasses within JRuby code, I gave it a try. The attached patch does the following:

- on instantiation, subclasses tell the parent class to add them to a weak set. - Class#subclasses returns a list of immediate subclasses. An optional boolean argument can enable full descendants. Since this is a non-standard API, I don't know how we'd want to handle it. It smells a little bit "embrace and extend". - ObjectSpace.each_object(Class) is short-circuited to actually do Object.subclasses(true).each.

This means the following works:

$ jruby -O -e "ObjectSpace.each_object(Class) {|c| puts c if c < Numeric}"
Float
Integer
Bignum
Fixnum

Note that ObjectSpace is technically disabled here, but it still works for each_object(Class).

Thoughts? This pattern seems to be by far the most common use of each_object, and with this patch ObjectSpace can be turned off and the pattern still works.

- Charlie
Index: src/org/jruby/runtime/ObjectSpace.java
===================================================================
--- src/org/jruby/runtime/ObjectSpace.java      (revision 4216)
+++ src/org/jruby/runtime/ObjectSpace.java      (working copy)
@@ -38,10 +38,13 @@
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import org.jruby.RubyArray;
+import org.jruby.RubyClass;
 
 /**
  * FIXME: This version is faster than the previous, but both suffer from a
@@ -113,40 +116,44 @@
     }
 
     public synchronized Iterator iterator(RubyModule rubyClass) {
-        final List objList = new ArrayList();
-        WeakReferenceListNode current = top;
-        while (current != null) {
-            IRubyObject obj = (IRubyObject)current.get();
-            if (obj != null && obj.isKindOf(rubyClass)) {
-                objList.add(current);
+        final Collection objList = new ArrayList();
+        if (rubyClass == rubyClass.getRuntime().getClass("Class")) {
+            return 
((RubyArray)rubyClass.getRuntime().getObject().subclasses(new IRubyObject[] 
{rubyClass.getRuntime().getTrue()})).iterator();
+        } else {
+            WeakReferenceListNode current = top;
+            while (current != null) {
+                IRubyObject obj = (IRubyObject)current.get();
+                if (obj != null && obj.isKindOf(rubyClass)) {
+                    objList.add(current);
+                }
+
+                current = current.nextNode;
             }
 
-            current = current.nextNode;
-        }
+            return new Iterator() {
+                private Iterator iter = objList.iterator();
 
-        return new Iterator() {
-            private Iterator iter = objList.iterator();
+                public boolean hasNext() {
+                    throw new UnsupportedOperationException();
+                }
 
-            public boolean hasNext() {
-                throw new UnsupportedOperationException();
-            }
+                public Object next() {
+                    Object obj = null;
+                    while (iter.hasNext()) {
+                        WeakReferenceListNode node = 
(WeakReferenceListNode)iter.next();
 
-            public Object next() {
-                Object obj = null;
-                while (iter.hasNext()) {
-                    WeakReferenceListNode node = 
(WeakReferenceListNode)iter.next();
+                        obj = node.get();
 
-                    obj = node.get();
+                        if (obj != null) break;
+                    }
+                    return obj;
+                }
 
-                    if (obj != null) break;
+                public void remove() {
+                    throw new UnsupportedOperationException();
                 }
-                return obj;
-            }
-
-            public void remove() {
-                throw new UnsupportedOperationException();
-            }
-        };
+            };
+        }
     }
 
     private synchronized void cleanup() {
Index: src/org/jruby/RubyClass.java
===================================================================
--- src/org/jruby/RubyClass.java        (revision 4216)
+++ src/org/jruby/RubyClass.java        (working copy)
@@ -32,6 +32,7 @@
 
 import java.io.IOException;
 import java.util.Map;
+import java.util.Set;
 import org.jruby.runtime.Block;
 import org.jruby.runtime.CallbackFactory;
 import org.jruby.runtime.ClassIndex;
@@ -41,6 +42,7 @@
 import org.jruby.runtime.builtin.IRubyObject;
 import org.jruby.runtime.marshal.MarshalStream;
 import org.jruby.runtime.marshal.UnmarshalStream;
+import org.jruby.util.collections.WeakHashSet;
 
 /**
  *
@@ -55,6 +57,8 @@
     
     private ObjectMarshal marshal;
     
+    private Set subclasses;
+    
     private static final ObjectMarshal DEFAULT_OBJECT_MARSHAL = new 
ObjectMarshal() {
         public void marshalTo(Ruby runtime, Object obj, RubyClass type,
                               MarshalStream marshalStream) throws IOException {
@@ -122,11 +126,47 @@
         // use parent's marshal, or default object marshal by default
         if (superClass != null) {
             this.marshal = superClass.getMarshal();
+            if (this.isClass()) {
+                superClass.addSubclass(this);
+            }
         } else {
             this.marshal = DEFAULT_OBJECT_MARSHAL;
         }
     }
     
+    public synchronized void addSubclass(RubyClass subclass) {
+        if (subclasses == null) subclasses = new WeakHashSet();
+        subclasses.add(subclass);
+    }
+    
+    protected Set getSubclasses() {
+        return subclasses;
+    }
+    
+    public IRubyObject subclasses(IRubyObject[] args) {
+        boolean recursive = false;
+        if (args.length == 1) {
+            if (args[0] instanceof RubyBoolean) {
+                recursive = args[0].isTrue();
+            } else {
+                getRuntime().newTypeError(args[0], 
getRuntime().getClass("Boolean"));
+            }
+        }
+        
+        if (subclasses != null) {
+            RubyArray result = 
getRuntime().newArrayNoCopyLight((IRubyObject[])subclasses.toArray(new 
IRubyObject[subclasses.size()]));
+            if (recursive) {
+                for (Object i: subclasses) {
+                    result.addAll((RubyArray)((RubyClass)i).subclasses(args));
+                }
+            }
+
+            return result;
+        } else {
+            return RubyArray.newArrayLight(getRuntime(), 0);
+        }
+    }
+    
     /**
      * Create an initial Object meta class before Module and Kernel 
dependencies have
      * squirreled themselves together.
@@ -220,6 +260,8 @@
         classClass.undefineMethod("append_features");
         classClass.undefineMethod("extend_object");
         
+        classClass.defineFastMethod("subclasses", 
callbackFactory.getFastOptMethod("subclasses"));
+        
         // FIXME: for some reason this dispatcher causes a VerifyError...
         //classClass.dispatcher = callbackFactory.createDispatcher(classClass);
     }

---------------------------------------------------------------------
To unsubscribe from this list please visit:

    http://xircles.codehaus.org/manage_email

Reply via email to