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 Thread
s 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 Thread
s 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:
- Creates a
CountDownLatch
with an initial count of 1 and stores it as an instance variable. This will be our blocking mechanism. - Installs a shutdown hook that, should it ever be called, will simply call
countDown()
on the latch. - Starts an
HttpServer
(orHttpServer
s) when the application scope is initialized. This will spawn a daemon thread that we don’t have any control over. Then we’ll store theHttpServer
so we can refer to it later. - Fires an asynchronous event that can be received only by the portable extension that indicates, basically, that we’re done starting servers.
- In the asynchronous observer, by definition on a different thread, call
await()
on theCountDownLatch
, 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private final CountDownLatch latch; | |
private final Collection<HttpServer> startedHttpServers; | |
public HttpServerStartingExtension() { | |
super(); | |
this.startedHttpServers = new LinkedList<>(); | |
this.latch = new CountDownLatch(1); | |
Runtime.getRuntime().addShutdownHook(new Thread(() -> { | |
latch.countDown(); | |
})); | |
} |
Next, we’ll start our HttpServer
s when the application scope comes up:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private final void startHttpServers(@Observes @Initialized(ApplicationScoped.class) @Priority(LIBRARY_AFTER) final Object event, final BeanManager beanManager) throws IOException, InterruptedException { | |
if (beanManager != null) { | |
final Instance<Object> beans = beanManager.createInstance(); | |
assert beans != null; | |
final Instance<HttpServer> httpServers = beans.select(HttpServer.class); | |
assert httpServers != null; | |
if (!httpServers.isUnsatisfied()) { | |
synchronized (this.startedHttpServers) { | |
for (final HttpServer httpServer : httpServers) { | |
if (httpServer != null) { | |
// This asynchronous method starts a daemon thread in | |
// the background; see | |
// https://github.com/GrizzlyNIO/grizzly-mirror/blob/2.3.x/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpServer.java#L816 | |
// and work backwards to the start() method. Among | |
// other things, that won't prevent the JVM from exiting | |
// normally. Think about that for a while. We'll need | |
// another mechanism to block the CDI container from | |
// simply shutting down. | |
httpServer.start(); | |
// We store our own list of started HttpServers to use | |
// in other methods in this class rather than relying on | |
// Instance<HttpServer> because we don't know what scope | |
// the HttpServer instances in question are. For | |
// example, they might be in Dependent scope, which | |
// would mean every Instance#get() invocation might | |
// create a new one. | |
this.startedHttpServers.add(httpServer); | |
} | |
} | |
if (!this.startedHttpServers.isEmpty()) { | |
// Here we fire an *asynchronous* event that will cause | |
// the thread it is received on to block. This is key: | |
// the JVM will be prevented from exiting, and the thread | |
// that the event is received on is (a) a non-daemon | |
// thread and (b) managed by the container. This is, in | |
// other words, a clever way to take advantage of the CDI | |
// container's mandated thread management behavior so | |
// that the CDI container stays up for at least as long | |
// as the HttpServer we spawned above. | |
// | |
// TODO: fire the event | |
// | |
} | |
} | |
} | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static final class BlockingEvent { | |
private BlockingEvent() { | |
super(); | |
} | |
} |
And we’ll set up an asynchronous observer method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private final void block(@ObservesAsync final BlockingEvent event) throws InterruptedException { | |
this.latch.await(); | |
} |
…and now we’ll replace that TODO
item above with the one-liner that will fire the asynchronous event:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
beanManager.getEvent().select(BlockingEvent.class).fireAsync(new BlockingEvent()); |
Putting the starting method together, then, it looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private final void startHttpServers(@Observes @Initialized(ApplicationScoped.class) @Priority(LIBRARY_AFTER) final Object event, final BeanManager beanManager) throws IOException, InterruptedException { | |
if (beanManager != null) { | |
final Instance<Object> beans = beanManager.createInstance(); | |
assert beans != null; | |
final Instance<HttpServer> httpServers = beans.select(HttpServer.class); | |
assert httpServers != null; | |
if (!httpServers.isUnsatisfied()) { | |
synchronized (this.startedHttpServers) { | |
for (final HttpServer httpServer : httpServers) { | |
if (httpServer != null) { | |
// This asynchronous method starts a daemon thread in | |
// the background; see | |
// https://github.com/GrizzlyNIO/grizzly-mirror/blob/2.3.x/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpServer.java#L816 | |
// and work backwards to the start() method. Among | |
// other things, that won't prevent the JVM from exiting | |
// normally. Think about that for a while. We'll need | |
// another mechanism to block the CDI container from | |
// simply shutting down. | |
httpServer.start(); | |
// We store our own list of started HttpServers to use | |
// in other methods in this class rather than relying on | |
// Instance<HttpServer> because we don't know what scope | |
// the HttpServer instances in question are. For | |
// example, they might be in Dependent scope, which | |
// would mean every Instance#get() invocation might | |
// create a new one. | |
this.startedHttpServers.add(httpServer); | |
} | |
} | |
if (!this.startedHttpServers.isEmpty()) { | |
// Here we fire an *asynchronous* event that will cause | |
// the thread it is received on to block. This is key: | |
// the JVM will be prevented from exiting, and the thread | |
// that the event is received on is (a) a non-daemon | |
// thread and (b) managed by the container. This is, in | |
// other words, a clever way to take advantage of the CDI | |
// container's mandated thread management behavior so | |
// that the CDI container stays up for at least as long | |
// as the HttpServer we spawned above. | |
beanManager.getEvent().select(BlockingEvent.class).fireAsync(new BlockingEvent()); | |
} | |
} | |
} | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private final void stopHttpServers(@Observes final BeforeShutdown event) { | |
synchronized (this.startedHttpServers) { | |
for (final HttpServer httpServer : this.startedHttpServers) { | |
if (httpServer != null && httpServer.isStarted()) { | |
httpServer.shutdownNow(); | |
} | |
} | |
} | |
} |
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
HttpServer
s 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 theCountDownLatch
. 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
HttpServer
s 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.
4 thoughts on “Blocking the CDI container politely”
Comments are closed.