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

com.github.t3hnar.scalax.util.ExpiringCache.scala Maven / Gradle / Ivy

The newest version!
package com.github.t3hnar.scalax.util

import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import scala.collection.concurrent.TrieMap

import scala.concurrent.{ ExecutionContext, Future }
import scala.concurrent.duration._

/**
 * @author Yaroslav Klymko, Sergiy Prydatchenko
 */
class ExpiringCache[K, V](
  val duration:      Long,
  val unit:          TimeUnit,
  val queryOverflow: Int              = 1000,
  executionContext:  ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global) {

  implicit val ec = executionContext

  def this(duration: FiniteDuration, queryOverflow: Int)(implicit ec: ExecutionContext) =
    this(duration.length, duration.unit, queryOverflow, ec)

  case class ExpiringValue(value: V, timestamp: Long)

  private[util] val map: TrieMap[K, ExpiringValue] = TrieMap.empty

  private val lock = new ReentrantLock()

  def get(key: K): Option[V] = {
    increaseQueryCount()
    maybeCleanExpired()
    map.get(key).collect {
      case ExpiringValue(value, timestamp) if !isExpired(timestamp) => value
    }
  }

  def put(entry: K, value: V): Option[V] =
    map.put(entry, ExpiringValue(value, currentMillis)) map (_.value)

  def remove(entry: K): Option[V] = map.remove(entry) map (_.value)

  protected def currentMillis = System.currentTimeMillis()

  @volatile private[util] var queryCount = 0

  protected def increaseQueryCount(): Unit = {
    queryCount = queryCount + 1
  }

  protected def maybeCleanExpired(): Unit = {
    if (queryCount >= queryOverflow) {
      try {
        lock.lock()
        if (queryCount >= queryOverflow) {
          queryCount = 0
          lock.unlock()
          cleanExpired()
        }
      } finally {
        if (lock.isHeldByCurrentThread) lock.unlock()
      }
    }
  }

  protected lazy val durationMillis = unit.toMillis(duration)
  protected def isExpired(timestamp: Long) =
    (timestamp + durationMillis) <= currentMillis

  def cleanExpired(): Unit = {
    Future {
      val expired = map.toList.collect {
        case (key, ExpiringValue(_, timestamp)) if isExpired(timestamp) => key
      }
      expired.foreach(map.remove)
    }
    ()
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy