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

com.github.mauricio.async.db.pool.SingleThreadedAsyncObjectPool.scala Maven / Gradle / Ivy

/*
 * Copyright 2013 Maurício Linhares
 *
 * Maurício Linhares licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.github.mauricio.async.db.pool

import java.util.concurrent.RejectedExecutionException

import com.github.mauricio.async.db.util.{Log, Worker}
import java.util.concurrent.atomic.AtomicLong
import java.util.{Timer, TimerTask}

import scala.collection.mutable.{ArrayBuffer, Queue, Stack}
import scala.concurrent.{Future, Promise}
import scala.util.{Failure, Success}

object SingleThreadedAsyncObjectPool {
  val Counter = new AtomicLong()
  val log = Log.get[SingleThreadedAsyncObjectPool[Nothing]]
}

/**
 *
 * Implements an [[com.github.mauricio.async.db.pool.AsyncObjectPool]] using a single thread from a
 * fixed executor service as an event loop to cause all calls to be sequential.
 *
 * Once you are done with this object remember to call it's close method to clean up the thread pool and
 * it's objects as this might prevent your application from ending.
 *
 * @param factory
 * @param configuration
 * @tparam T type of the object this pool holds
 */

class SingleThreadedAsyncObjectPool[T](
                                        factory: ObjectFactory[T],
                                        configuration: PoolConfiguration
                                        ) extends AsyncObjectPool[T] {

  import SingleThreadedAsyncObjectPool.{Counter, log}

  private val mainPool = Worker()
  private var poolables = new Stack[PoolableHolder[T]]()
  private val checkouts = new ArrayBuffer[T](configuration.maxObjects)
  private val waitQueue = new Queue[Promise[T]]()
  private val timer = new Timer("async-object-pool-timer-" + Counter.incrementAndGet(), true)
  timer.scheduleAtFixedRate(new TimerTask {
    def run() {
      mainPool.action {
        testObjects
      }
    }
  }, configuration.validationInterval, configuration.validationInterval)

  private var closed = false

  /**
   *
   * Asks for an object from the pool, this object should be returned to the pool when not in use anymore.
   *
   * @return
   */

  def take: Future[T] = {

    if (this.closed) {
      return Promise.failed(new PoolAlreadyTerminatedException).future
    }

    val promise = Promise[T]()
    this.checkout(promise)
    promise.future
  }

  /**
   *
   * Returns an object to the pool. The object is validated before being added to the collection
   * of available objects to make sure we have a usable object. If the object isn't valid it's discarded.
   *
   * @param item
   * @return
   */

  def giveBack(item: T): Future[AsyncObjectPool[T]] = {
    val promise = Promise[AsyncObjectPool[T]]()
    this.mainPool.action {
      // Ensure it came from this pool
      val idx = this.checkouts.indexOf(item)
      if(idx >= 0) {
        this.checkouts.remove(idx)
        this.factory.validate(item) match {
          case Success(item) => {
            this.addBack(item, promise)
          }
          case Failure(e) => {
            this.factory.destroy(item)
            promise.failure(e)
          }
        }
      } else {
        // It's already a failure but lets doublecheck why
        val isFromOurPool = (item match {
          case x: AnyRef => this.poolables.find(holder => x eq holder.item.asInstanceOf[AnyRef])
          case _ => this.poolables.find(holder => item == holder.item)
        }).isDefined

        if(isFromOurPool) {
          promise.failure(new IllegalStateException("This item has already been returned"))
        } else {
          promise.failure(new IllegalArgumentException("The returned item did not come from this pool."))
        }
      }
    }

    promise.future
  }

  def isFull: Boolean = this.poolables.isEmpty && this.checkouts.size == configuration.maxObjects

  def close: Future[AsyncObjectPool[T]] = {
    try {
      val promise = Promise[AsyncObjectPool[T]]()
      this.mainPool.action {
        if (!this.closed) {
          try {
            this.timer.cancel()
            this.mainPool.shutdown
            this.closed = true
            (this.poolables.map(i => i.item) ++ this.checkouts).foreach(item => factory.destroy(item))
            promise.success(this)
          } catch {
            case e: Exception => promise.failure(e)
          }
        } else {
          promise.success(this)
        }
      }
      promise.future
    } catch {
      case e: RejectedExecutionException if this.closed =>
        Future.successful(this)
    }
  }

  def availables: Traversable[T] = this.poolables.map(item => item.item)

  def inUse: Traversable[T] = this.checkouts

  def queued: Traversable[Promise[T]] = this.waitQueue

  def isClosed: Boolean = this.closed

  /**
   *
   * Adds back an object that was in use to the list of poolable objects.
   *
   * @param item
   * @param promise
   */

  private def addBack(item: T, promise: Promise[AsyncObjectPool[T]]) {
    this.poolables.push(new PoolableHolder[T](item))

    if (this.waitQueue.nonEmpty) {
      this.checkout(this.waitQueue.dequeue())
    }

    promise.success(this)
  }

  /**
   *
   * Enqueues a promise to be fulfilled in the future when objects are sent back to the pool. If
   * we have already reached the limit of enqueued objects, fail the promise.
   *
   * @param promise
   */

  private def enqueuePromise(promise: Promise[T]) {
    if (this.waitQueue.size >= configuration.maxQueueSize) {
      val exception = new PoolExhaustedException("There are no objects available and the waitQueue is full")
      exception.fillInStackTrace()
      promise.failure(exception)
    } else {
      this.waitQueue += promise
    }
  }

  private def checkout(promise: Promise[T]) {
    this.mainPool.action {
      if (this.isFull) {
        this.enqueuePromise(promise)
      } else {
        this.createOrReturnItem(promise)
      }
    }
  }

  /**
   *
   * Checks if there is a poolable object available and returns it to the promise.
   * If there are no objects available, create a new one using the factory and return it.
   *
   * @param promise
   */

  private def createOrReturnItem(promise: Promise[T]) {
    if (this.poolables.isEmpty) {
      try {
        val item = this.factory.create
        this.checkouts += item
        promise.success(item)
      } catch {
        case e: Exception => promise.failure(e)
      }
    } else {
      val item = this.poolables.pop().item
      this.checkouts += item
      promise.success(item)
    }
  }

  override def finalize() {
    this.close
  }

  /**
   *
   * Validates pooled objects not in use to make sure they are all usable, great if
   * you're holding onto network connections since you can "ping" the destination
   * to keep the connection alive.
   *
   */

  private def testObjects {
    val removals = new ArrayBuffer[PoolableHolder[T]]()
    this.poolables.foreach {
      poolable =>
        this.factory.test(poolable.item) match {
          case Success(item) => {
            if (poolable.timeElapsed > configuration.maxIdle) {
              log.debug("Connection was idle for {}, maxIdle is {}, removing it", poolable.timeElapsed, configuration.maxIdle)
              removals += poolable
              factory.destroy(poolable.item)
            }
          }
          case Failure(e) => {
            log.error("Failed to validate object", e)
            removals += poolable
            factory.destroy(poolable.item)
          }
        }
    }
    this.poolables = this.poolables.diff(removals)
  }

  private class PoolableHolder[T](val item: T) {
    val time = System.currentTimeMillis()

    def timeElapsed = System.currentTimeMillis() - time
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy