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

Disable unexpected compilation exceptions caching #40

Merged
merged 5 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.avsystem.scex
package compiler

import java.util.concurrent.{ExecutionException, TimeUnit}

import com.avsystem.scex.compiler.ScexCompiler.CompilationFailedException
import com.avsystem.scex.parsing.PositionMapping
import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator}
import com.google.common.cache.CacheBuilder
import com.google.common.util.concurrent.ExecutionError

import scala.util.Try
import java.util.concurrent.{ExecutionException, TimeUnit}
import scala.util.{Failure, Success, Try}

trait CachingScexCompiler extends ScexCompiler {

Expand Down Expand Up @@ -42,21 +42,40 @@ trait CachingScexCompiler extends ScexCompiler {
private val symbolValidatorsCache =
CacheBuilder.newBuilder.build[String, SymbolValidator]

private def invalidateCache(result: Try[_], invalidate: () => Unit): Unit = {
anetaporebska marked this conversation as resolved.
Show resolved Hide resolved
if (!settings.cacheUnexpectedCompilationExceptions.value)
result match {
case Failure(_: CompilationFailedException) | Success(_) =>
case Failure(_) => invalidate()
}
}

override protected def preprocess(expression: String, template: Boolean) =
unwrapExecutionException(
preprocessingCache.get((expression, template), callable(super.preprocess(expression, template))))

override protected def compileExpression(exprDef: ExpressionDef) =
unwrapExecutionException(
expressionCache.get(exprDef, callable(super.compileExpression(exprDef))))
override protected def compileExpression(exprDef: ExpressionDef) = {
val result = unwrapExecutionException(expressionCache.get(exprDef, callable(super.compileExpression(exprDef))))
invalidateCache(result, () => expressionCache.invalidate(exprDef))

override protected def compileProfileObject(profile: ExpressionProfile) =
unwrapExecutionException(underLock(
result
}

override protected def compileProfileObject(profile: ExpressionProfile) = {
val result = unwrapExecutionException(underLock(
profileCompilationResultsCache.get(profile, callable(super.compileProfileObject(profile)))))
invalidateCache(result, () => profileCompilationResultsCache.invalidate(profile))

override protected def compileExpressionUtils(source: NamedSource) =
unwrapExecutionException(underLock(
result
}

override protected def compileExpressionUtils(source: NamedSource) = {
val result = unwrapExecutionException(underLock(
utilsCompilationResultsCache.get(source.name, callable(super.compileExpressionUtils(source)))))
invalidateCache(result, () => utilsCompilationResultsCache.invalidate(source.name))

result
}

override protected def compileJavaGetterAdapters(profile: ExpressionProfile, name: String, classes: Seq[Class[_]], full: Boolean) =
unwrapExecutionException(underLock(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class ScexSettings extends Settings {
final val backwardsCompatCacheVersion = StringSetting("-SCEXbackwards-compat-cache-version", "versionString",
"Additional version string for controlling invalidation of classfile cache", "0")

final val cacheUnexpectedCompilationExceptions = BooleanSetting("-SCEXcache-unexpected-compilation-exceptions",
anetaporebska marked this conversation as resolved.
Show resolved Hide resolved
"Disables the caching of unexpected exceptions thrown during the expression compilation", default = true)
anetaporebska marked this conversation as resolved.
Show resolved Hide resolved

def resolvedClassfileDir: Option[PlainDirectory] = Option(classfileDirectory.value)
.filter(_.trim.nonEmpty).map(path => new PlainDirectory(new Directory(new File(path))))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.avsystem.scex.compiler

import com.avsystem.scex.ExpressionProfile
import com.avsystem.scex.compiler.ScexCompiler.CompileError
import com.avsystem.scex.japi.{DefaultJavaScexCompiler, JavaScexCompiler, ScalaTypeTokens}
import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext}
import com.google.common.util.concurrent.UncheckedExecutionException
import org.scalatest.funsuite.AnyFunSuite

final class ScexCompilationCachingTest extends AnyFunSuite with CompilationTest {

private var compilationCount = 0

private val settings = new ScexSettings
settings.classfileDirectory.value = "testClassfileCache"
settings.noGetterAdapters.value = true // to reduce number of compilations in tests

private val acl = PredefinedAccessSpecs.basicOperations
private val defaultProfile = createProfile(acl, utils = "val utilValue = 42")

private def createFailingCompiler: JavaScexCompiler =
new DefaultJavaScexCompiler(settings) {
override protected def compile(sourceFile: ScexSourceFile): Either[ScexClassLoader, List[CompileError]] = {
compilationCount += 1
if (compilationCount == 1) throw new NullPointerException()
else super.compile(sourceFile)
}
}

private def createCountingCompiler: JavaScexCompiler =
new DefaultJavaScexCompiler(settings) {
override protected def compile(sourceFile: ScexSourceFile): Either[ScexClassLoader, List[CompileError]] = {
compilationCount += 1
super.compile(sourceFile)
}
}

override def newProfileName(): String = "constant_name"

private def compileExpression(
compiler: JavaScexCompiler,
expression: String = s""""value"""",
profile: ExpressionProfile = defaultProfile,
): Unit = {
compiler.buildExpression
.contextType(ScalaTypeTokens.create[SimpleContext[Unit]])
.resultType(classOf[String])
.expression(expression)
.template(false)
.profile(profile)
.get
}

test("Unexpected exceptions should be cached by default") {
compilationCount = 0
val compiler = createFailingCompiler

assertThrows[UncheckedExecutionException](compileExpression(compiler))
assert(compilationCount == 1)
assertThrows[UncheckedExecutionException](compileExpression(compiler))
assert(compilationCount == 1) // result fetched from cache
}

test("Unexpected exceptions shouldn't be cached when disabled using ScexSettings") {
compilationCount = 0
val compiler = createFailingCompiler
compiler.settings.cacheUnexpectedCompilationExceptions.value = false

assertThrows[UncheckedExecutionException](compileExpression(compiler))
assert(compilationCount == 1) // utils compilation ended with NPE
compileExpression(compiler)
assert(compilationCount == 3) // 2x utils compilation + 1x final expression compilation
}

test("CompilationFailedExceptions should always be cached") {
compilationCount = 0
val compiler = createCountingCompiler
val profile = createProfile(acl, utils = """invalidValue""")

compiler.settings.cacheUnexpectedCompilationExceptions.value = true
assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile))
assert(compilationCount == 1)
assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile))
assert(compilationCount == 1)

compiler.settings.cacheUnexpectedCompilationExceptions.value = false
assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile))
assert(compilationCount == 1)
assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile))
assert(compilationCount == 1)
}

test("Successful compilation should always be cached") {
compilationCount = 0
val compiler = createCountingCompiler

compiler.settings.cacheUnexpectedCompilationExceptions.value = true
compileExpression(compiler)
assert(compilationCount == 2) // utils + expression value
compileExpression(compiler)
assert(compilationCount == 2)

compiler.settings.cacheUnexpectedCompilationExceptions.value = false
compileExpression(compiler)
assert(compilationCount == 2)
compileExpression(compiler)
assert(compilationCount == 2)
}
}