mirror of https://github.com/sbt/sbt.git
Fix global lock implementation for ComponentManager
This commit is contained in:
parent
1a74b7e863
commit
8e08708792
|
|
@ -1,7 +1,7 @@
|
||||||
package xsbt
|
package xsbt
|
||||||
|
|
||||||
import java.io.{File,FileOutputStream}
|
import java.io.{File,FileOutputStream}
|
||||||
import ComponentManager.lock
|
import java.util.concurrent.Callable
|
||||||
|
|
||||||
/** A component manager provides access to the pieces of xsbt that are distributed as components.
|
/** A component manager provides access to the pieces of xsbt that are distributed as components.
|
||||||
* There are two types of components. The first type is compiled subproject jars with their dependencies.
|
* There are two types of components. The first type is compiled subproject jars with their dependencies.
|
||||||
|
|
@ -12,7 +12,7 @@ import ComponentManager.lock
|
||||||
* This is used for compiled source jars so that the compilation need not be repeated for other projects on the same
|
* This is used for compiled source jars so that the compilation need not be repeated for other projects on the same
|
||||||
* machine.
|
* machine.
|
||||||
*/
|
*/
|
||||||
class ComponentManager(provider: xsbti.ComponentProvider, val log: IvyLogger) extends NotNull
|
class ComponentManager(globalLock: xsbti.GlobalLock, provider: xsbti.ComponentProvider, val log: IvyLogger) extends NotNull
|
||||||
{
|
{
|
||||||
/** Get all of the files for component 'id', throwing an exception if no files exist for the component. */
|
/** Get all of the files for component 'id', throwing an exception if no files exist for the component. */
|
||||||
def files(id: String)(ifMissing: IfMissing): Iterable[File] =
|
def files(id: String)(ifMissing: IfMissing): Iterable[File] =
|
||||||
|
|
@ -39,8 +39,9 @@ class ComponentManager(provider: xsbti.ComponentProvider, val log: IvyLogger) ex
|
||||||
|
|
||||||
lockLocalCache { getOrElse(fromGlobal) }
|
lockLocalCache { getOrElse(fromGlobal) }
|
||||||
}
|
}
|
||||||
private def lockLocalCache[T](action: => T): T = lock("local cache", provider.lockFile, log) ( action )
|
private def lockLocalCache[T](action: => T): T = lock(provider.lockFile)( action )
|
||||||
private def lockGlobalCache[T](action: => T): T = lock("global cache", IvyCache.lockFile, log)( action )
|
private def lockGlobalCache[T](action: => T): T = lock(IvyCache.lockFile)( action )
|
||||||
|
private def lock[T](file: File)(action: => T): T = globalLock(file, new Callable[T] { def call = action })
|
||||||
/** Get the file for component 'id', throwing an exception if no files or multiple files exist for the component. */
|
/** Get the file for component 'id', throwing an exception if no files or multiple files exist for the component. */
|
||||||
def file(id: String)(ifMissing: IfMissing): File =
|
def file(id: String)(ifMissing: IfMissing): File =
|
||||||
files(id)(ifMissing).toList match {
|
files(id)(ifMissing).toList match {
|
||||||
|
|
@ -69,19 +70,3 @@ object IfMissing
|
||||||
object Fail extends IfMissing
|
object Fail extends IfMissing
|
||||||
final class Define(val cache: Boolean, define: => Unit) extends IfMissing { def apply() = define }
|
final class Define(val cache: Boolean, define: => Unit) extends IfMissing { def apply() = define }
|
||||||
}
|
}
|
||||||
object ComponentManager
|
|
||||||
{
|
|
||||||
def lock[T](label: String, file: File, log: IvyLogger)(action: => T): T =
|
|
||||||
{
|
|
||||||
synchronized {
|
|
||||||
val channel = new FileOutputStream(file).getChannel
|
|
||||||
try {
|
|
||||||
val freeLock = channel.tryLock
|
|
||||||
val lock = if(freeLock eq null) { log.info("Waiting for " + label + " to be available..."); channel.lock } else freeLock
|
|
||||||
try { action }
|
|
||||||
finally { lock.release() }
|
|
||||||
}
|
|
||||||
finally { channel.close() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,7 @@ private object NotInCache
|
||||||
/** Provides methods for working at the level of a single jar file with the default Ivy cache.*/
|
/** Provides methods for working at the level of a single jar file with the default Ivy cache.*/
|
||||||
object IvyCache
|
object IvyCache
|
||||||
{
|
{
|
||||||
def lockFile = new File(System.getProperty("temp.dir"), "sbt.cache.lock")
|
def lockFile = new File(System.getProperty("java.io.tmpdir"), "sbt.cache.lock")
|
||||||
/** Caches the given 'file' with the given ID. It may be retrieved or cleared using this ID.*/
|
/** Caches the given 'file' with the given ID. It may be retrieved or cleared using this ID.*/
|
||||||
def cacheJar(moduleID: ModuleID, file: File, log: IvyLogger)
|
def cacheJar(moduleID: ModuleID, file: File, log: IvyLogger)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -77,5 +77,5 @@ object ComponentManagerTest extends Specification
|
||||||
private def writeRandomContent(file: File) = FileUtilities.write(file, randomString)
|
private def writeRandomContent(file: File) = FileUtilities.write(file, randomString)
|
||||||
private def randomString = "asdf"
|
private def randomString = "asdf"
|
||||||
private def withManager[T](f: ComponentManager => T): T =
|
private def withManager[T](f: ComponentManager => T): T =
|
||||||
TestIvyLogger( logger => withTemporaryDirectory { temp => f(new ComponentManager(new xsbt.boot.ComponentProvider(temp), logger)) } )
|
TestIvyLogger( logger => withTemporaryDirectory { temp => f(new ComponentManager(xsbt.boot.Locks, new xsbt.boot.ComponentProvider(temp), logger)) } )
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package xsbt.boot
|
package xsbt.boot
|
||||||
|
|
||||||
import java.io.File
|
import java.io.{File, FileOutputStream}
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import java.util.concurrent.Callable
|
||||||
|
|
||||||
object Launch
|
object Launch
|
||||||
{
|
{
|
||||||
|
|
@ -55,6 +57,8 @@ class Launch(val bootDirectory: File, repositories: Seq[Repository]) extends xsb
|
||||||
|
|
||||||
lazy val topLoader = new BootFilteredLoader(getClass.getClassLoader)
|
lazy val topLoader = new BootFilteredLoader(getClass.getClassLoader)
|
||||||
|
|
||||||
|
def globalLock: xsbti.GlobalLock = Locks
|
||||||
|
|
||||||
class ScalaProvider(val version: String) extends xsbti.ScalaProvider with Provider
|
class ScalaProvider(val version: String) extends xsbti.ScalaProvider with Provider
|
||||||
{
|
{
|
||||||
def launcher: xsbti.Launcher = Launch.this
|
def launcher: xsbti.Launcher = Launch.this
|
||||||
|
|
@ -106,9 +110,61 @@ class ComponentProvider(baseDirectory: File) extends xsbti.ComponentProvider
|
||||||
else
|
else
|
||||||
Copy(files, location)
|
Copy(files, location)
|
||||||
}
|
}
|
||||||
def lockFile =
|
def lockFile = ComponentProvider.lockFile(baseDirectory)
|
||||||
|
}
|
||||||
|
object ComponentProvider
|
||||||
|
{
|
||||||
|
def lockFile(baseDirectory: File) =
|
||||||
{
|
{
|
||||||
baseDirectory.mkdirs()
|
baseDirectory.mkdirs()
|
||||||
new File(baseDirectory, "sbt.components.lock")
|
new File(baseDirectory, "sbt.components.lock")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// gets a file lock by first getting a JVM-wide lock.
|
||||||
|
object Locks extends xsbti.GlobalLock
|
||||||
|
{
|
||||||
|
import scala.collection.mutable.HashMap
|
||||||
|
private[this] val locks = new HashMap[File, GlobalLock]
|
||||||
|
def apply[T](file: File, action: Callable[T]) =
|
||||||
|
{
|
||||||
|
val canonFile = file.getCanonicalFile
|
||||||
|
synchronized { locks.getOrElseUpdate(canonFile, new GlobalLock(canonFile)).withLock(action) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private[this] class GlobalLock(file: File)
|
||||||
|
{
|
||||||
|
private[this] var fileLocked = false
|
||||||
|
def withLock[T](run: Callable[T]): T =
|
||||||
|
synchronized
|
||||||
|
{
|
||||||
|
if(fileLocked)
|
||||||
|
run.call
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fileLocked = true
|
||||||
|
try { withFileLock(run) }
|
||||||
|
finally { fileLocked = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private[this] def withFileLock[T](run: Callable[T]): T =
|
||||||
|
{
|
||||||
|
def withChannel(channel: FileChannel) =
|
||||||
|
{
|
||||||
|
val freeLock = channel.tryLock
|
||||||
|
if(freeLock eq null)
|
||||||
|
{
|
||||||
|
println("Waiting for lock on " + file + " to be available...");
|
||||||
|
val lock = channel.lock
|
||||||
|
try { run.call }
|
||||||
|
finally { lock.release() }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try { run.call }
|
||||||
|
finally { freeLock.release() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Using(new FileOutputStream(file).getChannel)(withChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package xsbti;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public interface GlobalLock
|
||||||
|
{
|
||||||
|
public <T> T apply(File lockFile, Callable<T> run);
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package xsbti;
|
package xsbti;
|
||||||
|
|
||||||
|
|
||||||
public interface Launcher
|
public interface Launcher
|
||||||
{
|
{
|
||||||
public static final int InterfaceVersion = 1;
|
public static final int InterfaceVersion = 1;
|
||||||
public ScalaProvider getScala(String version);
|
public ScalaProvider getScala(String version);
|
||||||
public ClassLoader topLoader();
|
public ClassLoader topLoader();
|
||||||
|
public GlobalLock globalLock();
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue