7

I'm interested in doing some work on the PostgreSQL JDBC driver to help with an implementation of Statement.setQueryTimeout(...), one of the more problematic spec conformance holes in the driver. To do this, I need a portable way to get a timer or set an alarm/callback that works in all Java EE app servers, servlet containers, and in Java SE environments.

It seems that's not as simple as it should be, and I'm stuck enough that I'm throwing myself upon your mercy for a hint. How the hell do I do a simple timer callback that works in Java SE, Java EE, and servlet containers?

If necessary I can possibly endure doing separate -ee and -se releases, but it's extremely undesirable. Releases for each container are completely impractical, though auto-selected per-container adapters might be acceptable if strongly undersirable.

The PgJDBC driver must run in crusty old app servers under ancient versions of the JVM, but I don't care in the slightest if statement timeouts are only available in the JDBC4 version of the driver for modern containers and JVMs. There's already a conditional compilation infrastructure in place to allow releases of JDBC3/JDK 1.4 and JDBC4/JDK 1.5 drivers, so code that only works under 1.5 or even 1.6 isn't a problem.

(edit): An added complication is that the JDBC driver may be deployed by users:

  • As a container module or built-in component that's launched at container startup;
  • As a standalone deployment object that can be undeployed and redeployed at runtime; or
  • Embedded inside their application war or ear

... and we need to support all those scenarios, preferably without requiring custom application configuration! If we can't support all those scenarios it needs to at least work without statement timeout support and to fail gracefully where statement timeouts can't be supported.

Ah, write once, run anwwhere....

I can't just use java.util.Timer or java.util.concurrent:

I see broad statements that the use of java.util.Timer or the Java SE concurrency utilities (JSR-166) in the java.util.concurrent package is discouraged in Java EE, but rarely any detail. The JSR 236 proposal says that:

java.util.Timer, java.lang.Thread and the Java SE concurrency utilities (JSR-166) in the java.util.concurrency (sic) package should never be used within managed environments, as it creates threads outside the purview of the container.

A bit more reading suggests that calls from unmanaged threads won't get container services, so all sorts of things throughout the app may break in exciting and unexpected ways. Given that a timer invocation may result in an exception being thrown by PgJDBC and propagating into user application code this is important.

(edit): The JDBC driver its self does not require any container services so I don't care if they work within its timer thread(s) so long as those threads never run any user code. The issue is reliably ensuring that they don't.

The JSR 236 timer abstraction layer is defunct

JSR 236 is defunct, and I don't see any replacement that satisfies the same requirements for portable timers.

I can't find any reference to a cross-container portable way to obtain a container-pooled timer either. If I could grab a timer from JNDI on containers and fall back to direct instantiation where getting one from JNDI failed that'd be OK... but I can't even find a way to do that.

EJB timers are unsuitable

There are EJB timers, but they're unsuitable for low-level stuff like a JDBC driver implementation because they're:

  • persistent across container or machine restart
  • high overhead
  • may be implemented using a database for timer persistence
  • oriented toward business time not machine time
  • unavailable in plain servlet containers
  • unavailable in "web profile" EE app servers

So EJB timers can be struck entirely off the list.

I can't roll my own timer thread

The same issues that prevent the use of java.util.Timer and friends prevent me from launching my own timer thread and managing my own timers. That's a non-starter.

The Java EE spec says:

The enterprise bean must not attempt to manage threads. The enterprise bean must not attempt to start, stop, suspend, or resume a thread, or to change a thread’s priority or name. The enterprise bean must not attempt to manage thread groups.

and the EE tutorial says:

Resource adapters that improperly use threads can jeopardize the entire application server environment. For example, a resource adapter might create too many threads or might not properly release threads it has created. Poor thread handling inhibits application server shutdown and impacts the application server’s performance because creating and destroying threads are expensive operations.

WorkManager doesn't do timers

There's a javax.resource.spi.work.WorkManager, but (a) it's intended for use on the service provider side not the application side, and (b) it's not really designed for timers. A timer can probably be hacked in using a Work item that sleeps with a timeout, but that's ugly at best and probably going to be quite inefficient.

It doesn't look like it'll work on Java SE, either.

The Java Connector Architecture (JCA)

As referred to in the Java EE tutorial, the Connector Architecture might be a viable option for EE containers. However, again, servlet containers like Tomcat or Jetty may not support it.

I'm also concerned about the performance implications of going down this route.

So I'm stuck

How do I do this simple task?

Do I need to write a new ThreadPoolExecutor that gets threads from the container via JNDI, then use that as the base for a new ScheduledThreadPoolExecutor ? If so, is there even a portable way to get threads from a container, or do I need per-container JNDI lookup and adapter code?

Am I missing something stupid and blindingly obvious?

How do other libraries that need asynchronous work, timers or callbacks handle portability between Java EE and Java SE?

Craig Ringer
  • 259,831
  • 56
  • 584
  • 684
  • @BalusC Thankyou *VERY* much for your advice and links to discussions I hadn't found in prior searches. The combination of using a `ServiceLoader` and `ScheduledThreadPoolExecutor` looks very promising and bears investigation. Much appreciated. Might be a good idea to bang your comments in an answer if you want. – Craig Ringer Dec 15 '11 at 04:08

1 Answers1

3

Timer is indeed an extremely bad idea in a Java EE environment. For example, if it throws an exception, it's completely killed and you'd basically need to restart the whole server to get it to run again. But ScheduledExecutorService should do it, if used wisely and with extreme care.

You'd need to create it at application wide level, not inside some EJB or any other container managed class (as the Java EE spec is trying to tell you). Even more, most Java EE reference implementations also use application wide executors under the covers to speed up loading. Think of for example Glassfish and Mojarra.

As to the application wide startup/shutdown hook which works in both Java EE and Java SE, a JDBC4 compatible driver get loaded automatically by ServiceLoader mechanism by /META-INF/services/java.sql.Driver file, also in WARs. It get unloaded only on JVM shutdown. For Java EE you could make use of /META-INF/services/javax.servlet.ServletContainerInitializer to add a ServletContextListener programmatically which explicitly unregisters the driver and shutdowns its thread pool on contextDestroyed().

Related:

Community
  • 1
  • 1
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • Greatly appreciated once again. It's been hard to see my way through the warnings about thread creation within a container to recognise that it *is* possible to do so safely with care and by following certain constraints. Thanks so much for pointing the way. – Craig Ringer Dec 15 '11 at 05:47
  • Would providing the ServletContainerInitializer to cleanup resources be the responsibility of the JDBC driver, or should the JDBC driver just provide a facility that does the cleanup which the user of the driver can then call from its own ServletContainerInitializer? – Mark Rotteveel Dec 20 '11 at 14:11
  • @Mark: it boils down to that the JDBC driver should provide a public facility that does the cleanup. Then it's up to the JDBC driver vendor if it would provide an initializer or delegate the responsibility to the enduser. – BalusC Dec 20 '11 at 14:19
  • @BalusC Thanks for the suggestion. I was actually having the same question as Craig, but then for Jaybird. If the driver would provide such clean up, any ideas how this could be handled in a pre-Servlet 3.0 environment (as ServletContainerInitializer was added om 3.0)? – Mark Rotteveel Dec 20 '11 at 14:23
  • @Mark: manually register it as `` in WAR's `/WEB-INF/web.xml` or in JAR's `/META-INF/*.tld` file. – BalusC Dec 20 '11 at 14:26