Monday, February 26, 2007

The Prestige

I finally saw The Prestige over the weekend. I didn't think I'd be that into it beforehand, but it turned out to be one of the best movies I've seen in a long time. The plot twists and surprises kept me guessing and reminded me of The Usual Suspects, another personal favorite. I think I might actually watch it again.

Debugging Serialization Errors

Have you ever looked at a serialization exception and not been able to figure out where the heck an errant object is hiding out? I used to run into this problem all the time as we use serialization for HttpSession replication. Stack traces aren't much help. For example, this one only tells us we have a non-serializable instance of Tee in a List somewhere in our object graph:
Exception in thread "main" java.io.NotSerializableException: serialization.Main$Tee
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1075)
  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:291)
  at java.util.ArrayList.writeObject(ArrayList.java:569)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)     
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
  at java.lang.reflect.Method.invoke(Method.java:585)
  at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:890) 
  at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1333)
  at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1284)
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1073)
  at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1369)
  at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1341)
  at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1284)
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1073)
  at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1369)
  at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1341)
  at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1284)
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1073)
  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:291)
  at serialization.Main.main(Main.java:47)
After exploring numerous options, I've settled on a simple ObjectOutputStream hack which keeps track of the path to the errant object with very little overhead. Now you can wrap serialization exceptions and add more information:
DebuggingObjectOutputStream out =
    new DebuggingObjectOutputStream(...);
try {
  out.writeObject(...);
} catch (Exception e) {
  throw new RuntimeException(
      "Serialization error. Path to bad object: " 
          + out.getStack(), e);
}
The new exception message is much more helpful:
Exception in thread "main" java.lang.RuntimeException: Serialization error. 
Path to bad object: [serialization.Main$Foo@94948a, serialization.Main$Bar@a401c2, [serialization.Main$Tee@ff5ea7], serialization.Main$Tee@ff5ea7]
  at serialization.Main.main(Main.java:55)
Caused by: java.io.NotSerializableException: serialization.Main$Tee
We can now see that Foo references Bar which references a List which contains Tee. In our application, we actually log the type of each object, too, in case the toString() output isn't helpful.

As a few key ObjectOutputStream members are final, I ended up having to access the private depth field and use it in conjunction with replaceObject():

import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.ArrayList;

public class DebuggingObjectOutputStream
    extends ObjectOutputStream {

  private static final Field DEPTH_FIELD;
  static {
    try {
      DEPTH_FIELD = ObjectOutputStream.class
          .getDeclaredField("depth");
      DEPTH_FIELD.setAccessible(true);
    } catch (NoSuchFieldException e) {
      throw new AssertionError(e);
    }
  }

  final List<Object> stack
      = new ArrayList<Object>();

  /**
   * Indicates whether or not OOS has tried to
   * write an IOException (presumably as the
   * result of a serialization error) to the
   * stream.
   */
  boolean broken = false;

  public DebuggingObjectOutputStream(
      OutputStream out) throws IOException {
    super(out);
    enableReplaceObject(true);
  }

  /**
   * Abuse {@code replaceObject()} as a hook to
   * maintain our stack.
   */
  protected Object replaceObject(Object o) {
    // ObjectOutputStream writes serialization
    // exceptions to the stream. Ignore
    // everything after that so we don't lose
    // the path to a non-serializable object. So
    // long as the user doesn't write an
    // IOException as the root object, we're OK.
    int currentDepth = currentDepth();
    if (o instanceof IOException
        && currentDepth == 0) {
      broken = true;
    }
    if (!broken) {
      truncate(currentDepth);
      stack.add(o);
    }
    return o;
  }

  private void truncate(int depth) {
    while (stack.size() > depth) {
      pop();
    }
  }

  private Object pop() {
    return stack.remove(stack.size() - 1);
  }

  /**
   * Returns a 0-based depth within the object
   * graph of the current object being
   * serialized.
   */
  private int currentDepth() {
    try {
      Integer oneBased
          = ((Integer) DEPTH_FIELD.get(this));
      return oneBased - 1;
    } catch (IllegalAccessException e) {
      throw new AssertionError(e);
    }
  }

  /**
   * Returns the path to the last object
   * serialized. If an exception occurred, this
   * should be the path to the non-serializable
   * object.
   */
  public List<Object> getStack() {
    return stack;
  }
}
I'll see what I can do about building a more secure version of this into ObjectOutputStream in Java 7.

Saturday, February 17, 2007

Google Reader vs. Bloglines Market Share

Apparently I'm not alone in my switch to Google Reader. Now that it started reporting subscriber counts, I see 34% of my subscribers use Google Reader while only 25% use Bloglines. Good news travels fast.

Tuesday, February 13, 2007

Airport Extreme vs Airport Express

Dave asked how the new Airport Extreme fairs against 802.11g routers, and I was curious myself, so I ran some tests comparing the Extreme to the Express.

My router is in my living room. I have a Mac Pro (802.11n enabled) about 20 ft. away in the same room. From an upstairs bedroom about 50 ft. away from the router, I copied a 10 MB file from my MacBook Pro to my Mac Pro over the wirless network. I ran the test three times for each configuration: Airport Express, Airport Extreme before running the 802.11n enabler on my laptop, and Airport Extreme after running the enabler.

I created a 10 MB file of random bits like so:

dd if=/dev/urandom of=10MB bs=1024 count=10000

And I used SSH to copy the file over the network:

dd if=10MB | ssh macpro dd of=/dev/null

I'm sure this wasn't the most optimal way to copy a file over the network, but I think it provides a fair comparison. I knew my network was slow, but I was still a little surprised by the results. I hope my copying method is at fault (yes, those are megabits):

  • Express: 1.8 Mb/s
  • Extreme (before enabler): 3.6 Mb/s
  • Extreme (after enabler): 8.4 Mb/s

As both computers are on the wireless network, you have to consider that the actual data rate is at least doubled, i.e. the data has to go over the air from the laptop to the router and then back over the air again to the desktop. I could have connected one computer directly to the router using a cable, but the Express doesn't support this, and 100% wireless is more representative of my use.

Despite the slow performance, the Extreme performed twice as fast as the Express even before I enabled 802.11n. After I enabled 802.11n, the Extreme ran almost 5X as fast (4.6X to be a little more precise). On the bright side, transferring video around my network just got a lot less painful.

Friday, February 02, 2007

Will closures carry as much complexity as generics?

I don't think closures will warrant 300 page books, but that doesn't mean they're as easy as they sound. Pop quiz: based on Neal's proposal, will the following code compile?
  { Object => String } closure = 
    { String s => new Object(); }

The answer is in the comments. So far as closures go, that's a relatively simple example. Then again, the non-closure version isn't exactly simple either.

Thursday, February 01, 2007

I disabled anonymous comments.

I don't like requiring registration to comment on my blog, and it certainly doesn't preclude anonymity, but carrying on a conversation when everyone is named "Anonymous" gets confusing.

Lazy Loading Example

Alex found a great example of the Initialization on Demand Holder idiom in java.lang.Integer. I've actually looked at this code before but wasn't familiar enough with the pattern to recognize it. Very cool. Thanks, Alex.