“Object Main extends Application” considered harmful

You’re probably all familiar with the standard Java invocation for creating an applicaiton’s start point.

class Main{
public static void main(String[] args){
// code
}
}

Scala has a nice feature which lets you use a trait to define your application where you don’t care about the initial arguments.


object Main extends Application{

//code

}

What happens is that Application has a method main(args : Array[String]) which gets inherited, then a corresponding static method is created on the class Main. The application logic goes in Main’s initialisation code. Neat, huh?

All we’re missing is what the definition of main is.


trait Application{

def main(args : Array[String]) = createHorrificallyConfusingBugsIfYouSoMuchAsLookAtAThread();

}

Oops.

Ok, that’s not actually true. Really it’s


trait Application{

def main(args : Array[String]) = ()

}

It doesn’t do anything. Everything happens in the object initialisation code.

Why is this an issue?


object Main extends Application{

val foo = stuff();

spawn {  doSomethingWith(foo) }

doSomeLongRunningThingThatBlocks();

}

Why is this an issue?

Well, foo is a field. And Main’s constructor hasn’t completed. This means that the visibility of foo to other threads is completely undefined. Bad things will happen.

This isn’t a hypothetical scenario. We’ve just spent a non-trivial amount of time debugging a problem caused by this. We were spawning a background process from Main, and then entering QApplication.start() (the “let QT start doing stuff” method). The background process was blocking on trying to read a field from Main, and only unblocking when QApplication finally exited.

You should not expose partially constructed objects like this. It’s a bad idea, the VM will hate you for it and it will punish you with incomprehensible bugs as a result.

Edit: In fact, it turns out that this isn’t about partially constructed objects. It’s about static initialization. Because the initialization of the Main$ class depends on the construction of the Main object, Main$ will not be fully initialized until the constructor has exited. The JVM semantics guarantee that class loading is single threaded and that other threads cannot access the class until it has finished loading. Hence the observed problem.

This entry was posted in programming and tagged , on by .

5 thoughts on ““Object Main extends Application” considered harmful

  1. German B.

    “extends Application”: maybe Scala anti-pattern #1 of a hopefully-not-too-long list?

  2. Yang

    Do you have a small test case in Java which reproduces this? The following code seems to work fine, for instance:

    public class ClassLoadingThread {
    static {
    System.out.println(“hello”);
    final ClassLoadingThread x = new ClassLoadingThread();
    Runnable r = new Runnable() {
    public void run() {
    synchronized(x) { x.notify(); }
    }
    };
    // If we are currently being classloaded, then we won’t make it past this line, right?
    new Thread(r).start();
    try { synchronized(x) { x.wait(); } }
    catch (Exception ex) { throw new RuntimeException(ex); }
    }
    public static void main(String[] args) {
    System.out.println(“world”);
    }
    }

  3. david Post author

    I’m not entirely sure why your example doesn’t work. It seems like you should never get to “world”. Here’s an example that does demonstrate it though:

    public class ClassLoading {

    static {
    new Thread(){
    public void run() {
    System.out.println(new ClassLoading());
    }
    }.start();

    System.out.println(“Hello world”);

    boolean x = true;

    while(x){
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }

    public String toString() {
    return “Goodbye world”;
    }

    public static void main(String[] args) {

    }
    }

    This will print “Hello world” but will never print “Goodbye world”

  4. Pingback: Best of drmaciver.com | David R. MacIver

Comments are closed.