StaticModule: Lazily initialize zip FileSystem

Java's zip FileSystem is rather memory-hungry, as it maintains the
full central directory in memory, among other internal state. In the
headless server case, we never actually need to serve a UI from the
war, so it's not necessary to even load the zip FileSystem.

Maintain a synchronized map of zip FileSystems in GerritLauncher,
since each one still needs to be a singleton, but we don't want to
eagerly load it just because we wanted to look up the war location.

Change-Id: Id186964b2a197a8a0e874f86c72c21e06fc1388f
This commit is contained in:
Dave Borowitz
2015-11-16 15:41:00 -05:00
parent 982c80de15
commit f3ff4db9c3
2 changed files with 167 additions and 129 deletions

View File

@@ -41,20 +41,17 @@ public class StaticModule extends ServletModule {
static final String CACHE = "static_content";
private final GerritOptions options;
private final FileSystem warFs;
private final Path buckOut;
private final Path unpackedWar;
private Paths paths;
public StaticModule(GerritOptions options) {
this.options = options;
warFs = getDistributionArchive();
if (warFs == null) {
buckOut = getDeveloperBuckOut();
unpackedWar = makeWarTempDir();
} else {
buckOut = null;
unpackedWar = null;
}
private Paths getPaths() {
if (paths == null) {
paths = new Paths();
}
return paths;
}
@Override
@@ -69,128 +66,161 @@ public class StaticModule extends ServletModule {
}
});
if (options.enablePolyGerrit()) {
servePolyGerritUi();
install(new PolyGerritUiModule());
} else if (options.enableDefaultUi()) {
serveGwtUi();
install(new GwtUiModule());
}
}
private void serveGwtUi() {
serveRegex("^/gerrit_ui/(?!rpc/)(.*)$")
.with(Key.get(HttpServlet.class, Names.named(GWT_UI_SERVLET)));
if (warFs == null && buckOut != null) {
filter("/").through(new RecompileGwtUiFilter(buckOut, unpackedWar));
private class GwtUiModule extends ServletModule {
@Override
public void configureServlets() {
serveRegex("^/gerrit_ui/(?!rpc/)(.*)$")
.with(Key.get(HttpServlet.class, Names.named(GWT_UI_SERVLET)));
Paths p = getPaths();
if (p.warFs == null && p.buckOut != null) {
filter("/").through(new RecompileGwtUiFilter(p.buckOut, p.unpackedWar));
}
}
}
private void servePolyGerritUi() {
serve("/").with(PolyGerritUiIndexServlet.class);
serve("/c/*").with(PolyGerritUiIndexServlet.class);
serve("/q/*").with(PolyGerritUiIndexServlet.class);
serve("/x/*").with(PolyGerritUiIndexServlet.class);
serve("/admin/*").with(PolyGerritUiIndexServlet.class);
serve("/dashboard/*").with(PolyGerritUiIndexServlet.class);
serve("/settings/*").with(PolyGerritUiIndexServlet.class);
// TODO(dborowitz): These fragments conflict with the REST API namespace, so
// they will need to use a different path.
//serve("/groups/*").with(PolyGerritUiIndexServlet.class);
//serve("/projects/*").with(PolyGerritUiIndexServlet.class);
if (warFs == null) {
serve("/bower_components/*")
.with(Key.get(PolyGerritUiServlet.class, Names.named(BOWER_SERVLET)));
}
serve("/*").with(PolyGerritUiServlet.class);
}
@Provides
@Singleton
@Named(GWT_UI_SERVLET)
HttpServlet getGwtUiServlet(@Named(CACHE) Cache<Path, Resource> cache)
throws IOException {
if (warFs != null) {
return new WarGwtUiServlet(cache, warFs);
} else {
return new DeveloperGwtUiServlet(cache, unpackedWar);
}
}
@Provides
@Singleton
PolyGerritUiIndexServlet getPolyGerritUiIndexServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiIndexServlet(cache, polyGerritBasePath());
}
@Provides
@Singleton
PolyGerritUiServlet getPolyGerritUiServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiServlet(cache, polyGerritBasePath());
}
@Provides
@Singleton
@Named(BOWER_SERVLET)
PolyGerritUiServlet getPolyGerritUiBowerServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiServlet(cache,
polyGerritBasePath().resolveSibling("bower_components"));
}
private Path polyGerritBasePath() {
return warFs != null
? warFs.getPath("/polygerrit_ui")
: buckOut.getParent().resolve("polygerrit-ui").resolve("app");
}
private static FileSystem getDistributionArchive() {
try {
return GerritLauncher.getDistributionArchiveFileSystem();
} catch (IOException e) {
if ((e instanceof FileNotFoundException)
&& GerritLauncher.NOT_ARCHIVED.equals(e.getMessage())) {
return null;
@Provides
@Singleton
@Named(GWT_UI_SERVLET)
HttpServlet getGwtUiServlet(@Named(CACHE) Cache<Path, Resource> cache)
throws IOException {
Paths p = getPaths();
if (p.warFs != null) {
return new WarGwtUiServlet(cache, p.warFs);
} else {
return new DeveloperGwtUiServlet(cache, p.unpackedWar);
}
}
}
private class PolyGerritUiModule extends ServletModule {
@Override
public void configureServlets() {
serve("/").with(PolyGerritUiIndexServlet.class);
serve("/c/*").with(PolyGerritUiIndexServlet.class);
serve("/q/*").with(PolyGerritUiIndexServlet.class);
serve("/x/*").with(PolyGerritUiIndexServlet.class);
serve("/admin/*").with(PolyGerritUiIndexServlet.class);
serve("/dashboard/*").with(PolyGerritUiIndexServlet.class);
serve("/settings/*").with(PolyGerritUiIndexServlet.class);
// TODO(dborowitz): These fragments conflict with the REST API namespace,
// so they will need to use a different path.
//serve("/groups/*").with(PolyGerritUiIndexServlet.class);
//serve("/projects/*").with(PolyGerritUiIndexServlet.class);
if (getPaths().warFs == null) {
serve("/bower_components/*").with(
Key.get(PolyGerritUiServlet.class, Names.named(BOWER_SERVLET)));
}
serve("/*").with(PolyGerritUiServlet.class);
}
@Provides
@Singleton
PolyGerritUiIndexServlet getPolyGerritUiIndexServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiIndexServlet(cache, polyGerritBasePath());
}
@Provides
@Singleton
PolyGerritUiServlet getPolyGerritUiServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiServlet(cache, polyGerritBasePath());
}
@Provides
@Singleton
@Named(BOWER_SERVLET)
PolyGerritUiServlet getPolyGerritUiBowerServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiServlet(cache,
polyGerritBasePath().resolveSibling("bower_components"));
}
private Path polyGerritBasePath() {
Paths p = getPaths();
return p.warFs != null
? p.warFs.getPath("/polygerrit_ui")
: p.buckOut.getParent().resolve("polygerrit-ui").resolve("app");
}
}
private static class Paths {
private final FileSystem warFs;
private final Path buckOut;
private final Path unpackedWar;
private Paths() {
try {
warFs = getDistributionArchive();
if (warFs == null) {
buckOut = getDeveloperBuckOut();
unpackedWar = makeWarTempDir();
} else {
buckOut = null;
unpackedWar = null;
}
} catch (IOException e) {
throw new ProvisionException(
"Error initializing static content paths", e);
}
}
private static FileSystem getDistributionArchive() throws IOException {
File war;
try {
war = GerritLauncher.getDistributionArchive();
} catch (IOException e) {
if ((e instanceof FileNotFoundException)
&& GerritLauncher.NOT_ARCHIVED.equals(e.getMessage())) {
return null;
} else {
ProvisionException pe =
new ProvisionException("Error reading gerrit.war");
pe.initCause(e);
throw pe;
}
}
return GerritLauncher.getZipFileSystem(war.toPath());
}
private static Path getDeveloperBuckOut() {
try {
return GerritLauncher.getDeveloperBuckOut();
} catch (FileNotFoundException e) {
return null;
}
}
private static Path makeWarTempDir() {
// Obtain our local temporary directory, but it comes back as a file
// so we have to switch it to be a directory post creation.
//
try {
File dstwar = GerritLauncher.createTempFile("gerrit_", "war");
if (!dstwar.delete() || !dstwar.mkdir()) {
throw new IOException("Cannot mkdir " + dstwar.getAbsolutePath());
}
// Jetty normally refuses to serve out of a symlinked directory, as
// a security feature. Try to resolve out any symlinks in the path.
//
try {
return dstwar.getCanonicalFile().toPath();
} catch (IOException e) {
return dstwar.getAbsoluteFile().toPath();
}
} catch (IOException e) {
ProvisionException pe =
new ProvisionException("Error reading gerrit.war");
new ProvisionException("Cannot create war tempdir");
pe.initCause(e);
throw pe;
}
}
}
private static Path getDeveloperBuckOut() {
try {
return GerritLauncher.getDeveloperBuckOut();
} catch (FileNotFoundException e) {
return null;
}
}
private static Path makeWarTempDir() {
// Obtain our local temporary directory, but it comes back as a file
// so we have to switch it to be a directory post creation.
//
try {
File dstwar = GerritLauncher.createTempFile("gerrit_", "war");
if (!dstwar.delete() || !dstwar.mkdir()) {
throw new IOException("Cannot mkdir " + dstwar.getAbsolutePath());
}
// Jetty normally refuses to serve out of a symlinked directory, as
// a security feature. Try to resolve out any symlinks in the path.
//
try {
return dstwar.getCanonicalFile().toPath();
} catch (IOException e) {
return dstwar.getAbsoluteFile().toPath();
}
} catch (IOException e) {
ProvisionException pe =
new ProvisionException("Cannot create war tempdir");
pe.initCause(e);
throw pe;
}
}
}

View File

@@ -38,7 +38,9 @@ import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.jar.Attributes;
@@ -300,9 +302,10 @@ public final class GerritLauncher {
}
private static volatile File myArchive;
private static volatile FileSystem myArchiveFs;
private static volatile File myHome;
private static final Map<Path, FileSystem> zipFileSystems = new HashMap<>();
/**
* Locate the JAR/WAR file we were launched from.
*
@@ -319,19 +322,24 @@ public final class GerritLauncher {
return result;
}
result = locateMyArchive();
myArchiveFs = FileSystems.newFileSystem(
URI.create("jar:" + result.toPath().toUri()),
Collections.<String, String> emptyMap());
myArchive = result;
}
}
return result;
}
public static FileSystem getDistributionArchiveFileSystem()
throws FileNotFoundException, IOException {
getDistributionArchive();
return myArchiveFs;
public static synchronized FileSystem getZipFileSystem(Path zip)
throws IOException {
// FileSystems canonicalizes the path, so we should too.
zip = zip.toRealPath();
FileSystem zipFs = zipFileSystems.get(zip);
if (zipFs == null) {
zipFs = FileSystems.newFileSystem(
URI.create("jar:" + zip.toUri()),
Collections.<String, String> emptyMap());
zipFileSystems.put(zip, zipFs);
}
return zipFs;
}
private static File locateMyArchive() throws FileNotFoundException {