package xsbt.boot
import java.io.{ File, FileOutputStream, IOException }
import java.nio.channels.FileChannel
import java.util.concurrent.Callable
import scala.collection.immutable.List
import scala.annotation.tailrec
object GetLocks {
def find: xsbti.GlobalLock =
Loaders(getClass.getClassLoader.getParent).flatMap(tryGet).headOption.getOrElse(Locks)
private[this] def tryGet(loader: ClassLoader): List[xsbti.GlobalLock] =
try { getLocks0(loader) :: Nil } catch { case e: ClassNotFoundException => Nil }
private[this] def getLocks0(loader: ClassLoader) =
Class.forName("xsbt.boot.Locks$", true, loader).getField("MODULE$").get(null).asInstanceOf[xsbti.GlobalLock]
}
object Locks extends xsbti.GlobalLock {
private[this] val locks = new Cache[File, Unit, GlobalLock]((f, _) => new GlobalLock(f))
def apply[T](file: File, action: Callable[T]): T = if (file eq null) action.call else apply0(file, action)
private[this] def apply0[T](file: File, action: Callable[T]): T =
{
val lock =
synchronized {
file.getParentFile.mkdirs()
file.createNewFile()
locks(file.getCanonicalFile, ())
}
lock.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 { ignoringDeadlockAvoided(run) }
finally { fileLocked = false }
}
}
@tailrec private[this] def ignoringDeadlockAvoided[T](run: Callable[T]): T =
{
val result =
try { Some(withFileLock(run)) }
catch {
case i: IOException if isDeadlockAvoided(i) =>
Thread.sleep(200)
None
}
result match {
case Some(t) => t
case None => ignoringDeadlockAvoided(run)
}
}
private[this] def isDeadlockAvoided(i: IOException): Boolean =
i.getMessage == "Resource deadlock avoided"
private[this] def withFileLock[T](run: Callable[T]): T =
{
def withChannelRetries(retries: Int)(channel: FileChannel): T =
try { withChannel(channel) }
catch {
case i: InternalLockNPE =>
if (retries > 0) withChannelRetries(retries - 1)(channel) else throw i
}
def withChannel(channel: FileChannel) =
{
val freeLock = try { channel.tryLock } catch { case e: NullPointerException => throw new InternalLockNPE(e) }
if (freeLock eq null) {
System.out.println("Waiting for lock on " + file + " to be available...");
val lock = try { channel.lock } catch { case e: NullPointerException => throw new InternalLockNPE(e) }
try { run.call }
finally { lock.release() }
} else {
try { run.call }
finally { freeLock.release() }
}
}
Using(new FileOutputStream(file).getChannel)(withChannelRetries(5))
}
}
private[this] final class InternalLockNPE(cause: Exception) extends RuntimeException(cause)
}