# Thursday, 18 December 2003
« ThinkPad | Main | Building IKVM.NET on Mono/Linux »
Asynchronous Exceptions

I've been working on some low hanging fruit optimizations for the code generator and I came across the following peculiar bytecode pattern used by Jikes to compile synchronized blocks:

   0  goto 6
   3  aload_1
   4  monitorexit
   5  athrow
   6  aload_0
   7  dup
   8  astore_1
   9  monitorenter
  10  aload_1
  11  monitorexit
  12  return
Exception table:
   start_pc = 3
   end_pc = 5
   handler_pc = 3
   catch_type = java/lang/Throwable
   start_pc = 10
   end_pc = 12
   handler_pc = 3
   catch_type = java/lang/Throwable

This code results from this method:

static void main(String[] args) {
  synchronized(args) {
  }
}

I was confused by the first entry in the exception table. It protects part of the exception handler and points to itself. Why would you do this?

Luckily, Jikes is open source, so I went and looked at the source code (see ByteCode::EmitSynchronizedStatement). The comment in the function explains that the additional protection of the exception handler is there to deal with asynchronous exceptions. The Jikes bug database contains a better explanation of the issue.

So, it turns out that this somewhat strange looking construct is actually a perfect way to make sure that locks are always released (and only once) even if an asynchronous exception occurs. (Note that this assumes that monitorexit is an atomic instruction, wrt asynchronous exceptions, this isn't in the JVM specification[1], but it is a reasonable assumption.)

At the moment, IKVM compiles this code as follows (pseudo code):

  object local = args;
  object exception = null;
  Monitor.Enter(local);
  try {
    // this is where the body of the synchronized block would be
    Monitor.Exit(local);
  } catch(System.Exception x) {
    exception = x;
    ExceptionHelper.MapExceptionFast(x);
    goto handler;
  }
  return;
handler:
  try {
    Monitor.Exit(local);
  } catch(System.Exception x) {
    exception = x;
    ExceptionHelper.MapExceptionFast(x);
    goto handler;
  }
  throw x;

This is obviously pretty inefficient, but more importantly, it is incorrect. If an asynchronous exception occurs at the right (or rather, wrong) moment the lock will be released twice.

The right way to compile this would be (pseudo code again):

  object local = args;
  Monitor.Enter(local);
  try {
    // this is where the body of the synchronized block would be
  } finally {
    Monitor.Exit(local);
  }

Of course, in the current version of the CLR, this still wouldn't be safe in the face of asynchronous exceptions, but Chris Brumme assures us that in future versions it will be.

BTW, an alternative way to compile it (which, presumably, would work correctly even in today's CLR), is to move the synchronized block into a new method that is marked with MethodImplOptions.Synchronized.

The tricky part of both of these solutions, is recognizing the code sequences that need to be compiled as a try finally clause. Various Java compilers can use different patterns (although a pretty firm clue is provided by the two exception blocks that must end exactly after the monitorexit instruction).

This is one situation where compiling bytecode instead of Java source, makes it a lot harder to do the right thing.

[1] The JVM specification actually contains an incorrect example of how to compile a synchronized block. Not only does this example not use the above protection against asynchronous exceptions, it also doesn't protect the aload_2 and monitorexit instructions at offset 8 and 9.

Thursday, 18 December 2003 11:33:18 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
Name
E-mail
Home page

I apologize for the lameness of this, but the comment spam was driving me nuts. In order to be able to post a comment, you need to answer a simple question. Hopefully this question is easy enough not to annoy serious commenters, but hard enough to keep the spammers away.

Anti-Spam Question: What method on java.lang.System returns an object's original hashcode (i.e. the one that would be returned by java.lang.Object.hashCode() if it wasn't overridden)? (case is significant)

Answer:  
Comment (HTML not allowed)  

Live Comment Preview