All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fireflysource.common.pool.AsyncBoundObjectPool.kt Maven / Gradle / Ivy

There is a newer version: 5.0.2
Show newest version
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()




© 2015 - 2024 Weber Informatics LLC | Privacy Policy