Skip to content

Commit

Permalink
Merge pull request #345 from kubukoz/cats-effect
Browse files Browse the repository at this point in the history
Add cats-effect to the core module, remove modes
  • Loading branch information
cb372 committed Jul 31, 2020
2 parents a4977d0 + d1c29ad commit 7fdf424
Show file tree
Hide file tree
Showing 49 changed files with 724 additions and 1,235 deletions.
1 change: 0 additions & 1 deletion .travis.yml
@@ -1,7 +1,6 @@
sudo: false
language: scala
scala:
- 2.11.12
- 2.13.1
- 2.12.10
jdk:
Expand Down
51 changes: 6 additions & 45 deletions build.sbt
Expand Up @@ -29,8 +29,6 @@ lazy val root: Project = Project(id = "scalacache", base = file("."))
memcached,
redis,
caffeine,
catsEffect,
scalaz72,
circe,
tests
)
Expand All @@ -42,6 +40,7 @@ lazy val core =
moduleName := "scalacache-core",
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.typelevel" %%% "cats-effect" % "2.1.3",
"org.scalatest" %%% "scalatest" % "3.0.8" % Test,
"org.scalacheck" %%% "scalacheck" % "1.14.3" % Test
),
Expand All @@ -51,10 +50,6 @@ lazy val core =
.jvmSettings(
libraryDependencies ++= Seq(
"org.slf4j" % "slf4j-api" % "1.7.30"
),
scala211OnlyDeps(
"org.squeryl" %% "squeryl" % "0.9.15" % Test,
"com.h2database" % "h2" % "1.4.200" % Test
)
)

Expand Down Expand Up @@ -96,38 +91,12 @@ lazy val caffeine = jvmOnlyModule("caffeine")
coverageFailOnMinimum := true
)

lazy val catsEffect = jvmOnlyModule("cats-effect")
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % "2.0.0"
),
coverageMinimum := 50,
coverageFailOnMinimum := true
)

lazy val scalaz72 = jvmOnlyModule("scalaz72")
.settings(
libraryDependencies ++= Seq(
"org.scalaz" %% "scalaz-concurrent" % "7.2.30"
),
coverageMinimum := 40,
coverageFailOnMinimum := true
)

def circeVersion(scalaVersion: String) =
CrossVersion.partialVersion(scalaVersion) match {
case Some((2, scalaMajor)) if scalaMajor >= 12 => "0.13.0"
case Some((2, scalaMajor)) if scalaMajor >= 11 => "0.11.1"
case _ =>
throw new IllegalArgumentException(s"Unsupported Scala version $scalaVersion")
}

lazy val circe = jvmOnlyModule("circe")
.settings(
libraryDependencies ++= Seq(
"io.circe" %% "circe-core" % circeVersion(scalaVersion.value),
"io.circe" %% "circe-parser" % circeVersion(scalaVersion.value),
"io.circe" %% "circe-generic" % circeVersion(scalaVersion.value) % Test,
"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-parser" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0" % Test,
scalacheck
),
coverageMinimum := 80,
Expand All @@ -136,7 +105,7 @@ lazy val circe = jvmOnlyModule("circe")

lazy val tests = jvmOnlyModule("tests")
.settings(publishArtifact := false)
.dependsOn(caffeine, memcached, redis, catsEffect, scalaz72, circe)
.dependsOn(caffeine, memcached, redis, circe)

lazy val docs = jvmOnlyModule("docs")
.enablePlugins(MicrositesPlugin)
Expand All @@ -160,8 +129,6 @@ lazy val docs = jvmOnlyModule("docs")
memcached,
redis,
caffeine,
catsEffect,
scalaz72,
circe
)

Expand Down Expand Up @@ -192,7 +159,7 @@ lazy val commonSettings =
mavenSettings ++
Seq(
organization := "com.github.cb372",
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature"),
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-language:higherKinds"),
parallelExecution in Test := false
)

Expand All @@ -202,9 +169,3 @@ lazy val mavenSettings = Seq(
false
}
)

def scala211OnlyDeps(moduleIDs: ModuleID*) =
libraryDependencies ++= (scalaBinaryVersion.value match {
case "2.11" => moduleIDs
case other => Nil
})
Expand Up @@ -9,24 +9,28 @@ import com.github.benmanes.caffeine.cache.Caffeine
import scalacache._
import caffeine._
import memoization._
import scalacache.modes.sync._
import cats.effect.SyncIO
import cats.effect.Clock

@State(Scope.Thread)
class CaffeineBenchmark {

val underlyingCache = Caffeine.newBuilder().build[String, Entry[String]]()
implicit val cache: Cache[String] = CaffeineCache(underlyingCache)
implicit val clockSyncIO = Clock.create[SyncIO]

val key = "key"
val underlyingCache = Caffeine.newBuilder().build[String, Entry[String]]()
implicit val cache: Cache[SyncIO, String] = CaffeineCache[SyncIO, String](underlyingCache)

val key = "key"
val value: String = "value"

def itemCachedNoMemoize(key: String): Id[Option[String]] = {
cache.get(key)
def itemCachedNoMemoize(key: String): Option[String] = {
cache.get(key).unsafeRunSync()
}

def itemCachedMemoize(key: String): String = memoizeSync(None) {
value
}
def itemCachedMemoize(key: String): String =
memoize(None) {
value
}.unsafeRunSync()

// populate the cache
cache.put(key)(value)
Expand Down
Expand Up @@ -5,25 +5,28 @@ import com.github.benmanes.caffeine.cache.Caffeine
import scalacache._
import scalacache.caffeine._
import scalacache.memoization._
import scalacache.modes.sync._
import cats.effect.SyncIO
import cats.effect.Clock

/**
* Just runs forever, endlessly calling memoize, so Java Flight Recorder can output sampling data.
*/
object ProfilingMemoize extends App {

val underlyingCache = Caffeine.newBuilder().build[String, Entry[String]]()
implicit val cache = CaffeineCache[String](underlyingCache)
implicit val clockSyncIO = Clock.create[SyncIO]
val underlyingCache = Caffeine.newBuilder().build[String, Entry[String]]()
implicit val cache = CaffeineCache[SyncIO, String](underlyingCache)

val key = "key"
val key = "key"
val value: String = "value"

def itemCachedMemoize(key: String): String = memoizeSync(None) {
value
}
def itemCachedMemoize(key: String): String =
memoize(None) {
value
}.unsafeRunSync()

var result: String = _
var i = 0L
var i = 0L

while (i < Long.MaxValue) {
result = itemCachedMemoize(key)
Expand Down
@@ -1,79 +1,83 @@
package scalacache.caffeine

import java.time.temporal.ChronoUnit
import java.time.{Clock, Instant}
import java.time.{Instant}

import com.github.benmanes.caffeine.cache.{Caffeine, Cache => CCache}

import cats.effect.Clock
import scalacache.logging.Logger
import scalacache.{AbstractCache, CacheConfig, Entry, Mode}
import scalacache.{AbstractCache, CacheConfig, Entry}
import scala.concurrent.duration.Duration
import scala.language.higherKinds
import cats.effect.Sync
import java.util.concurrent.TimeUnit
import cats.implicits._
import cats.MonadError

/*
* Thin wrapper around Caffeine.
*
* This cache implementation is synchronous.
*/
class CaffeineCache[V](val underlying: CCache[String, Entry[V]])(
class CaffeineCache[F[_]: Sync, V](val underlying: CCache[String, Entry[V]])(
implicit val config: CacheConfig,
clock: Clock = Clock.systemUTC()
) extends AbstractCache[V] {
clock: Clock[F]
) extends AbstractCache[F, V] {
protected val F: Sync[F] = Sync[F]

override protected final val logger = Logger.getLogger(getClass.getName)

def doGet[F[_]](key: String)(implicit mode: Mode[F]): F[Option[V]] = {
mode.M.delay {
val entry = underlying.getIfPresent(key)
val result = {
if (entry == null || entry.isExpired)
None
else
Some(entry.value)
def doGet(key: String): F[Option[V]] = {
F.delay {
Option(underlying.getIfPresent(key))
}
.flatMap(_.filterA(Entry.isExpired[F, V]))
.map(_.map(_.value))
.flatTap { result =>
logCacheHitOrMiss(key, result)
}
logCacheHitOrMiss(key, result)
result
}
}

def doPut[F[_]](key: String, value: V, ttl: Option[Duration])(implicit mode: Mode[F]): F[Any] = {
mode.M.delay {
val entry = Entry(value, ttl.map(toExpiryTime))
underlying.put(key, entry)
logCachePut(key, ttl)
def doPut(key: String, value: V, ttl: Option[Duration]): F[Unit] =
ttl.traverse(toExpiryTime).flatMap { expiry =>
F.delay {
val entry = Entry(value, expiry)
underlying.put(key, entry)
} *> logCachePut(key, ttl)
}
}

override def doRemove[F[_]](key: String)(implicit mode: Mode[F]): F[Any] =
mode.M.delay(underlying.invalidate(key))
override def doRemove(key: String): F[Unit] =
F.delay(underlying.invalidate(key))

override def doRemoveAll[F[_]]()(implicit mode: Mode[F]): F[Any] =
mode.M.delay(underlying.invalidateAll())
override def doRemoveAll(): F[Unit] =
F.delay(underlying.invalidateAll())

override def close[F[_]]()(implicit mode: Mode[F]): F[Any] = {
override def close: F[Unit] = {
// Nothing to do
mode.M.pure(())
F.unit
}

private def toExpiryTime(ttl: Duration): Instant =
Instant.now(clock).plus(ttl.toMillis, ChronoUnit.MILLIS)
private def toExpiryTime(ttl: Duration): F[Instant] =
clock.monotonic(TimeUnit.MILLISECONDS).map(Instant.ofEpochMilli(_).plusMillis(ttl.toMillis))

}

object CaffeineCache {

/**
* Create a new Caffeine cache
* Create a new Caffeine cache.
*/
def apply[V](implicit config: CacheConfig): CaffeineCache[V] =
apply(Caffeine.newBuilder().build[String, Entry[V]]())
def apply[F[_]: Sync: Clock, V](implicit config: CacheConfig): F[CaffeineCache[F, V]] =
Sync[F].delay(Caffeine.newBuilder().build[String, Entry[V]]()).map(apply(_))

/**
* Create a new cache utilizing the given underlying Caffeine cache.
*
* @param underlying a Caffeine cache
*/
def apply[V](underlying: CCache[String, Entry[V]])(implicit config: CacheConfig): CaffeineCache[V] =
def apply[F[_]: Sync: Clock, V](
underlying: CCache[String, Entry[V]]
)(implicit config: CacheConfig): CaffeineCache[F, V] =
new CaffeineCache(underlying)

}

0 comments on commit 7fdf424

Please sign in to comment.