-
Notifications
You must be signed in to change notification settings - Fork 452
Description
Driver version
7.1.3 (preview release)
Problem description
The shared single threaded timeout handler introduced in #842, once started via running a query on a connection with a non-zero queryTimeout, never stops. It keeps running even after all connections are closed. This will lead to a classloader issue in any environment with multiple classloaders (ex: Tomcat) that dynamically add and remove the driver class as the associated classes can never be unloaded.
I haven't recreated that situation myself (I don't use Tomcat) but recognized it as an issue as the PostgreSQL JDBC driver had a similar issue a couple years back. For background, the original pgjdbc issue reported by an end user is is here: pgjdbc/pgjdbc#188, PR that fixes it is here: pgjdbc/pgjdbc#197.
The TimeoutPoller also executes the timeout specific interrupt() action in the single timer thread. From skimming through the code I don't think that's an issue right now as the interrupts being executed are non-blocking. If that changes down the road, then it'd be possible for a single hanging interrupt() execution to cause the timer thread to stall indefinitely. That'd stall all timeouts on all connections using the same classloader as the thread is shared. Having the interrupt execution happen in a separate thread (say from a cached thread pool) should fix that.
Again, I'm not sure if that specific problem could happen as the only IO operations inside the interrupt() calls I could see are closing sockets but it could happen down the road with unrelated code changes. Synchronized blocks may lead to to a similar issue.
Expected Behavior
- When all connections are closed there should not be any extra threads running and all classes should be unloaded.
- Timeout
interrupt()operations from one command should not be able to stall timeouts for other commands.
Regarding actual implementation, since the newer versions of the driver target JDK 8+, I'd suggest using a shared ScheduledThreadPoolExecutor with a pool size of one (1) that could shutdown when there are no more connections referencing it. That'd be a slightly more modern version of what pgjdbc implemented.
One other advantage compared vs the current implementation of a single thread in a sleep / check loop is that that the standard implementation of ScheduledThreadPoolExecutor uses a heap to sort the timeout commands rather than looping through the entire list every time and it sleeps until the next timeout period rather than waking up every second.
If there's confirmation and internal API agreement that command specific interrupt() calls cannot invoke any blocking operation then it should be fine to have a single thread invoking them. Otherwise, the interupt() calls should be handled via a separate thread pool to ensure that one operation does not block all other timeouts.