From 84749cc1bb506659ab3741768180381bec2a7711 Mon Sep 17 00:00:00 2001 From: Sam Halliday Date: Sun, 11 Dec 2016 12:45:08 +0000 Subject: SI-9632 don't keep JarFile open in ZipArchive --- src/reflect/scala/reflect/io/ZipArchive.scala | 76 ++++++++++++++++++++------- 1 file changed, 58 insertions(+), 18 deletions(-) (limited to 'src/reflect') diff --git a/src/reflect/scala/reflect/io/ZipArchive.scala b/src/reflect/scala/reflect/io/ZipArchive.scala index 262ab22ce9..f4e1633af4 100644 --- a/src/reflect/scala/reflect/io/ZipArchive.scala +++ b/src/reflect/scala/reflect/io/ZipArchive.scala @@ -27,6 +27,8 @@ import scala.annotation.tailrec * ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ object ZipArchive { + private[io] val closeZipFile = sys.props.get("scala.classpath.closeZip").map(_.toBoolean).getOrElse(false) + /** * @param file a File * @return A ZipArchive if `file` is a readable zip file, otherwise null. @@ -120,31 +122,69 @@ abstract class ZipArchive(override val file: JFile) extends AbstractFile with Eq } /** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ final class FileZipArchive(file: JFile) extends ZipArchive(file) { + private[this] def openZipFile(): ZipFile = try { + new ZipFile(file) + } catch { + case ioe: IOException => throw new IOException("Error accessing " + file.getPath, ioe) + } + + private[this] class LazyEntry( + name: String, + time: Long, + size: Int + ) extends Entry(name) { + override def lastModified: Long = time // could be stale + override def input: InputStream = { + val zipFile = openZipFile() + val entry = zipFile.getEntry(name) + val delegate = zipFile.getInputStream(entry) + new FilterInputStream(delegate) { + override def close(): Unit = { zipFile.close() } + } + } + override def sizeOption: Option[Int] = Some(size) // could be stale + } + + // keeps a file handle open to ZipFile, which forbids file mutation + // on Windows, and leaks memory on all OS (typically by stopping + // classloaders from being garbage collected). But is slightly + // faster than LazyEntry. + private[this] class LeakyEntry( + zipFile: ZipFile, + zipEntry: ZipEntry + ) extends Entry(zipEntry.getName) { + override def lastModified: Long = zipEntry.getTime + override def input: InputStream = zipFile.getInputStream(zipEntry) + override def sizeOption: Option[Int] = Some(zipEntry.getSize.toInt) + } + lazy val (root, allDirs) = { val root = new DirEntry("/") val dirs = mutable.HashMap[String, DirEntry]("/" -> root) - val zipFile = try { - new ZipFile(file) - } catch { - case ioe: IOException => throw new IOException("Error accessing " + file.getPath, ioe) - } - + val zipFile = openZipFile() val enum = zipFile.entries() - while (enum.hasMoreElements) { - val zipEntry = enum.nextElement - val dir = getDir(dirs, zipEntry) - if (zipEntry.isDirectory) dir - else { - class FileEntry() extends Entry(zipEntry.getName) { - override def getArchive = zipFile - override def lastModified = zipEntry.getTime() - override def input = getArchive getInputStream zipEntry - override def sizeOption = Some(zipEntry.getSize().toInt) + try { + while (enum.hasMoreElements) { + val zipEntry = enum.nextElement + val dir = getDir(dirs, zipEntry) + if (zipEntry.isDirectory) dir + else { + val f = + if (ZipArchive.closeZipFile) + new LazyEntry( + zipEntry.getName(), + zipEntry.getTime(), + zipEntry.getSize().toInt + ) + else + new LeakyEntry(zipFile, zipEntry) + + dir.entries(f.name) = f } - val f = new FileEntry() - dir.entries(f.name) = f } + } finally { + if (ZipArchive.closeZipFile) zipFile.close() } (root, dirs) } -- cgit v1.2.3