Author: markt Date: Tue Jan 27 19:42:22 2015 New Revision: 1655135 URL: http://svn.apache.org/r1655135 Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=57472 Cache JarFile instances to speed up web application start, particularly with signed JARs.
Modified: tomcat/tc8.0.x/trunk/ (props changed) tomcat/tc8.0.x/trunk/java/org/apache/catalina/WebResourceSet.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardContext.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResource.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractFileResourceSet.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractResourceSet.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/EmptyResourceSet.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResource.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResourceSet.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResource.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/StandardRoot.java tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml Propchange: tomcat/tc8.0.x/trunk/ ------------------------------------------------------------------------------ --- svn:mergeinfo (original) +++ svn:mergeinfo Tue Jan 27 19:42:22 2015 @@ -1 +1 @@ -/tomcat/trunk:1636524,1637156,1637176,1637188,1637331,1637684,1637695,1638720-1638725,1639653,1640010,1640083-1640084,1640088,1640275,1640322,1640347,1640361,1640365,1640403,1640410,1640652,1640655-1640658,1640688,1640700-1640883,1640903,1640976,1640978,1641000,1641026,1641038-1641039,1641051-1641052,1641058,1641064,1641300,1641369,1641374,1641380,1641486,1641634,1641656-1641692,1641704,1641707-1641718,1641720-1641722,1641735,1641981,1642233,1642280,1642554,1642564,1642595,1642606,1642668,1642679,1642697,1642699,1642766,1643002,1643045,1643054-1643055,1643066,1643121,1643128,1643206,1643209-1643210,1643216,1643249,1643270,1643283,1643309-1643310,1643323,1643365-1643366,1643370-1643371,1643465,1643474,1643536,1643570,1643634,1643649,1643651,1643654,1643675,1643731,1643733-1643734,1643761,1643766,1643814,1643937,1643963,1644017,1644169,1644201-1644203,1644321,1644323,1644516,1644523,1644529,1644535,1644730,1644768,1644784-1644785,1644790,1644793,1644815,1644884,1644886,1644890,1644892 ,1644910,1644924,1644929-1644930,1644935,1644989,1645011,1645247,1645355,1645357-1645358,1645455,1645465,1645469,1645471,1645473,1645475,1645486-1645488,1645626,1645641,1645685,1645743,1645763,1645951-1645953,1645955,1645993,1646098-1646106,1646178,1646220,1646302,1646304,1646420,1646470-1646471,1646476,1646559,1646717-1646723,1646773,1647026,1647042,1647530,1647655,1648304,1648815,1648907,1650081,1650365,1651116,1651120,1651280,1651470,1652938,1652970,1653041,1653471,1653550,1653574,1653797,1653815-1653816,1653819,1653840,1653857,1653888,1653972,1654013,1654030,1654050,1654123,1654148,1654159,1654513,1654515,1654517,1654522,1654524,1654725,1654735,1654766,1654785,1654851-1654852,1654978 +/tomcat/trunk:1636524,1637156,1637176,1637188,1637331,1637684,1637695,1638720-1638725,1639653,1640010,1640083-1640084,1640088,1640275,1640322,1640347,1640361,1640365,1640403,1640410,1640652,1640655-1640658,1640688,1640700-1640883,1640903,1640976,1640978,1641000,1641026,1641038-1641039,1641051-1641052,1641058,1641064,1641300,1641369,1641374,1641380,1641486,1641634,1641656-1641692,1641704,1641707-1641718,1641720-1641722,1641735,1641981,1642233,1642280,1642554,1642564,1642595,1642606,1642668,1642679,1642697,1642699,1642766,1643002,1643045,1643054-1643055,1643066,1643121,1643128,1643206,1643209-1643210,1643216,1643249,1643270,1643283,1643309-1643310,1643323,1643365-1643366,1643370-1643371,1643465,1643474,1643536,1643570,1643634,1643649,1643651,1643654,1643675,1643731,1643733-1643734,1643761,1643766,1643814,1643937,1643963,1644017,1644169,1644201-1644203,1644321,1644323,1644516,1644523,1644529,1644535,1644730,1644768,1644784-1644785,1644790,1644793,1644815,1644884,1644886,1644890,1644892 ,1644910,1644924,1644929-1644930,1644935,1644989,1645011,1645247,1645355,1645357-1645358,1645455,1645465,1645469,1645471,1645473,1645475,1645486-1645488,1645626,1645641,1645685,1645743,1645763,1645951-1645953,1645955,1645993,1646098-1646106,1646178,1646220,1646302,1646304,1646420,1646470-1646471,1646476,1646559,1646717-1646723,1646773,1647026,1647042,1647530,1647655,1648304,1648815,1648907,1650081,1650365,1651116,1651120,1651280,1651470,1652938,1652970,1653041,1653471,1653550,1653574,1653797,1653815-1653816,1653819,1653840,1653857,1653888,1653972,1654013,1654030,1654050,1654123,1654148,1654159,1654513,1654515,1654517,1654522,1654524,1654725,1654735,1654766,1654785,1654851-1654852,1654978,1655122-1655124,1655126-1655127,1655129-1655130,1655132-1655133 Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/WebResourceSet.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/WebResourceSet.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/WebResourceSet.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/WebResourceSet.java Tue Jan 27 19:42:22 2015 @@ -145,4 +145,10 @@ public interface WebResourceSet extends * read-only, otherwise <code>false</code> */ boolean isReadOnly(); + + /** + * Hook to allow the WebResourceRoot to trigger regular tasks on this set of + * resources. + */ + void backgroundProcess(); } Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardContext.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardContext.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardContext.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardContext.java Tue Jan 27 19:42:22 2015 @@ -5216,6 +5216,13 @@ public class StandardContext extends Con broadcaster.sendNotification(notification); } + // The WebResources implementation caches references to JAR files. On + // some platforms these references may lock the JAR files. The + // WebResources implementaion cleans-up unused JAR file references every + // run of background processing but since web application start is + // likely to have read from lots of JARs, trigger a clean-up now. + getResources().backgroundProcess(); + // Reinitializing if something went wrong if (!ok) { setState(LifecycleState.FAILED); Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResource.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResource.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResource.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResource.java Tue Jan 27 19:42:22 2015 @@ -22,36 +22,31 @@ import java.net.MalformedURLException; import java.net.URL; import java.security.cert.Certificate; import java.util.jar.JarEntry; -import java.util.jar.JarFile; import java.util.jar.Manifest; -import org.apache.catalina.WebResourceRoot; - public abstract class AbstractArchiveResource extends AbstractResource { - private final String base; + private final AbstractArchiveResourceSet archiveResourceSet; private final String baseUrl; private final JarEntry resource; - private final Manifest manifest; private final String codeBaseUrl; private final String name; private boolean readCerts = false; private Certificate[] certificates; - protected AbstractArchiveResource(WebResourceRoot root, String webAppPath, - String base, String baseUrl, JarEntry jarEntry, - String internalPath, Manifest manifest, String codeBaseUrl) { - super(root, webAppPath); - this.base = base; + protected AbstractArchiveResource(AbstractArchiveResourceSet archiveResourceSet, + String webAppPath, String baseUrl, JarEntry jarEntry, String codeBaseUrl) { + super(archiveResourceSet.getRoot(), webAppPath); + this.archiveResourceSet = archiveResourceSet; this.baseUrl = baseUrl; this.resource = jarEntry; - this.manifest = manifest; this.codeBaseUrl = codeBaseUrl; String resourceName = resource.getName(); if (resourceName.charAt(resourceName.length() - 1) == '/') { resourceName = resourceName.substring(0, resourceName.length() - 1); } + String internalPath = archiveResourceSet.getInternalPath(); if (internalPath.length() > 0 && resourceName.equals( internalPath.subSequence(1, internalPath.length()))) { name = ""; @@ -65,8 +60,12 @@ public abstract class AbstractArchiveRes } } + protected AbstractArchiveResourceSet getArchiveResourceSet() { + return archiveResourceSet; + } + protected final String getBase() { - return base; + return archiveResourceSet.getBase(); } protected final String getBaseUrl() { @@ -205,7 +204,7 @@ public abstract class AbstractArchiveRes @Override public Manifest getManifest() { - return manifest; + return archiveResourceSet.getManifest(); } @Override @@ -215,15 +214,19 @@ public abstract class AbstractArchiveRes protected abstract JarInputStreamWrapper getJarInputStreamWrapper(); + /** + * This wrapper assumes that the InputStream was created from a JarFile + * obtained from a call to getArchiveResourceSet().getJarFile(). If this is + * not the case then the usage counting in AbstractArchiveResourceSet will + * break and the JarFile may be unexpectedly closed. + */ protected class JarInputStreamWrapper extends InputStream { - private final JarFile jarFile; private final JarEntry jarEntry; private final InputStream is; - public JarInputStreamWrapper(JarFile jarFile, JarEntry jarEntry, InputStream is) { - this.jarFile = jarFile; + public JarInputStreamWrapper(JarEntry jarEntry, InputStream is) { this.jarEntry = jarEntry; this.is = is; } @@ -261,9 +264,7 @@ public abstract class AbstractArchiveRes @Override public void close() throws IOException { - // Closing the JarFile releases the file lock on the JAR and also - // closes all input streams created from the JarFile. - jarFile.close(); + archiveResourceSet.closeJarFile(); } Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java Tue Jan 27 19:42:22 2015 @@ -17,6 +17,7 @@ package org.apache.catalina.webresources; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; @@ -24,6 +25,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Set; import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.catalina.WebResource; @@ -37,11 +39,19 @@ public abstract class AbstractArchiveRes private String baseUrlString; private Manifest manifest; + private JarFile archive = null; + private final Object archiveLock = new Object(); + private long archiveUseCount = 0; + protected final void setManifest(Manifest manifest) { this.manifest = manifest; } + protected final Manifest getManifest() { + return manifest; + } + protected final void setBaseUrl(URL baseUrl) { this.baseUrl = baseUrl; if (baseUrl == null) { @@ -266,4 +276,34 @@ public abstract class AbstractArchiveRes throw new IllegalArgumentException( sm.getString("abstractArchiveResourceSet.setReadOnlyFalse")); } + + protected JarFile openJarFile() throws IOException { + synchronized (archiveLock) { + if (archive == null) { + archive = new JarFile(getBase()); + } + archiveUseCount++; + return archive; + } + } + + protected void closeJarFile() { + synchronized (archiveLock) { + archiveUseCount--; + } + } + + @Override + public void backgroundProcess() { + synchronized (archiveLock) { + if (archive != null && archiveUseCount == 0) { + try { + archive.close(); + } catch (IOException e) { + // Log at least WARN + } + archive = null; + } + } + } } Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractFileResourceSet.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractFileResourceSet.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractFileResourceSet.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractFileResourceSet.java Tue Jan 27 19:42:22 2015 @@ -126,8 +126,19 @@ public abstract class AbstractFileResour } } + /** + * {@inheritDoc} + * <p> + * This is a NO-OP by default for File based resource sets. + */ + @Override + public void backgroundProcess() { + // NO-OP + } + //-------------------------------------------------------- Lifecycle methods + @Override protected void initInternal() throws LifecycleException { fileBase = new File(getBase(), getInternalPath()); Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractResourceSet.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractResourceSet.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractResourceSet.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/AbstractResourceSet.java Tue Jan 27 19:42:22 2015 @@ -111,6 +111,7 @@ public abstract class AbstractResourceSe this.staticOnly = staticOnly; } + //-------------------------------------------------------- Lifecycle methods @Override protected final void startInternal() throws LifecycleException { Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/EmptyResourceSet.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/EmptyResourceSet.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/EmptyResourceSet.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/EmptyResourceSet.java Tue Jan 27 19:42:22 2015 @@ -153,6 +153,11 @@ public class EmptyResourceSet extends Li } @Override + public void backgroundProcess() { + // NO-OP + } + + @Override protected void initInternal() throws LifecycleException { // NO-OP } Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResource.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResource.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResource.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResource.java Tue Jan 27 19:42:22 2015 @@ -20,9 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.jar.Manifest; -import org.apache.catalina.WebResourceRoot; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -34,21 +32,19 @@ public class JarResource extends Abstrac private static final Log log = LogFactory.getLog(JarResource.class); - public JarResource(WebResourceRoot root, String webAppPath, String base, - String baseUrl, JarEntry jarEntry, String internalPath, - Manifest manifest) { - super(root, webAppPath, base, "jar:" + baseUrl, jarEntry, internalPath, - manifest, baseUrl); + public JarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath, + String baseUrl, JarEntry jarEntry) { + super(archiveResourceSet, webAppPath, "jar:" + baseUrl, jarEntry, baseUrl); } @Override protected JarInputStreamWrapper getJarInputStreamWrapper() { try { - JarFile jarFile = new JarFile(getBase()); + JarFile jarFile = getArchiveResourceSet().openJarFile(); // Need to create a new JarEntry so the certificates can be read JarEntry jarEntry = jarFile.getJarEntry(getResource().getName()); InputStream is = jarFile.getInputStream(jarEntry); - return new JarInputStreamWrapper(jarFile, jarEntry, is); + return new JarInputStreamWrapper(jarEntry, is); } catch (IOException e) { if (log.isDebugEnabled()) { log.debug(sm.getString("jarResource.getInputStreamFail", Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResourceSet.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResourceSet.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResourceSet.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarResourceSet.java Tue Jan 27 19:42:22 2015 @@ -78,8 +78,7 @@ public class JarResourceSet extends Abst @Override protected WebResource createArchiveResource(JarEntry jarEntry, String webAppPath, Manifest manifest) { - return new JarResource(getRoot(), webAppPath, getBase(), getBaseUrlString(), - jarEntry, getInternalPath(), manifest); + return new JarResource(this, webAppPath, getBaseUrlString(), jarEntry); } //-------------------------------------------------------- Lifecycle methods Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResource.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResource.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResource.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResource.java Tue Jan 27 19:42:22 2015 @@ -21,9 +21,7 @@ import java.io.InputStream; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; -import java.util.jar.Manifest; -import org.apache.catalina.WebResourceRoot; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -37,18 +35,17 @@ public class JarWarResource extends Abst private final String archivePath; - public JarWarResource(WebResourceRoot root, String webAppPath, String base, - String baseUrl, JarEntry jarEntry, String archivePath, - String internalPath, Manifest manifest) { - super(root, webAppPath, base, "jar:war:" + baseUrl + "^/" + archivePath, - jarEntry, internalPath, manifest, "jar:" + baseUrl + "!/" + archivePath); + public JarWarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath, + String baseUrl, JarEntry jarEntry, String archivePath) { + super(archiveResourceSet, webAppPath, "jar:war:" + baseUrl + "^/" + archivePath, + jarEntry, "jar:" + baseUrl + "!/" + archivePath); this.archivePath = archivePath; } @Override protected JarInputStreamWrapper getJarInputStreamWrapper() { try { - JarFile warFile = new JarFile(getBase()); + JarFile warFile = getArchiveResourceSet().openJarFile(); JarEntry jarFileInWar = warFile.getJarEntry(archivePath); InputStream isInWar = warFile.getInputStream(jarFileInWar); @@ -73,7 +70,7 @@ public class JarWarResource extends Abst return null; } - return new JarInputStreamWrapper(warFile, entry, jarIs); + return new JarInputStreamWrapper(entry, jarIs); } catch (IOException e) { if (log.isDebugEnabled()) { log.debug(sm.getString("fileResource.getInputStreamFail", Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/JarWarResourceSet.java Tue Jan 27 19:42:22 2015 @@ -81,8 +81,7 @@ public class JarWarResourceSet extends A @Override protected WebResource createArchiveResource(JarEntry jarEntry, String webAppPath, Manifest manifest) { - return new JarWarResource(getRoot(), webAppPath, getBase(), getBaseUrlString(), - jarEntry, archivePath, getInternalPath(), manifest); + return new JarWarResource(this, webAppPath, getBaseUrlString(), jarEntry, archivePath); } //-------------------------------------------------------- Lifecycle methods Modified: tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/StandardRoot.java URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/StandardRoot.java?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/StandardRoot.java (original) +++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/webresources/StandardRoot.java Tue Jan 27 19:42:22 2015 @@ -589,9 +589,15 @@ public class StandardRoot extends Lifecy mainResources.add(main); } + @Override public void backgroundProcess() { cache.backgroundProcess(); + for (List<WebResourceSet> list : allResources) { + for (WebResourceSet webResourceSet : list) { + webResourceSet.backgroundProcess(); + } + } } Modified: tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml?rev=1655135&r1=1655134&r2=1655135&view=diff ============================================================================== --- tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml Tue Jan 27 19:42:22 2015 @@ -51,6 +51,10 @@ Clarify threaded usage of variables by removing volatile marker in NonceInfo. Issue reported by Coverity Scan. (fschumacher) </fix> + <fix> + <bug>57472</bug>: Fix performance regression in resources implementation + when signed JARs are used in a web application. (markt) + </fix> </changelog> </subsection> <subsection name="WebSocket"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org