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

Write Cache to file #44

Merged
merged 7 commits into from
Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ lazy val server = (project in file("."))
// classpath used by Scala's reflection.
Test / fork := true,

// Compilation settings
libraryDependencies += "net.liftweb" %% "lift-json" % "3.5.0",
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.10",
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.2.1",
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.6.10",
Expand Down
9 changes: 9 additions & 0 deletions src/main/scala/viper/server/ViperConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ class ViperConfig(args: Seq[String]) extends ScallopConf(args) {
hidden = false
)

val cacheFile: ScallopOption[String] = opt[String]("cacheFile",
descr = ("Specifies the file from which the cache gets initialized on startup and to which the resulting cache gets written to."
+ "If it is not set, the cache will be initially empty and is only kept in memory, so it is not persisted during runs"
),
default = None,
noshort = true,
hidden = false
)

val nThreads: ScallopOption[Int] = opt[Int](
name = "nThreads",
descr = s"Maximal number of threads that should be used (not taking threads used by backend into account)\n" +
Expand Down
9 changes: 5 additions & 4 deletions src/main/scala/viper/server/core/VerificationWorker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val programId: St
_frontend.logger.debug(s"Verification has failed (errors: ${f.errors.map(_.readableMessage).mkString("\n")}; members: ${_ast.members.map(_.name).mkString(", ")})")
_frontend.reporter report OverallFailureMessage(_frontend.getVerifierName,
System.currentTimeMillis() - _frontend.startTime,
Failure(f.errors.filter { e => !e.cached }))
Failure(f.errors))
case None =>
}
}
Expand Down Expand Up @@ -181,8 +181,6 @@ class ViperBackend(private val _frontend: SilFrontend, private val programId: St
_frontend.setVerificationResult(ver_result)
_frontend.setState(DefaultStates.Verification)

_frontend.logger.debug(s"Latest verification result: $ver_result")

val meth_to_err_map: Seq[(Method, Option[List[AbstractVerificationError]])] = methodsToVerify.map((m: Method) => {
// Results come back irrespective of program Member.
val cacheable_errors: Option[List[AbstractVerificationError]] = for {
Expand Down Expand Up @@ -228,9 +226,12 @@ class ViperBackend(private val _frontend: SilFrontend, private val programId: St
}
}
} else {
_frontend.logger.debug(s"Inconsistent error splitting; no cache update for this verification attempt with ProgramID $programId.")
_frontend.logger.warn(s"Inconsistent error splitting; no cache update for this verification attempt with ProgramID $programId.")
}

// Write cache to file at the end of a verification run
ViperCache.writeToFile()
jogasser marked this conversation as resolved.
Show resolved Hide resolved

// combine errors:
if (all_cached_errors.nonEmpty) {
_frontend.getVerificationResult.get match {
Expand Down
249 changes: 165 additions & 84 deletions src/main/scala/viper/server/core/ViperCache.scala

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/main/scala/viper/server/core/ViperCoreServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ViperCoreServer(val config: ViperConfig)(implicit val executor: Verificati
println(s"Writing [level:${config.logLevel()}] logs into " +
s"${if (!config.logFile.isSupplied) "(default) " else ""}journal: ${logger.file.get}")

ViperCache.initialize(logger.get, config.backendSpecificCache())
ViperCache.initialize(logger.get, config.backendSpecificCache(), config.cacheFile.toOption)

super.start(config.maximumActiveJobs()) map { _ =>
logger.get.info(s"ViperCoreServer has started.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ViperHttpServer(config: ViperConfig)(executor: VerificationExecutionContex
_logger = ViperLogger("ViperServerLogger", config.getLogFileWithGuarantee, config.logLevel())
println(s"Writing [level:${config.logLevel()}] logs into ${if (!config.logFile.isSupplied) "(default) " else ""}journal: ${logger.file.get}")

ViperCache.initialize(logger.get, config.backendSpecificCache())
ViperCache.initialize(logger.get, config.backendSpecificCache(), config.cacheFile.toOption)

port = config.port.toOption.getOrElse(0) // 0 causes HTTP server to automatically select a free port
super.start(config.maximumActiveJobs()).map({ _ =>
Expand Down
101 changes: 54 additions & 47 deletions src/main/scala/viper/server/vsi/Cache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ package viper.server.vsi

import viper.silver.utility.CacheHelper

import scala.collection.mutable.{ListBuffer, Map => MutableMap}
import java.security.MessageDigest
import java.time.Instant
import scala.collection.mutable.ListBuffer

/** The goal of this generic caching trait is to provide:
*
Expand Down Expand Up @@ -46,10 +48,16 @@ abstract class Cache {
* The inner map is referred to as the fileCache. As the name indicates, it stores, for each
* file, a number of hashes and corresponding cache entries.
*/
protected val _cache: MutableMap[String, FileCash] = MutableMap()
type FileCash = MutableMap[String, List[CacheEntry]]
type FileKey = String
type MethodHash = String
jogasser marked this conversation as resolved.
Show resolved Hide resolved
type FileCache = Map[MethodHash, List[CacheEntry]]
type Cache = Map[FileKey, FileCache]
protected var _cache: Cache = Map()

protected val _program_cache: MutableMap[Ast, MutableMap[CacheableMember, List[Member]]] = MutableMap()
type ProgramHash = String
type DependencyHash = String
type DependendyMap = Map[MethodHash, DependencyHash]
protected var _program_cache: Map[ProgramHash, DependendyMap] = Map()

/** This method transforms a program and returns verification results based on the cache's
* current state.
Expand All @@ -72,40 +80,40 @@ abstract class Cache {
//read errors from cache
val cachable_members = input_prog.decompose()

val prog_dependencies = _program_cache.find({
case (k, _) => k.equals(input_prog)
})
val input_prog_hex = MessageDigest.getInstance("SHA-1")
.digest(input_prog.toString.getBytes("UTF-8"))
.map("%02x".format(_)).mkString

prog_dependencies match {
case Some((_, dep_map)) =>
cachable_members.foreach(cm => {
val dependencies = dep_map(cm)
get(file_key, cm, dependencies) match {
case Some(matched_entry) =>
concerningsToCache += cm.transform
cache_entries += matched_entry
case None =>
//Nothing in cache, request verification
concerningsToVerify += cm
}
})
val prog_dependencies = _program_cache.get(input_prog_hex)

val dep_map = prog_dependencies match {
case Some(dep_map) => dep_map
case None =>
val dep_map = MutableMap[CacheableMember, List[Member]]()
var dep_map = Map[String, String]()
cachable_members.foreach(cm => {
val dependencies = cm.getDependencies(input_prog)
dep_map += (cm -> dependencies)
get(file_key, cm, dependencies) match {
case Some(matched_entry) =>
concerningsToCache += cm.transform
cache_entries += matched_entry
case None =>
//Nothing in cache, request verification
concerningsToVerify += cm
}
val concerning_hash = cm.hash()
val dependency_hash = CacheHelper.buildHash(concerning_hash + cm.getDependencies(input_prog).map(_.hash()).mkString(" "))
dep_map = dep_map + (concerning_hash -> dependency_hash)
})
_program_cache += (input_prog -> dep_map)
_program_cache = _program_cache + (input_prog_hex -> dep_map)
dep_map
}

cachable_members.foreach(cm => {
val concerning_hash = cm.hash()
val dependency_hash = dep_map(concerning_hash)

get(file_key, concerning_hash, dependency_hash) match {
case Some(matched_entry) =>
matched_entry.lastAccessed = Instant.now()
concerningsToCache += cm.transform
cache_entries += matched_entry
case None =>
//Nothing in cache, request verification
concerningsToCache += cm
}
})

val all_concernings: List[CacheableMember] = concerningsToCache.toList ++ concerningsToVerify.toList
val output_prog: Ast = input_prog.compose(all_concernings)
(output_prog, cache_entries.toList)
Expand All @@ -114,18 +122,13 @@ abstract class Cache {
/** Utility function to retrieve entries for single members.
* */
final def get(file_key: String,
key: CacheableMember,
dependencies: List[Member]): Option[CacheEntry] = {

val concerning_hash = key.hash()
val dependencies_hash = dependencies.map(_.hash()).mkString(" ")
val dependency_hash = CacheHelper.buildHash(concerning_hash + dependencies_hash)
concerning_hash: String,
dependency_hash: String): Option[CacheEntry] = {
assert(concerning_hash != null)

for {
fileCache <- _cache.get(file_key)
cacheEntries <- fileCache.get(concerning_hash)
validEntry <- cacheEntries.find(_.depency_hash == dependency_hash)
validEntry <- cacheEntries.find(_.dependencyHash == dependency_hash)
} yield validEntry
}

Expand All @@ -147,7 +150,7 @@ abstract class Cache {
val concerning_hash = key.hash()
val dependencies_hash = dependencies.map(_.hash()).mkString(" ")
val dependency_hash = CacheHelper.buildHash(concerning_hash + dependencies_hash)
val new_entry: CacheEntry = new CacheEntry(key, content, dependency_hash)
val new_entry: CacheEntry = CacheEntry(key.hash(), content, dependency_hash)

assert(concerning_hash != null)

Expand All @@ -156,28 +159,32 @@ abstract class Cache {
val existing_entries = fileCache.getOrElse(concerning_hash, Nil)
val updated_cacheEntries = new_entry :: existing_entries

fileCache(concerning_hash) = updated_cacheEntries
val updatedFileCache = fileCache + (concerning_hash -> updated_cacheEntries)
_cache = _cache + (file_key -> updatedFileCache)
updated_cacheEntries
case None =>
//if file not in cache yet, create new map entry for it. Recall the function.
_cache += (file_key -> collection.mutable.Map[String, List[CacheEntry]]())
_cache = _cache + (file_key -> Map())
update(file_key, key, dependencies, content)
}
}

/** Resets the cache for a particular file.
* */
def forgetFile(file_key: String): Option[String] = {
_cache.remove(file_key) match {
val elem = _cache.get(file_key) match {
case Some(_) => Some(file_key)
case None => None
}
_cache -= file_key
elem
jogasser marked this conversation as resolved.
Show resolved Hide resolved
}

/** Resets the entire cache.
* */
def resetCache(): Unit = {
_cache.clear()
_program_cache = Map()
_cache = Map()
}
}

Expand All @@ -194,7 +201,7 @@ abstract class Cache {
* created. If, at some point, a members dependency hash is no longer equal to the one stored
* here, the entry is no longer valid.
* */
case class CacheEntry(concerning: CacheableMember, content: CacheContent, depency_hash: String)
case class CacheEntry(concerningHash: String, content: CacheContent, dependencyHash: String, created: Instant = Instant.now(), var lastAccessed: Instant = Instant.now())


// ===== AUXILIARY TRAITS ==================================================================
Expand Down Expand Up @@ -245,7 +252,7 @@ trait CacheableMember extends Member {
* terms of verification.
*
* A member may hit the cache, but the attached verification results might be invalid. Reason
* for this is that other members in the program have might have changed in such a way that
* for this is that other members in the program might have changed in such a way that
* they influenced this member's verification outcome. These influencing members are called
* dependencies and need to be checked when retrieving a member's attached result from cache.
* */
Expand Down