From 356c36a514f1a96cbb367b5b143888d1a3f00cfc Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Tue, 25 Mar 2025 05:28:11 +0300 Subject: [PATCH] Draft implementation of PreparedStatementsCompilationPhantomCache --- .../PreparedStatementWrapper.kt | 7 +- ...eparedStatementsCompilationPhantomCache.kt | 132 ++++++++++++++++++ .../_PhantomCacheCleanup.kt | 74 ++++++++++ 3 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.kt create mode 100644 src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/_PhantomCacheCleanup.kt diff --git a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt index 3b90140..4c7a5c3 100644 --- a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt +++ b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementWrapper.kt @@ -30,14 +30,18 @@ internal abstract class PreparedStatementWrapper(protected val _orig: PreparedSt private val _refcnt = CloseableReferenceCounter("This prepared statement was returned to pool to be used in future") private var _currentQuery: ResultSetWrapper? = null private var _closeOnCompletion: Boolean = false + private var _isClosed = false protected abstract fun _onClose() override fun close() { this._refcnt.close("Can't close prepared statement while it's in use") + this._isClosed = true this._onClose() } + override fun isClosed(): Boolean = this._isClosed + override fun closeOnCompletion() = this._refcnt.withRef { this._closeOnCompletion = true } @@ -344,9 +348,6 @@ internal abstract class PreparedStatementWrapper(protected val _orig: PreparedSt override fun getResultSetHoldability(): Int = this._refcnt.withRef(this._orig::getResultSetHoldability) - override fun isClosed(): Boolean = - this._refcnt.withRef(this._orig::isClosed) - override fun setPoolable(poolable: Boolean) { if (poolable) return diff --git a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.kt b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.kt new file mode 100644 index 0000000..0741957 --- /dev/null +++ b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationPhantomCache.kt @@ -0,0 +1,132 @@ +package ru.langrafhomyak.db.jdbc_resources_manager + +import java.sql.Connection +import java.sql.PreparedStatement +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty +import org.intellij.lang.annotations.Language + +public class PreparedStatementsCompilationPhantomCache( + @Language("SQL") + private val _statementSource: String +) : ReadOnlyProperty { + private val _mapSync = ReentrantLock() + private val _map = arrayOfNulls(7) + private var _connectionsCount = 0 + + override fun getValue(thisRef: Any?, property: KProperty<*>): Accessor = + this.Accessor() + + public inner class Accessor { + public operator fun invoke(connection: Connection): PreparedStatement = + this@PreparedStatementsCompilationPhantomCache.getForConnection(connection) + } + + private class ConnectionsMapNode( + owner: PreparedStatementsCompilationPhantomCache, + connection: Connection, + @JvmField + var nextNode: ConnectionsMapNode?, + @JvmField + val connectionHashCode: Int + ) { + @JvmField + val connection = owner.BoundPhantomReference(connection, this) + + @JvmField + var statement: PreparedStatement? = null + + @JvmField + val lock = ReentrantLock() + + @JvmField + var wrapper: BoundPreparedStatementWrapper? = null + } + + private fun _resizeIfNeeded() { + if (this._connectionsCount / 0.75 > this._map.size) { + val newMap = arrayOfNulls(this._map.size * 2) + for (s in this._map) { + var p = s + while (p != null) { + val i = Integer.remainderUnsigned(p.connectionHashCode, this._map.size) + val n = p.nextNode + p.nextNode = newMap[i] + newMap[i] = p + p = n + } + } + } + } + + private fun _getNode(connection: Connection): ConnectionsMapNode { + val newNode = this._mapSync.withLock { + val connectionHashCode = connection.hashCode() + val index = Integer.remainderUnsigned(connectionHashCode, this._map.size) + var p = this._map[index] + while (p != null) { + if (p.connection.refersTo(connection)) + return@_getNode p + p = p.nextNode + } + val node = ConnectionsMapNode(this, connection, this._map[index], connectionHashCode) + this._map[index] = node + this._connectionsCount++ + this._resizeIfNeeded() + return@withLock node + } + + newNode.statement = connection.prepareStatement(this._statementSource) + return newNode + } + + public fun getForConnection(connection: Connection): PreparedStatement { + val node = this._getNode(connection) + val wrapper: PreparedStatement + node.lock.withLock { + if (node.wrapper != null) + throw IllegalStateException("Requested prepared statement already used by this connection (or not closed after using)") + wrapper = BoundPreparedStatementWrapper(node.statement!!, node) + node.wrapper = wrapper + } + return wrapper + } + + + private inner class BoundPhantomReference( + ref: Connection, + private val descriptor: ConnectionsMapNode + ) : _PhantomCacheCleanup.CleanablePhantomReference(ref) { + override fun cleanup() { + this@PreparedStatementsCompilationPhantomCache._mapSync.withLock { + val i = Integer.remainderUnsigned(this.descriptor.connectionHashCode, this@PreparedStatementsCompilationPhantomCache._map.size) + var r = this@PreparedStatementsCompilationPhantomCache._map[i] + if (r === this.descriptor) { + this@PreparedStatementsCompilationPhantomCache._map[i] = r.nextNode + } + while (r != null) { + val n = r.nextNode + if (n === this.descriptor) { + r.nextNode = n.nextNode + break + } + r = n + } + } + } + } + + private class BoundPreparedStatementWrapper(orig: PreparedStatement, private val _node: ConnectionsMapNode) : PreparedStatementWrapper(orig) { + override fun _onClose() { + this._orig.clearParameters() + this._orig.clearBatch() + this._orig.clearWarnings() + this._orig.close() + this._node.lock.withLock { + this._node.wrapper = null + } + } + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/_PhantomCacheCleanup.kt b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/_PhantomCacheCleanup.kt new file mode 100644 index 0000000..dfe1942 --- /dev/null +++ b/src/jvmMain/kotlin/ru/langrafhomyak/db/jdbc_resources_manager/_PhantomCacheCleanup.kt @@ -0,0 +1,74 @@ +package ru.langrafhomyak.db.jdbc_resources_manager + +import java.lang.ref.PhantomReference +import java.lang.ref.ReferenceQueue + +internal object _PhantomCacheCleanup { + private val _thread: Thread + + @Suppress("MAY_BE_CONST") + private val _virtualThreadProperty = "ru.langrafhomyak.db.jdbc_resources_manager._PhantomCacheCleanup.USE_VIRTUAL_THREAD" + private val _referenceQueue = ReferenceQueue() + + + init { + if (this._isVirtualThreadsSupported() && System.getProperty(this._virtualThreadProperty)?.lowercase() != "false") + this._thread = this._startVirtualThread() + else + this._thread = this._startSystemThreadBeforeJ21() + } + + private fun _isVirtualThreadsSupported(): Boolean { + try { + Thread::ofVirtual + return true + } catch (_: NoSuchMethodError) { + return false + } + } + + @Suppress("MAY_BE_CONST") + private val _threadName = "ru.langrafhomyak.db.jdbc_resources_manager._PhantomCacheCleanup" + + private fun _startSystemThreadBeforeJ21(): Thread { + val t = Thread( + null, + CleanupRoutine, this._threadName, + 1024 * 16, false + ) + t.isDaemon = true + t.priority = Thread.MIN_PRIORITY + 1 + t.start() + return t + } + + private fun _startVirtualThread(): Thread { + val b = Thread.ofVirtual() + b.name(this._threadName) + return b.start(CleanupRoutine) + } + + + private object CleanupRoutine : Runnable { + override fun run() { + while (true) { + // (Thread.currentThread() as? InnocuousThread)?.eraseThreadLocals() + + try { + (this@_PhantomCacheCleanup._referenceQueue.remove(60 * 60) as CleanablePhantomReference<*>).cleanup() + } /* catch (_: LinkageError) { + return + } */ catch (_: InterruptedException) { + return + } catch (e: Throwable) { + e.printStackTrace() + } + } + + } + } + + abstract class CleanablePhantomReference(ref: T) : PhantomReference(ref, this@_PhantomCacheCleanup._referenceQueue) { + abstract fun cleanup() + } +} \ No newline at end of file