refactor/use inMemory cache instead of AtomicReference for ClassPool

This commit is contained in:
hongwei 2024-10-29 13:12:57 +01:00
parent 7dcbbf3663
commit a7ec44067b
4 changed files with 69 additions and 13 deletions

View File

@ -284,6 +284,12 @@
<artifactId>scalacache-redis_${scala.version}</artifactId>
<version>0.9.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.cb372/scalacache-guava -->
<dependency>
<groupId>com.github.cb372</groupId>
<artifactId>scalacache-guava_${scala.version}</artifactId>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-slf4j_${scala.version}</artifactId>

View File

@ -36,6 +36,29 @@ object Caching extends MdcLoggable {
}
}
def memoizeSyncWithImMemory[A](cacheKey: Option[String])(ttl: Duration)(f: => A)(implicit m: Manifest[A]): A = {
(cacheKey, ttl) match {
case (_, t) if t == Duration.Zero => // Just forwarding a call
f
case (Some(_), _) => // Caching a call
InMemory.memoizeSyncWithInMemory(cacheKey)(ttl)(f)
case _ => // Just forwarding a call
f
}
}
def memoizeWithImMemory[A](cacheKey: Option[String])(ttl: Duration)(f: => Future[A])(implicit m: Manifest[A]): Future[A] = {
(cacheKey, ttl) match {
case (_, t) if t == Duration.Zero => // Just forwarding a call
f
case (Some(_), _) => // Caching a call
InMemory.memoizeWithInMemory(cacheKey)(ttl)(f)
case _ => // Just forwarding a call
f
}
}
/**
* the default MemoCacheBuilder for annotation OBPMemoize

View File

@ -0,0 +1,28 @@
package code.api.cache
import code.util.Helper.MdcLoggable
import com.google.common.cache.CacheBuilder
import scalacache.ScalaCache
import scalacache.guava.GuavaCache
import scalacache.memoization.{cacheKeyExclude, memoize, memoizeSync}
import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.language.postfixOps
import com.openbankproject.commons.ExecutionContext.Implicits.global
object InMemory extends MdcLoggable {
val underlyingGuavaCache = CacheBuilder.newBuilder().maximumSize(10000L).build[String, Object]
implicit val scalaCache = ScalaCache(GuavaCache(underlyingGuavaCache))
def memoizeSyncWithInMemory[A](cacheKey: Option[String])(@cacheKeyExclude ttl: Duration)(@cacheKeyExclude f: => A): A = {
logger.debug(s"InMemory.memoizeSyncWithInMemory.underlyingGuavaCache size ${underlyingGuavaCache.size()}, current cache key is $cacheKey")
memoizeSync(ttl)(f)
}
def memoizeWithInMemory[A](cacheKey: Option[String])(@cacheKeyExclude ttl: Duration)(@cacheKeyExclude f: => Future[A])(implicit @cacheKeyExclude m: Manifest[A]): Future[A] = {
logger.debug(s"InMemory.memoizeWithInMemory.underlyingGuavaCache size ${underlyingGuavaCache.size()}, current cache key is $cacheKey")
memoize(ttl)(f)
}
}

View File

@ -36,6 +36,7 @@ import code.api.UKOpenBanking.v3_1_0.OBP_UKOpenBanking_310
import code.api._
import code.api.berlin.group.v1.OBP_BERLIN_GROUP_1
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ErrorMessageBG, ErrorMessagesBG}
import code.api.cache.Caching
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
import code.api.dynamic.endpoint.helper.{DynamicEndpointHelper, DynamicEndpoints}
import code.api.dynamic.entity.OBPAPIDynamicEntity
@ -4227,10 +4228,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
case x => NewStyle.function.getCounterpartyByCounterpartyId(x, _)
}
// cache for method -> called obp methods:
// (className, methodName, signature) -> List[(className, methodName, signature)]
private val memo = new Memo[(String, String, String), List[(String, String, String)]]
private val classPool = {
val pool = ClassPool.getDefault
// avoid error when call with JDK 1.8:
@ -4238,13 +4235,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
pool.appendClassPath(new LoaderClassPath(Thread.currentThread.getContextClassLoader))
pool
}
//NOTE: this will be also a issue, to set the big object classLoader as the cache key.
private val memoClassPool = new Memo[ClassLoader, ClassPool]
private def getClassPool(classLoader: ClassLoader) = memoClassPool.memoize(classLoader){
val classPool = ClassPool.getDefault
classPool.appendClassPath(new LoaderClassPath(classLoader))
classPool
private def getClassPool(classLoader: ClassLoader) = {
import scala.concurrent.duration._
Caching.memoizeSyncWithImMemory(Some(classLoader.toString()))(DurationInt(30) days) {
val classPool: ClassPool = ClassPool.getDefault
classPool.appendClassPath(new LoaderClassPath(classLoader))
classPool
}
}
/**
@ -4339,8 +4337,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
* The comment will take "className = code.api.UKOpenBanking.v3_1_0.APIMethods_AccountAccessApi$$anonfun$createAccountAccessConsents$lzycompute$1"
* as an example to explain the whole code.
*/
def getObpTrace(clazzName: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] =
memo.memoize((clazzName, methodName, signature)) {
def getObpTrace(clazzName: String, methodName: String, signature: String, exclude: List[(String, String, String)] = Nil): List[(String, String, String)] = {
import scala.concurrent.duration._
Caching.memoizeSyncWithImMemory(Some(clazzName + methodName + signature))(DurationInt(30) days) {
// List:: className->methodName->signature, find all the dependent methods for one
val methods = getDependentMethods(clazzName, methodName, signature)
@ -4353,7 +4352,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
getObpTrace(clazzName, mName, mSignature, list ::: exclude)
}.flatten.distinct
}
}
//NOTE: MEMORY_USER this ctClass will be cached in ClassPool, it may load too many classes into heap.
//in scala the partialFunction is also treat as a class, we can get it from classPool
val endpointCtClass = classPool.get(endpointClassName)