Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cats-effect to the core module, remove modes #345

Merged
merged 21 commits into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
})
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's write one-liners like this as F.delay(...) instead of with curly braces.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a personal preference of putting by-name parameters in curly braces (if the parameter list only has one parameter), especially when lifting side effects. But I can adjust :)

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)

}
Loading