Draft implementation of PreparedStatementsCompilationPhantomCache
This commit is contained in:
parent
0c553c7c3d
commit
356c36a514
@ -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 val _refcnt = CloseableReferenceCounter("This prepared statement was returned to pool to be used in future")
|
||||||
private var _currentQuery: ResultSetWrapper? = null
|
private var _currentQuery: ResultSetWrapper? = null
|
||||||
private var _closeOnCompletion: Boolean = false
|
private var _closeOnCompletion: Boolean = false
|
||||||
|
private var _isClosed = false
|
||||||
|
|
||||||
protected abstract fun _onClose()
|
protected abstract fun _onClose()
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
this._refcnt.close("Can't close prepared statement while it's in use")
|
this._refcnt.close("Can't close prepared statement while it's in use")
|
||||||
|
this._isClosed = true
|
||||||
this._onClose()
|
this._onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun isClosed(): Boolean = this._isClosed
|
||||||
|
|
||||||
override fun closeOnCompletion() =
|
override fun closeOnCompletion() =
|
||||||
this._refcnt.withRef { this._closeOnCompletion = true }
|
this._refcnt.withRef { this._closeOnCompletion = true }
|
||||||
|
|
||||||
@ -344,9 +348,6 @@ internal abstract class PreparedStatementWrapper(protected val _orig: PreparedSt
|
|||||||
override fun getResultSetHoldability(): Int =
|
override fun getResultSetHoldability(): Int =
|
||||||
this._refcnt.withRef(this._orig::getResultSetHoldability)
|
this._refcnt.withRef(this._orig::getResultSetHoldability)
|
||||||
|
|
||||||
override fun isClosed(): Boolean =
|
|
||||||
this._refcnt.withRef(this._orig::isClosed)
|
|
||||||
|
|
||||||
override fun setPoolable(poolable: Boolean) {
|
override fun setPoolable(poolable: Boolean) {
|
||||||
if (poolable)
|
if (poolable)
|
||||||
return
|
return
|
||||||
|
@ -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<Any?, PreparedStatementsCompilationPhantomCache.Accessor> {
|
||||||
|
private val _mapSync = ReentrantLock()
|
||||||
|
private val _map = arrayOfNulls<ConnectionsMapNode>(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<ConnectionsMapNode>(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<Connection>(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<Any?>()
|
||||||
|
|
||||||
|
|
||||||
|
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<T>(ref: T) : PhantomReference<T>(ref, this@_PhantomCacheCleanup._referenceQueue) {
|
||||||
|
abstract fun cleanup()
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user