ASF Bugzilla – Attachment 34981 Details for
Bug 60963
Optimize class loading for unpackWARs=false case
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
updated patch
cade06c.diff (text/plain), 15.54 KB, created by
Thomas Meyer
on 2017-05-05 07:58:23 UTC
(
hide
)
Description:
updated patch
Filename:
MIME Type:
Creator:
Thomas Meyer
Created:
2017-05-05 07:58:23 UTC
Size:
15.54 KB
patch
obsolete
>From cade06c57f4a7d44e5dbd27b45a727cb7b0d1c67 Mon Sep 17 00:00:00 2001 >From: Thomas Meyer <thomas@m3y3r.de> >Date: Sat, 08 Apr 2017 21:43:45 +0200 >Subject: [PATCH] Optimize class loading for unpackWARs=false case > >Verify the JarWarResourceSet once and then skip to the correct PK entry >in the zip file. > >When running with unpackWARs="false", it's adviced to build your WAR >with this setting: ><archive> > <compress>false</compress> ></archive> >which results in bigger WAR files (depending on the number of classes >resp. libs) but much faster start-up times. > >(see also >https://maven.apache.org/plugins/maven-war-plugin/war-mojo.html) > >Rework: Add a basic ZIP file parser to extract the start offsets for all PK >entries in the ZIP file for fast forwarding. > >Rework: Exit early in parser when hitting a central directory header. > >Rework: Bug fix. Extend bitshift to long to make it work correctly. > >Rework: Simplify searchNextSignature() > >Change-Id: Ie99d9ebd1aea28f262ebc160687e93b9c47ff923 >--- > >diff --git a/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java b/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java >index 195b8a8..5f840cc 100644 >--- a/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java >+++ b/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java >@@ -179,7 +179,7 @@ > * @return The archives entries mapped to their names or null if > * {@link #getArchiveEntry(String)} should be used. > */ >- protected abstract HashMap<String,JarEntry> getArchiveEntries(boolean single); >+ protected abstract Map<String,JarEntry> getArchiveEntries(boolean single); > > > /** >diff --git a/java/org/apache/catalina/webresources/AbstractSingleArchiveResourceSet.java b/java/org/apache/catalina/webresources/AbstractSingleArchiveResourceSet.java >index 2257803..0d86ff9 100644 >--- a/java/org/apache/catalina/webresources/AbstractSingleArchiveResourceSet.java >+++ b/java/org/apache/catalina/webresources/AbstractSingleArchiveResourceSet.java >@@ -21,6 +21,7 @@ > import java.net.MalformedURLException; > import java.util.Enumeration; > import java.util.HashMap; >+import java.util.Map; > import java.util.jar.JarEntry; > import java.util.jar.JarFile; > >@@ -59,7 +60,7 @@ > > > @Override >- protected HashMap<String,JarEntry> getArchiveEntries(boolean single) { >+ protected Map<String,JarEntry> getArchiveEntries(boolean single) { > synchronized (archiveLock) { > if (archiveEntries == null && !single) { > JarFile jarFile = null; >diff --git a/java/org/apache/catalina/webresources/JarWarResource.java b/java/org/apache/catalina/webresources/JarWarResource.java >index 90321ee..caae522 100644 >--- a/java/org/apache/catalina/webresources/JarWarResource.java >+++ b/java/org/apache/catalina/webresources/JarWarResource.java >@@ -21,6 +21,8 @@ > import java.util.jar.JarEntry; > import java.util.jar.JarFile; > import java.util.jar.JarInputStream; >+import java.util.zip.ZipEntry; >+import java.util.zip.ZipInputStream; > > import org.apache.juli.logging.Log; > import org.apache.juli.logging.LogFactory; >@@ -47,25 +49,34 @@ > > @Override > protected JarInputStreamWrapper getJarInputStreamWrapper() { >- JarFile warFile = null; >- JarInputStream jarIs = null; >- JarEntry entry = null; > try { >- warFile = getArchiveResourceSet().openJarFile(); >- JarEntry jarFileInWar = warFile.getJarEntry(archivePath); >- InputStream isInWar = warFile.getInputStream(jarFileInWar); >- >- jarIs = new JarInputStream(isInWar); >- entry = jarIs.getNextJarEntry(); >- while (entry != null && >- !entry.getName().equals(getResource().getName())) { >- entry = jarIs.getNextJarEntry(); >- } >- >+ JarEntry entry = getArchiveResourceSet().getArchiveEntries(false).get(getResource().getName()); > if (entry == null) { > return null; > } >+ assert entry.getName() != null; > >+ JarFile warFile = getArchiveResourceSet().openJarFile(); >+ JarEntry jarFileInWar = warFile.getJarEntry(archivePath); >+ InputStream isInWar = warFile.getInputStream(jarFileInWar); >+ ZipInputStream jarIs = null; >+ ZipEntry searchedEntry = null; >+ >+ Long skipPos = ((JarWarResourceSet)getArchiveResourceSet()).getArchiveEntryPositions().get(entry.getName()); >+ if(skipPos != null) { >+ // parser did work correctly, fast forward >+ isInWar.skip(skipPos); >+ jarIs = new ZipInputStream(isInWar); >+ searchedEntry = jarIs.getNextEntry(); >+ } else { >+ // parser failed to parse zip file, use old seach method >+ jarIs = new JarInputStream(isInWar); >+ searchedEntry = jarIs.getNextEntry(); >+ while (searchedEntry != null && !entry.getName().equals(searchedEntry.getName())) { >+ searchedEntry = jarIs.getNextEntry(); >+ } >+ } >+ assert entry.getName().equals(searchedEntry.getName()); > return new JarInputStreamWrapper(entry, jarIs); > } catch (IOException e) { > if (log.isDebugEnabled()) { >@@ -73,19 +84,6 @@ > getResource().getName(), getBaseUrl()), e); > } > return null; >- } finally { >- if (entry == null) { >- if (jarIs != null) { >- try { >- jarIs.close(); >- } catch (IOException ioe) { >- // Ignore >- } >- } >- if (warFile != null) { >- getArchiveResourceSet().closeJarFile(); >- } >- } > } > } > >diff --git a/java/org/apache/catalina/webresources/JarWarResourceSet.java b/java/org/apache/catalina/webresources/JarWarResourceSet.java >index 7f16b63..8995672 100644 >--- a/java/org/apache/catalina/webresources/JarWarResourceSet.java >+++ b/java/org/apache/catalina/webresources/JarWarResourceSet.java >@@ -16,11 +16,19 @@ > */ > package org.apache.catalina.webresources; > >+import java.io.Closeable; > import java.io.File; > import java.io.IOException; > import java.io.InputStream; > import java.net.MalformedURLException; >+import java.nio.charset.StandardCharsets; >+import java.util.Collections; >+import java.util.EventListener; >+import java.util.EventObject; > import java.util.HashMap; >+import java.util.List; >+import java.util.Map; >+import java.util.concurrent.CopyOnWriteArrayList; > import java.util.jar.JarEntry; > import java.util.jar.JarFile; > import java.util.jar.JarInputStream; >@@ -39,6 +47,7 @@ > public class JarWarResourceSet extends AbstractArchiveResourceSet { > > private final String archivePath; >+ private Map<String, Long> archiveEntryPositions; > > /** > * Creates a new {@link org.apache.catalina.WebResourceSet} based on a JAR >@@ -94,24 +103,40 @@ > * returned. > */ > @Override >- protected HashMap<String,JarEntry> getArchiveEntries(boolean single) { >+ protected Map<String,JarEntry> getArchiveEntries(boolean single) { > synchronized (archiveLock) { > if (archiveEntries == null) { > JarFile warFile = null; >- InputStream jarFileIs = null; > archiveEntries = new HashMap<>(); >+ archiveEntryPositions = new HashMap<>(); >+ > try { > warFile = openJarFile(); > JarEntry jarFileInWar = warFile.getJarEntry(archivePath); >- jarFileIs = warFile.getInputStream(jarFileInWar); > >- try (JarInputStream jarIs = new JarInputStream(jarFileIs)) { >+ /* process and validate the JAR file */ >+ try (InputStream jarFileIs = warFile.getInputStream(jarFileInWar); >+ JarInputStream jarIs = new JarInputStream(jarFileIs)) { > JarEntry entry = jarIs.getNextJarEntry(); > while (entry != null) { > archiveEntries.put(entry.getName(), entry); > entry = jarIs.getNextJarEntry(); > } > setManifest(jarIs.getManifest()); >+ } >+ >+ /* process again, this time determine the positions of all PK entries in the InputStream */ >+ try (InputStream jarFileIs = warFile.getInputStream(jarFileInWar); >+ ZipEntryParser zipParser = new ZipEntryParser(jarFileIs)) { >+ /* sadly we are still stuck with Java 7... */ >+ ZipEventListener listener = new ZipEventListener() { >+ @Override >+ public void onZipEntry(ZipEvent ze) { >+ archiveEntryPositions.put(ze.entryName, ze.position); >+ } >+ }; >+ zipParser.addEventListener(listener); >+ zipParser.run(); > } > } catch (IOException ioe) { > // Should never happen >@@ -121,16 +146,9 @@ > if (warFile != null) { > closeJarFile(); > } >- if (jarFileIs != null) { >- try { >- jarFileIs.close(); >- } catch (IOException e) { >- // Ignore >- } >- } > } > } >- return archiveEntries; >+ return Collections.unmodifiableMap(archiveEntries); > } > } > >@@ -146,6 +164,9 @@ > throw new IllegalStateException("Coding error"); > } > >+ public Map<String, Long> getArchiveEntryPositions() { >+ return Collections.unmodifiableMap(archiveEntryPositions); >+ } > > //-------------------------------------------------------- Lifecycle methods > @Override >@@ -169,3 +190,166 @@ > } > } > } >+ >+class ZipEvent extends EventObject { >+ private static final long serialVersionUID = 1L; >+ final long position; >+ final String entryName; >+ public ZipEvent(Object source, long position, String entryName) { >+ super(source); >+ this.position = position; >+ this.entryName = entryName; >+ } >+} >+ >+interface ZipEventListener extends EventListener { >+ void onZipEntry(ZipEvent ze); >+} >+ >+/** >+ * Parses a ZIP format from an InputStream and determines the start offset in the stream >+ * for all valid PK entries. >+ * If parser encounters something it can't handle it will early abort processing >+ * See also https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT >+ */ >+class ZipEntryParser implements Runnable, Closeable { >+ private static final long SIG_LOCAL_FILE_HEADER = 0x04034b50l; >+ private static final long SIG_CENTRAL_DIRECTORY_HEADER = 0x02014b50l; >+ >+ private List<ZipEventListener> listeners = new CopyOnWriteArrayList<>(); >+ private long bytesRead; >+ private InputStream in; >+ >+ public ZipEntryParser(final InputStream in) throws IOException { >+ this.in = in; >+ } >+ >+ public void addEventListener(ZipEventListener listener) { >+ listeners.add(listener); >+ } >+ >+ @Override >+ public void run() { >+ try { >+ long signature = fromLittleEndian(in, 4); >+ while(signature == SIG_LOCAL_FILE_HEADER) { >+ // remember current position >+ long pos = bytesRead - 4l; >+ if(pos < 0) return; // something is fishy >+ >+ // skip "version needed to extract" >+ skip(2); >+ >+ long flags = fromLittleEndian(in, 2); >+ long compressionMethod = fromLittleEndian(in, 2); >+ >+ // skip "last mod file time", "last mod file date", "crc-32" >+ skip(8); >+ long compressedSize = fromLittleEndian(in, 4); >+ long uncompressedSize = fromLittleEndian(in, 4); >+ long fileNameLength = fromLittleEndian(in, 2); >+ long extraFieldLength = fromLittleEndian(in, 2); >+ byte[] fileName = new byte[(int) fileNameLength]; >+ long lex = fileNameLength, l = in.read(fileName); >+ if(l < lex) return; >+ count(l); >+ >+ boolean searchNextLocalFileSignature = false; >+ if((flags & 8l) > 1) { // correct values of "compressedSize" et al. are in data descriptor >+ if(compressionMethod == 0) { // file is stored, give up >+ return; >+ } else { >+ searchNextLocalFileSignature = true; >+ } >+ } >+ >+ skip(extraFieldLength); >+ >+ // we did fully parse the "local file header", fire event now >+ fireEvent(new ZipEvent(in, pos, new String(fileName, StandardCharsets.UTF_8))); // always assume UTF-8 >+ >+ skip(compressedSize); >+ >+ if(searchNextLocalFileSignature) { >+ signature = searchNextSignature(); >+ } else { >+ signature = fromLittleEndian(in, 4); >+ } >+ } >+ } catch(Exception e) { >+ // something bad happened, abort >+ } >+ } >+ >+ /** >+ * we try our luck and search for the next local file header resp. central directory header signature in the stream, >+ * this can result in wrong results >+ * @throws IOException in case of an IO error >+ */ >+ private long searchNextSignature() throws IOException { >+ long sig = fromLittleEndian(in, 4); >+ while(!(sig == SIG_LOCAL_FILE_HEADER || sig == SIG_CENTRAL_DIRECTORY_HEADER)) { >+ int b = in.read(); >+ if(b < 0) { >+ return -1; >+ } >+ count(1); >+ sig = ((sig >> 8) + (b << 24)) & 0xff_ff_ff_ffl; >+ } >+ return sig; >+ } >+ >+ private void fireEvent(ZipEvent zipEvent) { >+ for(ZipEventListener l : listeners) { >+ l.onZipEntry(zipEvent); >+ } >+ } >+ >+ private long skip(final long len) throws IOException { >+ final long al = in.skip(len); >+ if(al < len) >+ throw new IOException("premature end of data"); >+ count(al); >+ return al; >+ } >+ >+ /** >+ * Reads the given number of bytes from the given stream as a little endian long. >+ * @param in the stream to read from >+ * @param length the number of bytes representing the value >+ * @return the number read >+ * @throws IllegalArgumentException if len is bigger than eight >+ * @throws IOException if reading fails or the stream doesn't >+ * contain the given number of bytes anymore >+ */ >+ public long fromLittleEndian(InputStream in, int length) throws IOException { >+ long l = 0; >+ for (int i = 0; i < length; i++) { >+ long b = in.read(); >+ if (b == -1) { >+ throw new IOException("premature end of data"); >+ } >+ count(1); >+ l |= (b << (i * 8)); >+ } >+ return l; >+ } >+ /** >+ * Increments the counter of already read bytes. >+ * Doesn't increment if the EOF has been hit (read == -1) >+ * >+ * @param read the number of bytes read >+ */ >+ private void count(final long read) { >+ if (read != -1) { >+ bytesRead += read; >+ } >+ } >+ >+ @Override >+ public void close() throws IOException { >+ if(in != null) { >+ in.close(); >+ } >+ } >+}
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 60963
:
34902
|
34980
| 34981