Friday, November 27, 2009

Concurrently initialized singletons in Guice

William Pietri asked whether it would be possible to take advantage of concurrency and Guice's knowledge of dependencies to provide interdependent (and expensive to create) singleton services in parallel.

For example, say that service A depends on services B and C and that B and C don't know about each other (and so could be constructed concurrently). Obviously, you could explicitly construct B and C in separate tasks running in a thread pool, then construct A once B and C exist.

class A {
    A(B b, C c) { /* ... use b and c ... */ }
}

class B {
    B() { /* ... takes a long time ... */ }
}

class C {
    C() { /* ... takes a long time ... */ }
}

class Services {
    A a; B b; C c;
    void init() throws InterruptedException {
        final CountDownLatch ready = new CountDownLatch(2);
        Executor pool = Executors.newCachedThreadPool();
        pool.execute(new Runnable() {
            public void run() { b = new B(); ready.countDown(); }
        })
        pool.execute(new Runnable() {
            public void run() { c = new C(); ready.countDown(); }
        })
        ready.await();
        a = new A(b, c);
        // Now publish a, b, and c safely -- not shown.
    }
}

But it's a pain to have to orchestrate this yourself. You have to know the dependencies intimately and write a lot of tricky code. If the dependencies change, the code has to be completely rethought. Wouldn't it be nice if Guice could do this for you automatically without giving up on the opportunities for concurrency? Well, it turns out that it can.

The idea is to use a variant of singleton scope that runs the providers of all bindings for that scope in separate threads. Naturally some of these providers block in their own thread while their dependencies finish in other threads, but those services that can be constructed in parallel will be.

Here's what the Guice version looks like:

@ConcurrentSingleton
class A {
    @Inject A(B b, C c) { /* ... use b and c ... */ }
}

@ConcurrentSingleton
class B {
    @Inject B() { /* ... takes a long time ... */ }
}

@ConcurrentSingleton
class C {
    @Inject C() { /* ... takes a long time ... */ }
}

No special initialization code needed, just inject A, B, and C wherever they are needed. (Note that if you have circular dependencies, this probably won't work. So don't have circular dependencies.)

ConcurrentSingleton is just a scope annotation. The actual scope implementation is ConcurrentSingletonScope. I have a fleshed-out version of the A, B, C example, ConcurrentSingletonExample, that is slightly different from the code above; it uses Providers for B and C so that A can do some work before getting B and C in the constructor.

You aren't forced to do all the expensive work in the constructor. You could also inject a start method with injected parameters to provide needed dependencies.

In the same thread that provoked the ConcurrentSingleton response, Jesse Wilson suggested the use of the experimental lifecycle facility in Guava. This could prove useful in the current context if, for example, you have singleton services that take a while to stop and could benefit from being stopped in parallel.

Update 2009-11-30
I added a call to pool.shutdown() in the scope implementation to make sure the pool threads don't prevent the JVM from exiting. It might not be necessary, but I don't think it hurts.

Update 2010-7-3
Added explicit notice that the sources are in the public domain.

7 comments:

swankjesse said...

This is fantastic. Getting the concurrency right is tricky, but your code looks both correct and efficient. Very cool. I'll add this to our wiki...

pohl said...

I was happy to discover this. It seems like a nice alternative to the manual starting & stopping of things that implement the Service interface described in "Modules should be fast and side-effect free":

http://code.google.com/docreader/#p=google-guice&s=google-guice&t=ModulesShouldBeFastAndSideEffectFree

I have a reservation, though, about not having a solution to the shutdown hook problem when using the ConcurrentSingleton scope you provided. Do you have any thoughts about how to handle this?

Tim Peierls said...

@pohl - I don't understand. Why not just add your shutdown hook at the end of your service startup code?

pohl said...

That's a good question, and your suggestion is obvious in retrospect.

I think the reason that it didn't occur to me is that I assumed that shutting down may also take some time and that it may be useful to perform the teardown concurrently as well.

But I suppose since Guice is just an instantiator that's beyond the scope of ConcurrentSingleton.

flashboss said...

Hi.. I'm working for infinispan project. I'm interested to make jcip-annotations OSGI compliant so it can be loaded inside a OSGI repository. To do it it's enough add thee rows in the MANIFEST.MF:

Bundle-SymbolicName: jcip-annotations
Bundle-Version: 1.0
Export-Package: net.jcip.annotations

What do you think about it?

Tim Peierls said...

@flashboss - Probably best to post this question to the concurrency-interest list, or write directly to Brian Goetz (copying me) to see what he thinks.

Paul Walker said...

good job