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

com.eharmony.aloha.NoEvictionCache.scala Maven / Gradle / Ivy

The newest version!
package com.eharmony.aloha

import com.eharmony.aloha.reflect.{RefInfo, RefInfoOps}

import scala.collection.concurrent.TrieMap
import scala.concurrent.{ExecutionContext, Future, Promise}

/** A cache that is a hybrid of the Memoizer in Listing 5.19 in Java Concurrency in Practice and
  * https://www.bionicspirit.com/blog/2012/07/02/love-scala.html.  Makes sure to put a Promise
  * of the memoized value into the cache only if it's not already there.  Then only compute the
  * actual value (in a Future) and complete the Promise with the Future only if the promise was
  * successfully inserted atomically.  Otherwise, another promise was snuck in so use the Future
  * associated with that Promise.
  *
  * {{{
  * val cache = new NoEvictionCache
  * val slow = () => {Thread.sleep(5000); println("executing slow()"); 1}
  * val t1 = System.nanoTime                                      // Start the clock
  * val futures = Seq(                                            // Returns immediately (non-blocking)
  *       cache("one"){slow()},                                   // Only execute slow() once.
  *       cache("one"){slow()})
  * val a = Future.fold(futures)(0)(_+_)                          // Returns immediately (non-blocking)
  * val t2 = System.nanoTime
  * println("Runtime for setup: " + (1.0e-9*(t2-t1)).toFloat)     // Prints: "Runtime for setup: 0.001041"
  * a.onSuccess{ case i =>
  *   val t3 = System.nanoTime                                    // Determine elapsed time.
  *   println(i + ", runtime: " + (1.0e-9*(t3-t1)).toFloat)       // Prints: "2, runtime: 5.002082"
  * }
  * }}}
  * @param ec an execution context in which to run the futures that are created.
  */
class NoEvictionCache(implicit ec: ExecutionContext) {

    /**
     * The first string in the key is the "key" variable passed to apply.  The second is the string
     * representation of the TypeTag.
     */
    private[this] val cache = TrieMap.empty[(String, String), Promise[_]]

    /** Cache a value.
      * @param key string identifier
      * @param a call-by-name value to be cached or retrieved from the cache.
      * @tparam A type of value to be memoized
      * @return a Future containing the result of a
      */
    def apply[A: RefInfo](key: String)(a: => A): Future[A] = {
        val k = typedKey(key)
        val f = cache.getOrElse(k, createAndCache(k, a)).asInstanceOf[Future[A]]
        f
    }

    /**
      * @param k the key in the cache.  This is a combination of the string ID and the TypeTag string
      * representation.
      * @param a call-by-name value to be executed and cached or to be retrieved from the cache.
      * @tparam A type of value to be memoized
      * @return the promise in the cache associated with k
      */
    @inline private[this] def createAndCache[A](k: (String, String), a: => A): Promise[_] = {
        val p = Promise[A]
        val op = cache.putIfAbsent(k, p)                // Only insert p if another promise didn't race in.
        val p1 = op.getOrElse(p completeWith Future(a)) // Only execute a if another promise didn't race in.
        logging(k, op.isEmpty)                          // Can be overridden for testing if necessary.
        p1
    }

    /** Generate a cache key.
      * @param s string key
      * @tparam A type of value to be memoized
      * @return
      */
    @inline private[this] def typedKey[A: RefInfo](s: String) = (s, RefInfoOps.toString[A])

    /** Made protected so that it can be overridden in a test class to test caching.
      * @param k
      * @param inserting
      * @tparam A
      */
    protected[this] def logging[A](k: (String, String), inserting: Boolean) {
        // TODO: Logging // println("caching: " + k)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy