Skip to content

Commit 55cf993

Browse files
Marco Vermeulenlhotari
authored andcommitted
GRAILS-6622 - Introduced ConcurrentLinkedHashMap to solve memory leak in DefaultUrlMappingsHolder.
1 parent 6c997be commit 55cf993

File tree

10 files changed

+63
-13
lines changed

10 files changed

+63
-13
lines changed

build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ bundlor.commons.fileupload.version=1.2.1
2929
bundlor.commons.io.version=1.4.0
3030
bundlor.commons.collections.version=3.2.1
3131
bundlor.commons.validator.version=1.3.1
32+
bundlor.concurrentlinkedhashmap.version=1.0
3233

3334
grails.src=src/java
3435
grails.test=src/test

dependencies.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ The following libraries are included in Grails because they are required either
1717
- CGLIB 2.1_3 with ObjectWeb ASM 1.5.3 (http://cglib.sourceforge.net) Apache 1.1 License
1818
- required for running Grails applications (Spring AOP & Hibernate)
1919

20+
* clhm-release-1.0-lru.jar
21+
- ConcurrentLinkedHashMap policy: least-recently-used, jdk: 6 (https://code.google.com/p/concurrentlinkedhashmap) Apache License 2.0
22+
- required for building Grails core
23+
- required for running Grails applications
24+
2025
* commons-beanutils-1.8.0.jar
2126
- Commons BeanUtils 1.8.0 (http://commons.apache.org/beanutils/) Apache 2.0 License
2227
- required for running Grails applications

ivy.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<dependency org="antlr" name="antlr" rev="2.7.6"/>
3535
<dependency org="jaxen" name="jaxen" rev="1.1-beta-11"/>
3636
<dependency org="bsf" name="bsf" rev="2.3.0"/>
37-
<dependency org="hibernate" name="hibernate" rev="3.2"/>
37+
<dependency org="hibernate" name="hibernate" rev="3.2"/>
38+
<dependency org="com.googlecode.concurrentlinkedhashmap" name="concurrentlinkedhashmap-lru" rev="1.0"/>
3839
</dependencies>
3940
</ivy-module>

lib/clhm-release-1.0-lru.jar

34.8 KB
Binary file not shown.

maven/grails-web.mf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Import-Template:
3838
org.w3c.dom.*;version="0",
3939
org.xml.sax.*;version="0",
4040
org.xmlpull.v1.*;version="${xpp.version:[=.=.=, +1.0.0)}",
41-
org.springframework.*;version="${spring.version:[=.=.=, +1.0.0)}"
41+
org.springframework.*;version="${spring.version:[=.=.=, +1.0.0)}",
42+
com.googlecode.concurrentlinkedhashmap.*;version="${concurrentlinkedhashmap.version:[=.=.=, +1.0)}"
43+
4244

4345

maven/grails-web.pom.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@
108108

109109
<!-- Runtime Dependencies -->
110110

111+
<!-- ConcurrentLinkedHashmap used by DefaultUrlMappingsHolder -->
112+
<dependency>
113+
<groupId>com.googlecode.concurrentlinkedhashmap</groupId>
114+
<artifactId>concurrentlinkedhashmap-lru</artifactId>
115+
<version>1.0</version>
116+
</dependency>
111117

112118

113119
<!-- Test Dependencies -->

src/grails/grails-app/conf/Config.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
2626
form: 'application/x-www-form-urlencoded',
2727
multipartForm: 'multipart/form-data'
2828
]
29+
30+
// URL Mapping Cache Max Size, defaults to 5000
31+
//grails.urlmapping.cache.maxsize = 1000
32+
2933
// The default codec used to encode data with ${}
3034
grails.views.default.codec = "none" // none, html, base64
3135
grails.views.gsp.encoding = "UTF-8"

src/java/org/codehaus/groovy/grails/resolve/IvyDependencyManager.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ class IvyDependencyManager extends AbstractIvyDependencyManager implements Depen
237237
"org.grails:grails-web:$grailsVersion",
238238
"org.slf4j:slf4j-api:1.5.8",
239239
"org.slf4j:slf4j-log4j12:1.5.8",
240-
"org.springframework:org.springframework.test:3.0.3.RELEASE"
240+
"org.springframework:org.springframework.test:3.0.3.RELEASE",
241+
"com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.0"
241242

242243
docs "org.xhtmlrenderer:core-renderer:R8",
243244
"com.lowagie:itext:2.0.8",
@@ -257,6 +258,7 @@ class IvyDependencyManager extends AbstractIvyDependencyManager implements Depen
257258
}
258259

259260
"${compileTimeDependenciesMethod}"("aopalliance:aopalliance:1.0",
261+
"com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.0",
260262
"commons-codec:commons-codec:1.3",
261263
"commons-collections:commons-collections:3.2.1",
262264
"commons-io:commons-io:1.4",

src/java/org/codehaus/groovy/grails/web/mapping/DefaultUrlMappingsHolder.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,18 @@
2626
import java.util.Set;
2727
import java.util.SortedSet;
2828
import java.util.TreeSet;
29-
import java.util.concurrent.ConcurrentHashMap;
3029

3130
import org.apache.commons.logging.Log;
3231
import org.apache.commons.logging.LogFactory;
3332
import org.codehaus.groovy.grails.commons.GrailsControllerClass;
3433
import org.codehaus.groovy.grails.validation.ConstrainedProperty;
3534
import org.springframework.core.style.ToStringCreator;
3635

36+
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
37+
3738
/**
3839
* Default implementation of the UrlMappingsHolder interface that takes a list of mappings and
39-
* then sorts them according to their precdence rules as defined in the implementation of Comparable.
40+
* then sorts them according to their precedence rules as defined in the implementation of Comparable.
4041
*
4142
* @see org.codehaus.groovy.grails.web.mapping.UrlMapping
4243
* @see Comparable
@@ -48,9 +49,11 @@
4849
public class DefaultUrlMappingsHolder implements UrlMappingsHolder {
4950

5051
private static final transient Log LOG = LogFactory.getLog(DefaultUrlMappingsHolder.class);
52+
private static final int DEFAULT_MAX_WEIGHTED_CAPACITY = 5000;
5153

52-
private Map<String, UrlMappingInfo> cachedMatches = new ConcurrentHashMap<String, UrlMappingInfo>();
53-
private Map<String, List<UrlMappingInfo>> cachedListMatches = new ConcurrentHashMap<String, List<UrlMappingInfo>>();
54+
private int maxWeightedCacheCapacity;
55+
private Map<String, UrlMappingInfo> cachedMatches;
56+
private Map<String, List<UrlMappingInfo>> cachedListMatches;
5457

5558
private List<UrlMapping> urlMappings = new ArrayList<UrlMapping>();
5659
private UrlMapping[] mappings;
@@ -68,18 +71,34 @@ public class DefaultUrlMappingsHolder implements UrlMappingsHolder {
6871

6972
public DefaultUrlMappingsHolder(List<UrlMapping> mappings) {
7073
urlMappings = mappings;
74+
this.maxWeightedCacheCapacity = DEFAULT_MAX_WEIGHTED_CAPACITY;
7175
initialize();
7276
}
7377

7478
public DefaultUrlMappingsHolder(List<UrlMapping> mappings, List excludePatterns) {
7579
urlMappings = mappings;
7680
this.excludePatterns = excludePatterns;
81+
this.maxWeightedCacheCapacity = DEFAULT_MAX_WEIGHTED_CAPACITY;
7782
initialize();
7883
}
79-
84+
85+
public DefaultUrlMappingsHolder(List<UrlMapping> mappings, List excludePatterns, int maxWeightedUrlCacheSize){
86+
urlMappings = mappings;
87+
this.excludePatterns = excludePatterns;
88+
this.maxWeightedCacheCapacity = maxWeightedUrlCacheSize;
89+
initialize();
90+
}
91+
8092
private void initialize() {
8193
sortMappings();
8294

95+
cachedMatches = new ConcurrentLinkedHashMap.Builder<String, UrlMappingInfo>()
96+
.maximumWeightedCapacity(maxWeightedCacheCapacity)
97+
.build();
98+
cachedListMatches = new ConcurrentLinkedHashMap.Builder<String, List<UrlMappingInfo>>()
99+
.maximumWeightedCapacity(maxWeightedCacheCapacity)
100+
.build();
101+
83102
mappings = urlMappings.toArray(new UrlMapping[urlMappings.size()]);
84103

85104
for (UrlMapping mapping : mappings) {
@@ -346,12 +365,13 @@ public String toString() {
346365
/**
347366
* A class used as a key to lookup a UrlMapping based on controller, action and parameter names
348367
*/
368+
@SuppressWarnings("unchecked")
349369
class UrlMappingKey implements Comparable {
350370
String controller;
351371
String action;
352-
Set paramNames = Collections.EMPTY_SET;
372+
Set<String> paramNames = Collections.EMPTY_SET;
353373

354-
public UrlMappingKey(String controller, String action, Set paramNames) {
374+
public UrlMappingKey(String controller, String action, Set<String> paramNames) {
355375
this.controller = controller;
356376
this.action = action;
357377
this.paramNames = paramNames;

src/java/org/codehaus/groovy/grails/web/mapping/UrlMappingsHolderFactoryBean.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.Properties;
2122

2223
import javax.servlet.ServletContext;
2324

@@ -39,7 +40,7 @@
3940
* @since 0.5
4041
*/
4142
public class UrlMappingsHolderFactoryBean implements FactoryBean<UrlMappingsHolder>, InitializingBean, GrailsApplicationAware, ServletContextAware {
42-
43+
private static final String URL_MAPPING_CACHE_MAX_SIZE = "grails.urlmapping.cache.maxsize";
4344
private GrailsApplication grailsApplication;
4445
private UrlMappingsHolder urlMappingsHolder;
4546
private UrlMappingEvaluator mappingEvaluator;
@@ -83,8 +84,16 @@ public void afterPropertiesSet() throws Exception {
8384
excludePatterns.addAll(mappingClass.getExcludePatterns());
8485
}
8586
}
86-
87-
urlMappingsHolder = new DefaultUrlMappingsHolder(urlMappings, excludePatterns);
87+
88+
Properties props = grailsApplication.getConfig().toProperties();
89+
String maxUrlCacheSize = props.getProperty(URL_MAPPING_CACHE_MAX_SIZE);
90+
if(maxUrlCacheSize == null){
91+
urlMappingsHolder = new DefaultUrlMappingsHolder(urlMappings, excludePatterns);
92+
93+
} else {
94+
int cacheSize = Integer.valueOf(maxUrlCacheSize);
95+
urlMappingsHolder = new DefaultUrlMappingsHolder(urlMappings, excludePatterns, cacheSize);
96+
}
8897
}
8998

9099
public void setGrailsApplication(GrailsApplication grailsApplication) {

0 commit comments

Comments
 (0)