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

Caffeine cache API has inconsistent behavior with differing types and between sync/async #12354

Open
akervinen opened this issue Feb 9, 2024 · 1 comment

Comments

@akervinen
Copy link

akervinen commented Feb 9, 2024

Play Version

2.9.1

API

Scala 2.13.12

Operating System

Windows 10

JDK

openjdk version "17.0.10" 2024-01-16 LTS
OpenJDK Runtime Environment Corretto-17.0.10.7.1 (build 17.0.10+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.10.7.1 (build 17.0.10+7-LTS, mixed mode, sharing)

Library Dependencies

object playcaffeinerepro extends PlayModule with SingleModule {

  def scalaVersion = "2.13.12"
  def playVersion = "2.9.1"
  def twirlVersion = "1.6.2"

  object test extends PlayTests

  override def ivyDeps = super.ivyDeps() ++ Agg(caffeine())
}

Expected Behavior

If current sync.get behavior is expected:

  1. cache.set("foo", "bar")
  2. cache.get[Int]("foo") == None
  3. cache.getOrElseUpdate[Int]("foo")(1) == 1

But any consistent (and documented) behavior would be good.

Actual Behavior

Sync:

  1. sync.set("foo", "bar")
  2. sync.get[Int]("foo") == None
  3. sync.getOrElseUpdate[Int]("foo")(1) throws ClassCastException (inconsistent with sync.get)

Async:

  1. async.set("foo", "bar")
  2. async.get[Int]("foo") == Some("bar") (inconsistent with sync.get, ignores type parameter)
  3. async.getOrElseUpdate[Int]("foo")(Future(1)) throws ClassCastException (consistent with async.get)

I have not tested other cache implementations.

Reproducible Test Case

class CaffeineSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
  lazy val cache = inject[SyncCacheApi]
  lazy val asyncCache = inject[AsyncCacheApi]

  "Caffeine" when {
    "sync" should {
      "return None when type differs" in {
        cache.set("foo", "bar")
        val ret = cache.get[Int]("foo")
        ret mustBe None
      }

      "set via getOrElseUpdate" in {
        cache.set("foo", "bar")
        val ret = cache.getOrElseUpdate[Int]("foo")(1)
        ret mustBe 1
      }
    }

    "async" should {
      "return None when type differs" in {
        await(asyncCache.set("foo", "bar"))
        val ret = await(asyncCache.get[Int]("foo"))
        ret mustBe None
      }

      "set via getOrElseUpdate" in {
        await(asyncCache.set("foo", "bar"))
        val ret = await(asyncCache.getOrElseUpdate[Int]("foo")(Future.successful(1)))
        ret mustBe 1
      }
    }
  }
}
CaffeineSpec
Caffeine
  when sync
  - should return None when type differs
  - should set via getOrElseUpdate *** FAILED ***
    java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
    at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
    at CaffeineSpec.$anonfun$new$4(CaffeineSpec.scala:26)
    ...
  when async
  - should return None when type differs *** FAILED ***
    Some("bar") was not equal to None (CaffeineSpec.scala:35)
  - should set via getOrElseUpdate *** FAILED ***
    java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
    at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
    at CaffeineSpec.$anonfun$new$8(CaffeineSpec.scala:40)
    ...
Execution took 2.72s
4 tests, 1 passed, 3 failed
@mkurz
Copy link
Member

mkurz commented Feb 22, 2024

Thanks @akervinen.
This results definitely seem a bit strange. Having a consistent behavior would definitely make sense.
Would you be interested in providing a pull request to fix this?
Thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants