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