Search |
||
Notes on unspecified behaviour, with a contentious aside about astronomyPosted by emcmanus on November 10, 2006 at 8:09 AM PST
An inadvertent change in JDK 6 means that MBean attributes and operations no longer appear in the order they were in a Standard MBean interface. I wanted to fix this, but now I'm not so sure. Here's the background. Suppose you have a Standard MBean with this interface:
public interface PlanetMBean {
public int getMercury();
public String getVenus();
public void setVenus(String x);
public BigInteger getEarth();
public void setEarth(BigInteger x);
public boolean isMars();
public double getJupiter();
public byte getSaturn();
public short getUranus();
public void setUranus(short x);
public long getNeptune();
public void neptune();
public void uranus(int x);
public int saturn(int x, int y);
public short jupiter(int x, long y, double z);
public void mars(boolean x);
public BigInteger earth();
public String venus();
public int mercury();
}
As you can see, it defines a set of attributes and operations with random types, but names in a very definite and familiar order. If we create a Standard MBean like this, using JDK 5.0, then it will look like this in JConsole.
However, if we run the same program on JDK 6, then it looks like this:
Observe that the planets are no longer in astronomical order, but in alphabetical order. Aside: what about Pluto? You might have noticed that I omitted Pluto from my list. That's because Pluto is not a Planet. A rare victory in the ongoing and hopeless war against Stupidism was won earlier this year when the International Astronomical Union voted to recognize logic and common sense and ignore inertia, nostalgia, and welearneditinschoolsoitmustbetrueforeveria. More details than anyone could possibly want to know are in Wikipedia. Beyond just astronomical reality, computer scientists will surely be happier that there are eight planets, divided into four big ones and four small ones, and with each group of four further divisible into two big and two small. Or maybe that's just me. It turns out that this reordering is an unintended side-effect of my rewrite of the MBean Server internals. I had intended to preserve the order from the MBean interface, but in a moment of distraction I used a TreeMap instead of a LinkedHashMap. Nothing in the JMX specification requires that attributes and operations in a Standard MBean appear in the same order in the MBeanInfo as they do in the MBean interface, so none of our automated tests check that. We only discovered it recently, as a side-effect of some unrelated tests being coded by one of our Quality Engineers, Sandra Lions. Sandra logged bug 6486655 to track this. This looks like something we would obviously want to fix,
right? Perhaps you thought carefully about the order you want
to present your attributes in, and followed that order in your
MBean interface. Perhaps you want to group related operations
together, for example So, since I broke it, I started the process of fixing it. The
fix itself is completely trivial - just replace two
The JDK has a clearly-defined engineering process. One rule in that process is that every time we fix a bug, there must be a regression test that fails without the fix and passes with the fix. So I duly coded a regression test. It was pretty much what you would expect. In outline:
public class MethodOrderTest {
public static interface PlanetMBean {
...same as above...
}
private static final String[] planets = {
"mercury", "venus", "earth", "mars", "jupiter", "saturn",
"uranus", "neptune"
};
public static void main(String[] args) throws Exception {
...create a Standard MBean using the PlanetMBean interface...
MBeanInfo mbi = ...the MBeanInfo of this MBean...;
MBeanAttributeInfo[] mbais = mbi.getAttributes();
...check that mbais[] is in the same order as planets[]...
MBeanOperationInfo[] mbois = mbi.getOperations();
...check that mbois[] is in the opposite order as planets[]...
}
}
After fixing the usual stupid mistakes in the test, the attribute check passed. But the operation check failed! What was different about operations? I went back and looked at the introspection code but could see nothing. What's more, the order the operations were appearing in was
surprising. I would have understood if they were in
alphabetical order. That would have meant that something else
besides the But the operations were in astronomical order, from Mercury to Neptune, even though the interface declared them in reverse astronomical order, from Neptune to Mercury. What could be reversing them? I won't get into the details of the painstaking investigation that followed. Suffice it to say that I ended up boiling the test failure down to this program:
public class MethodOrderTest {
public static interface PlanetMBean {
...same as above...
}
private static final String[] planets = {
"mercury", "venus", "earth", "mars", "jupiter", "saturn",
"uranus", "neptune"
};
public static void main(String[] args) throws Exception {
System.out.println(Arrays.asList(PlanetMBean.class.getMethods()));
}
}
Notice that there's no JMX stuff at all here. It's just a call to the Reflection API. And it shows the methods in the following order: [public abstract int MethodOrderTest$PlanetMBean.mercury(), public abstract java.lang.String MethodOrderTest$PlanetMBean.venus(), public abstract java.math.BigInteger MethodOrderTest$PlanetMBean.earth(), public abstract void MethodOrderTest$PlanetMBean.mars(boolean), public abstract short MethodOrderTest$PlanetMBean.jupiter(int,long,double), public abstract int MethodOrderTest$PlanetMBean.saturn(int,int), public abstract void MethodOrderTest$PlanetMBean.uranus(int), public abstract void MethodOrderTest$PlanetMBean.neptune(), public abstract int MethodOrderTest$PlanetMBean.getMercury(), public abstract java.lang.String MethodOrderTest$PlanetMBean.getVenus(), public abstract void MethodOrderTest$PlanetMBean.setVenus(java.lang.String), public abstract java.math.BigInteger MethodOrderTest$PlanetMBean.getEarth(), public abstract void MethodOrderTest$PlanetMBean.setEarth(java.math.BigInteger), public abstract boolean MethodOrderTest$PlanetMBean.isMars(), public abstract double MethodOrderTest$PlanetMBean.getJupiter(), public abstract byte MethodOrderTest$PlanetMBean.getSaturn(), public abstract short MethodOrderTest$PlanetMBean.getUranus(), public abstract void MethodOrderTest$PlanetMBean.setUranus(short), public abstract long MethodOrderTest$PlanetMBean.getNeptune()] OK. So the getters and setters appear in the same order as in the interface, but the other methods have been reordered and appear earlier. Does reflection treat getters and setters differently from other methods? What on earth...what the devil is going on? The next, surprising step is to comment out the definition of
the Further investigation shows that if I include just the strings
If we go back and look at the documentation for Class.getMethods(), we learn that "the elements in the array returned are not sorted and are not in any particular order". I had read that text long ago, but I had also observed that in practice the elements were in the order that the methods were declared in the class. So I wasn't too uneasy about assuming they would be. But the investigation above shows that this assumption is
false. In fact, the order of methods is the order in which the
JVM first encountered the method name strings. If your
interface has methods called The implications are wide-ranging. In particular, JUnit runs the various
It turns out that this surprising order is recorded if you look very hard. For example bug 4789197 includes the text "In the HotSpot VM, the order is actually dependent on the order in which the VM's UTF-8 constants are created, i.e., order will be different if a program loads other classes which uses the same name-type id's in random order." Anyway, now I'm faced with a dilemma. I'd quite like to restore the previous behaviour of Standard MBeans, where attributes and operations show up in the same order as the methods in the MBean interface. Code that depends on this is surely broken, but code like JConsole that presents the attributes and operations in a user interface would surely be better served by this arrangement. Except that now I know that that order can be perturbed by unrelated classes that happen to be loaded earlier. Is it still worth trying to preserve this fragile order? I'd be interested in what people think. »
Related Topics >>
Open JDK Comments
Comments are listed in date ascending order (oldest first)
|
||
|
|