Make `ConnectionCustomizer` a bit easier to work with regarding class loaders
dpsutton opened this issue · comments
I want to be able to create a ConnectionCustomizer
to instrument our connection pools to monitor for activity for our health endpoint without necessarily hitting the database in the endpoint itself if we can avoid it.
But I'm having issues getting the class I'm creating visible to c3p0's classloader because we are using Clojure. Clojure uses a dynamic classloader to load classfiles compiled by each form.
In Clojure, I can do
(defrecord CheckinTracker []
ConnectionCustomizer
(onAcquire [_ _connection _identity-token])
(onCheckIn [_ _connection _identity-token])
(onCheckOut [_ _connection _identity-token])
(onDestroy [_ _connection _identity-token]))
with suitable bodies of these methods for our purposes. And then pass "connectionCustomizerClassName" (.getName CheckinTracker)
as connection properties.
the problem for us though is that c3p0 looks for these ConnectionCustomizer
with Class.forName
(source). This happens from threads created by a threadpool somewhere that has the application class loader rather than Clojure's dynamic classloader. I'm able to work around this issue with a bit of reflection and setting the instantiated customizer in the cache with
(let [field (doto (.getDeclaredField com.mchange.v2.c3p0.C3P0Registry "classNamesToConnectionCustomizers")
(.setAccessible true))]
(.put (.get field com.mchange.v2.c3p0.C3P0Registry)
(.getName CheckinTracker) (->CheckinTracker)))
and this works, but I was wondering if this could be made a bit easier. Some possible solutions:
- a static method to register connection customizers
- passing an instantiated connection customizer to one of
Datasources/pooledDataSource
methods - an easy way to configure the thread factories used by c3p0 so i can set my context class loader
Thank you for the lovely library otherwise and wondering if you have any thoughts about a way to make this easier.
Hi!
I am sorry for the long delay. I am not from this timeline.
c3p0 does have the config parameter contextClassLoaderSource
, and by default (ordinarily by default), that's the parent Thread's (in c3p0-speak the "caller") context ClassLoader, in practice the context ClassLoader
of the first Thread
to call getConnection()
on a PooledDataSource
. So, ensuring the ClassLoader
you want at that initializing call would be one way to go, although it seems a bit fragile.
As of c3p0-0.10.0, c3p0 allows you to take full control over threading by defining a TaskRunnerFactory
. So you can set whatever context ClassLoader
you want.
However, though its fun to use Executors and loom Threads and stuff, most users should have little reason to switch from c3p0's old-school, battle-hardened hand-rolled Thread pool. (c3p0 predates java.util.concurrent.*
) That's DefaultTaskRunnerFactory
. c3p0 plug-ins are all no-arg public constructor reflectons, because being old-school JavaBeans means we are happiest with simple sorts of properties. But if there were an abstract version of DefaultTaskRunnerFactory
with an abstract method like ClassLoader customContextClassLoader()
that returns either null
or an overriding context ClassLoader, might that help? Could you set-up the ClassLoader
you need somewhere it could be looked up prior to c3p0 init?
Let me know! If you are working at my pace, I'll expect to hear from you, er, sometime in the next year or two. 😊