Michael
Michael

Reputation: 42100

How to implement immutable cache in Scala?

Suppose I have a server, which calls a time consuming function slow: Int => String upon a client request. If slow does not return within the timeout the server returns an error to the client.

def trySlow(timeout: Duration)(id: Int): Try[String] = {
  val fut = Future(slow(id))
  try {
    Await.ready(fut, timeout).value match {
      case Some(r) => r
      case None => Failure(new TimeoutException()) // should not happen
    }
  } catch {
     case e: TimeoutException => Failure(e)
  } 
}

Now I'd like to cache the futures so that multiple threads calling trySlow with the same id will wait for the same future.

I am going to use a mutable concurrent TrieMap to implement a singleton cache.

case class CacheEntry (
  future: Future[String], 
  start: Long = System.currentTimeMillis() // need it to clean the cache
) 

type Cache = TrieMap[Int, CacheEntry]

def trySlow(timeout: Duration, cache: Cache)(id: Int): Try[String] = {

  val fut = cache.getOrElseUpdate(id,  CacheEntry(Future(slow(id))))

  ... // as in above
}

Does it make sense ? How to do it with immutable non-singleton cache ?

Upvotes: 4

Views: 1183

Answers (2)

Rüdiger Klaehn
Rüdiger Klaehn

Reputation: 12565

If you want to use only things within scala collections, scala.collection.concurrent.TrieMap is a decent choice. However, please be aware that TrieMap#getOrElseUpdate had a thread safety bug that was only fixed recently in 2.11.6.

If you can afford the additional dependency, guava cache is quite good for writing such caches. Especially if you want entries in the cache to expire in some way.

Regarding the API of your cache: assuming you are talking about pure functions, the cache generator should be just a thing that takes a function T => U and returns a function T => U

So something along these lines:

object Cached {
  def apply[T,U](f: T=>U): T=>U = { ??? }
}

Usage:

def slow(id: Int): Try[String] = ??? // something complex including futures, timeouts etc.
val fast: Int => Try[String] = Cached(slow)

The caching API should not have to know anything about the function being cached, other than that you expect it to be pure.

Upvotes: 4

Margus
Margus

Reputation: 20068

I recommend you use guava libraries in general. (https://code.google.com/p/guava-libraries/)

Like Rüdiger Klaehn mentioned cache is a good place to start.

Upvotes: 1

Related Questions