diff options
-rw-r--r-- | stage1/KeyLockedLazyCache.scala | 33 | ||||
-rw-r--r-- | stage1/resolver.scala | 54 |
2 files changed, 87 insertions, 0 deletions
diff --git a/stage1/KeyLockedLazyCache.scala b/stage1/KeyLockedLazyCache.scala new file mode 100644 index 0000000..c8b37ea --- /dev/null +++ b/stage1/KeyLockedLazyCache.scala @@ -0,0 +1,33 @@ +/* +package cbt +import java.util.concurrent.ConcurrentHashMap +import scala.concurrent.Future + +/** +A cache that lazily computes values if needed during lookup. +Locking occurs on the key, so separate keys can be looked up +simultaneously without a deadlock. +*/ +final private[cbt] class KeyLockedLazyCache[Key <: AnyRef,Value]{ + private val keys = new ConcurrentHashMap[Key,LockableKey]() + private val builds = new ConcurrentHashMap[LockableKey,Value]() + + private class LockableKey + def get( key: Key, value: => Value ): Value = { + val keyObject = keys.synchronized{ + if( ! (keys containsKey key) ){ + keys.put( key, new LockableKey ) + } + keys get key + } + // synchronizing on key only, so asking for a particular key does + // not block the whole cache, but just that cache entry + key.synchronized{ + if( ! (builds containsKey keyObject) ){ + builds.put( keyObject, value ) + } + builds get keyObject + } + } +} +*/ diff --git a/stage1/resolver.scala b/stage1/resolver.scala index 30f03e2..566b37d 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -5,6 +5,8 @@ import java.io._ import scala.collection.immutable.Seq import scala.xml._ import paths._ +import scala.concurrent._ +import scala.concurrent.duration._ private final class Tree( val root: Dependency, computeChildren: => Seq[Tree] ){ lazy val children = computeChildren @@ -35,8 +37,60 @@ abstract class Dependency{ def canBeCached = false def cacheDependencyClassLoader = true + //private type BuildCache = KeyLockedLazyCache[Dependency, Future[ClassPath]] + def exportClasspathConcurrently: ClassPath = { + // FIXME: this should separate a blocking and a non-blocking EC + import scala.concurrent.ExecutionContext.Implicits.global + Await.result( + exportClasspathConcurrently( + transitiveDependencies + .collect{ case d: ArtifactInfo => d } + .groupBy( d => (d.groupId,d.artifactId) ) + .mapValues( _.head ) + //, new BuildCache + ), + Duration.Inf + ) + } + + def concurrencyEnabled = false + + /** + The implementation of this method is untested and likely buggy + at this stage. + */ + private object cacheExportClasspathConcurrently extends Cache[Future[ClassPath]] + private def exportClasspathConcurrently( + latest: Map[(String, String),ArtifactInfo]//, cache: BuildCache + )( implicit ec: ExecutionContext ): Future[ClassPath] = cacheExportClasspathConcurrently{ + Future.sequence( // trigger compilation / download of all dependencies first + this.dependencies.map{ + d => + // find out latest version of the required dependency + val l = d match { + case m: MavenDependency => latest( (m.groupId,m.artifactId) ) + case _ => d + } + // // trigger compilation if not already triggered + // cache.get( l, l.exportClasspathConcurrently( latest, cache ) ) + l.exportClasspathConcurrently( latest ) + } + ).map( + // merge dependency classpaths into one + ClassPath.flatten(_) + ).map( + _ => + // now that all dependencies are done, compile the code of this + exportedClasspath + ) + } + private object cacheClassLoaderBasicBuild extends Cache[URLClassLoader] def classLoader: URLClassLoader = cacheClassLoaderBasicBuild{ + if( concurrencyEnabled ){ + // trigger concurrent building / downloading dependencies + exportClasspathConcurrently + } val transitiveClassPath = transitiveDependencies.map{ case d if d.canBeCached => Left(d) case d => Right(d) |