Blocking the CDI container politely

If, like me, you’re looking for a way to run other containers (like web servers) inside a CDI container, you may have been frustrated by the annoying propensity of the Java Virtual Machine to exit helpfully when no non-daemon Threads remain alive.

(If you’re not yet like me, you may want to start with my post series CDI 2.0 and read up to here.)

Specifically, if you try to launch, say, an HttpServer, then you will discover that the simplest way to do this will not prevent the CDI container from simply exiting right out from under you, since it spawns daemon Threads that you can’t get a handle on which don’t prevent the Java Virtual Machine from exiting.  So the only non-daemon thread in existence will be the container thread, and it will run to completion, and then your HttpServer daemon threads will all be killed and then the Java Virtual Machine will exit and you will be out of luck.

You could just (as I did, misguidedly, in an earlier proof of concept) join() on the main container thread.  But you don’t want to do this.  You don’t really want to mindlessly block the CDI container thread, since you don’t really know where in its lifecycle you’re doing the blocking.  Also, there may be other CDI beans that wish to be notified of context initialization and may want to do things that have nothing to do with your container. Finally, Weld developers become very concerned if you even suggest doing such a thing. 😀

And you don’t want to get in the way of any shutdown hooks that might be installed by certain industry-leading CDI implementations at particular moments in the portable extension lifecycle.

Lastly, you want to make sure that CTRL-C still works on any program you execute that uses CDI 2.0, and that it doesn’t prevent the normal CDI container lifecycle cleanup from running normally.

Here’s how I did it.

First, you want the blocking behavior to be such that if you were to unblock it in some way, the regular container shutdown semantics would still happen.  That is, @BeforeDestroyed(ApplicationScoped.class) events and BeforeShutdown portable extension events would still be fired normally.

And, as we said, you want the blocking behavior not to actually prevent the main CDI thread from doing its business in its ordinary way.  (Part of its business might very well be to install shutdown hooks, which will be the only way to unblock things if you wish later on.)

So at some level really you don’t want to block the main container thread at all.  But at another hazy level, you do.  Paradox!

To resolve this contradiction, we’re going to have to block some other non-daemon thread so that the JVM won’t exit, but the main container loop can still do its normal business.

But merely starting a non-daemon Thread and then blocking it won’t work.  True, the Java Virtual Machine won’t exit, but the container thread—now not being blocked—will simply very quickly run to completion, and now we’ll have a bunch of HttpServer daemon threads out there that were started from within a CDI container but now don’t have a CDI container in play, and a blocked thread, and an unresponsive Java Virtual Machine, and probably sixteen other kinds of horrible disasters just waiting to happen. So we are not going to do this. 😀

Instead, it would be really nice if the container could somehow manage or otherwise be aware of this other non-daemon thread so that blocking it would somehow happen only during the “open for business” portion of the container lifecycle, and in a legal way.  Then we would achieve our seemingly paradoxical goals of both blocking the CDI container and not blocking the CDI container’s main thread.

Fortunately, there is a way to do this: use asynchronous events.

The general approach will be to define a portable extension that does the following things:

  1. Creates a CountDownLatch with an initial count of 1 and stores it as an instance variable.  This will be our blocking mechanism.
  2. Installs a shutdown hook that, should it ever be called, will simply call countDown() on the latch.
  3. Starts an HttpServer (or HttpServers) when the application scope is initialized.  This will spawn a daemon thread that we don’t have any control over.  Then we’ll store the HttpServer so we can refer to it later.
  4. Fires an asynchronous event that can be received only by the portable extension that indicates, basically, that we’re done starting servers.
  5. In the asynchronous observer, by definition on a different thread, call await() on the CountDownLatch, thus blocking the observer thread, which is managed by the container.

Most of the above is pretty straightforward, but the asynchronous event reception is worth a closer look.

The CDI specification tells us this about firing asynchronous events:

Event fired [sic] with the fireAsync() method is fired asynchronously. All the resolved asynchronous observers (as defined in Observer resolution) are called in one or more different threads.

The language here is not very specific.  If we have six asynchronous observers, will they all be called on one additional thread or six additional threads, or, say, three?  Luckily in our case this ambiguity doesn’t matter, as we will define the only possible asynchronous observer for the event we’re going to fire, and we’re guaranteed that the thread it runs on will be “different” from the main CDI container thread.

Furthermore, although the specification says nothing about this, we can surmise that the container will be biased in favor of letting an asynchronous observer thread run to completion, since obviously it has no idea what that thread is doing.  That means, in other words, that the container will almost certainly block while our asynchronous observer thread is still alive, waiting for it to finish.  The nice thing is that the container is in charge of deciding when to block, which is exactly what we want.

So back to the recipe.  Here’s our constructor, satisfying the first two steps in our recipe:

Next, we’ll start our HttpServers when the application scope comes up:

At line 43 above you can see that we need to fire an event signaling that we’re done starting servers.  We’re in a portable extension, so we’ll need to do this from the BeanManager—a portable extension’s container lifecycle event observing observer method may have only two parameters, and the second parameter, if present, must be of type BeanManager. We also want to make sure that our portable extension is the only possible source of this event and the only possible receiver.

So first we’ll define a simple event object as a private inner class:

And we’ll set up an asynchronous observer method:

…and now we’ll replace that TODO item above with the one-liner that will fire the asynchronous event:

Putting the starting method together, then, it looks like this:

Finally, more or less orthogonally to all this, let’s define what should happen when the CDI container shuts down, no matter how that should happen:

So we know that no matter how the container decides to shut down, this logic will ensure that we at least make an attempt to gracefully shut down any HttpServer we might have started.

If we put it all together:

  • If for any reason at any point CTRL-C is pressed, then we effectively disable all blocking behavior (or unblock any thread we’re currently blocking).  We do this by using CountDownLatch‘s facilities.
  • We start HttpServers and stash them away for later shutdown.  Daemon threads out of our control are created and started by Grizzly.
  • We fire an asynchronous event that causes the CDI container to start or allocate a managed thread.  On that thread, we block, using the await() call of the CountDownLatch.  This prevents the CDI container from shutting down until CTRL-C is received.
  • Finally, we define what happens if the CDI container shuts down for any reason—namely we shut down any HttpServers we’ve started.

I hope you can see that this allows the running of a container within a CDI container while allowing the CDI container to function and shut down normally.

Author: Laird Nelson

Devoted husband and father; working on Helidon at the intersection of Java, Jakarta EE, architecture, Kubernetes and microservices at Oracle; open source guy; Hammond B3 player and Bainbridge Islander.