com.fireflysource.common.pool.AsyncBoundObjectPool.kt Maven / Gradle / Ivy
package com.fireflysource.common.pool
import com.fireflysource.common.coroutine.CoroutineDispatchers.scheduler
import com.fireflysource.common.coroutine.event
import com.fireflysource.common.coroutine.pollAll
import com.fireflysource.common.func.Callback
import com.fireflysource.common.lifecycle.AbstractLifeCycle
import com.fireflysource.common.sys.Result
import com.fireflysource.common.sys.SystemLogger
import com.fireflysource.common.track.FixedTimeLeakDetector
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.future.await
import java.util.*
import java.util.concurrent.*
/**
* @author Pengtao Qiu
*/
class AsyncBoundObjectPool(
private val maxSize: Int,
private val timeout: Long,
private val objectFactory: Pool.ObjectFactory,
private val validator: Pool.Validator,
private val dispose: Pool.Dispose,
leakDetectorInterval: Long,
releaseTimeout: Long,
noLeakCallback: Callback
) : AbstractLifeCycle(), AsyncPool {
companion object {
private val log = SystemLogger.create(AsyncBoundObjectPool::class.java)
}
private var createdCount = 0
private var size = 0
private val pool: LinkedList> = LinkedList()
private val waitQueue: LinkedList> = LinkedList()
private val poolMessageChannel: Channel> = Channel(Channel.UNLIMITED)
private val leakDetector = FixedTimeLeakDetector>(
scheduler,
leakDetectorInterval, leakDetectorInterval, releaseTimeout, TimeUnit.SECONDS,
noLeakCallback
)
private val handlePoolMessageJob = handlePoolMessage()
init {
start()
}
override suspend fun takePooledObject(): PooledObject = poll().await()
override fun poll(): CompletableFuture> {
val future = CompletableFuture>()
val timeoutJob: ScheduledFuture<*> = scheduler.schedule({
if (!future.isDone) {
future.completeExceptionally(TimeoutException("Take pooled object timeout"))
}
}, timeout, TimeUnit.SECONDS)
poolMessageChannel.trySend(PollObject(future, timeoutJob))
return future
}
private fun handlePoolMessage(): Job {
val job = event {
while (true) {
when (val message = poolMessageChannel.receive()) {
is PollObject -> handlePollObjectMessage(message)
is ReleaseObject -> handleReleaseObjectMessage(message)
}
}
}
job.invokeOnCompletion { cause ->
if (cause != null) {
log.info { "The pool message job completion. cause: ${cause.message}" }
}
clearMessage()
}
return job
}
private fun clearMessage() {
poolMessageChannel.pollAll {
when (it) {
is PollObject -> it.future.completeExceptionally(IllegalStateException("The pool has closed."))
is ReleaseObject -> it.future.completeExceptionally(IllegalStateException("The pool has closed."))
}
}
while (true) {
val m: PollObject? = waitQueue.poll()
if (m != null) {
m.future.completeExceptionally(IllegalStateException("The pool has closed."))
} else break
}
}
private suspend fun handlePollObjectMessage(message: PollObject) {
try {
val pooledObject = createNew() ?: getFromPool()
if (pooledObject != null) {
initPooledObject(pooledObject)
message.future.complete(pooledObject)
message.timeoutJob.cancel(true)
} else waitQueue.offer(message)
} catch (e: Exception) {
log.error(e) { "Handle poll object message exception." }
message.future.completeExceptionally(e)
message.timeoutJob.cancel(true)
}
}
private fun initPooledObject(pooledObject: PooledObject) {
pooledObject.released.set(false)
leakDetector.register(pooledObject) {
try {
pooledObject.leakCallback.accept(it)
} catch (e: Exception) {
log.error(e) { "The pooled object has leaked. object: $it ." }
} finally {
destroyPooledObject(it)
}
}
}
private suspend fun createNew(): PooledObject? = if (createdCount < maxSize) {
val pooledObject = objectFactory.createNew(this).await()
createdCount++
log.debug { "create a new object. $pooledObject" }
pooledObject
} else null
private suspend fun getFromPool(): PooledObject? {
val oldPooledObject: PooledObject? = pool.poll()
return if (oldPooledObject != null) {
size--
if (isValid(oldPooledObject)) {
log.debug { "get an old object. $oldPooledObject" }
oldPooledObject
} else {
destroyPooledObject(oldPooledObject)
val newPooledObject = createNew()
requireNotNull(newPooledObject)
newPooledObject
}
} else null
}
private fun destroyPooledObject(pooledObject: PooledObject) {
try {
dispose.destroy(pooledObject)
} catch (e: Exception) {
log.error(e) { "destroy pooled object exception." }
} finally {
log.debug { "destroy the object: $pooledObject ." }
createdCount--
if (createdCount < 0) {
log.error { "The created object count must not be less than 0" }
createdCount = 0
}
}
}
private suspend fun handleReleaseObjectMessage(message: ReleaseObject) {
val (pooledObject, future) = message
if (pooledObject.released.compareAndSet(false, true)) {
leakDetector.clear(pooledObject)
pool.offer(pooledObject)
size++
Result.done(future)
log.debug { "release pooled object: $pooledObject, pool size: ${size()}." }
handleWaitingMessage()
}
}
private suspend fun handleWaitingMessage() {
while (true) {
val pollObjectMessage: PollObject? = waitQueue.poll()
if (pollObjectMessage != null) {
if (!pollObjectMessage.future.isDone) {
handlePollObjectMessage(pollObjectMessage)
break
} else {
log.debug { "Discard the polling message when it is done." }
}
} else break
}
}
override fun release(pooledObject: PooledObject): CompletableFuture {
val future = CompletableFuture()
poolMessageChannel.trySend(ReleaseObject(pooledObject, future))
return future
}
override suspend fun putPooledObject(pooledObject: PooledObject) {
release(pooledObject).await()
}
override fun isValid(pooledObject: PooledObject): Boolean {
return try {
validator.isValid(pooledObject)
} catch (e: Exception) {
log.error(e) { "Valid pooled object exception" }
false
}
}
override fun size(): Int = size
override fun isEmpty(): Boolean {
return size() == 0
}
override fun getLeakDetector(): FixedTimeLeakDetector> = leakDetector
override fun getCreatedObjectCount(): Int = createdCount
override fun init() {
}
override fun destroy() {
leakDetector.stop()
handlePoolMessageJob.cancel(CancellationException("Cancel object pool message job exception."))
clearMessage()
pool.forEach { destroyPooledObject(it) }
pool.clear()
}
}
sealed class PoolMessage
class PollObject(val future: CompletableFuture>, val timeoutJob: ScheduledFuture<*>) :
PoolMessage()
data class ReleaseObject(val pooledObject: PooledObject, val future: CompletableFuture) : PoolMessage()