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
orear
... 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?