Author: pmouawad
Date: Sun Oct 23 17:38:38 2011
New Revision: 1187939
URL: http://svn.apache.org/viewvc?rev=1187939&view=rev
Log:
Bug 51919 - Random ConcurrentModificationException or NoSuchElementException in
CookieManager#removeMatchingCookies when using Concurrent Download
Fix proposition:
- Clone Sampler
- Clone CookieManager for each AsyncSampler
- Implement custom thread factory to add thread end notification that will call
threadFinished on cloned sampler
we can revert if you don't agree
Modified:
jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java
jakarta/jmeter/trunk/xdocs/changes.xml
Modified:
jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java
URL:
http://svn.apache.org/viewvc/jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java?rev=1187939&r1=1187938&r2=1187939&view=diff
==============================================================================
---
jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java
(original)
+++
jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java
Sun Oct 23 17:38:38 2011
@@ -36,6 +36,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -45,6 +46,7 @@ import org.apache.jmeter.config.Argument
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.protocol.http.control.AuthManager;
import org.apache.jmeter.protocol.http.control.CacheManager;
+import org.apache.jmeter.protocol.http.control.Cookie;
import org.apache.jmeter.protocol.http.control.CookieManager;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.protocol.http.parser.HTMLParseException;
@@ -62,6 +64,7 @@ import org.apache.jmeter.testelement.Tes
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.testelement.ThreadListener;
import org.apache.jmeter.testelement.property.BooleanProperty;
+import org.apache.jmeter.testelement.property.CollectionProperty;
import org.apache.jmeter.testelement.property.IntegerProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.PropertyIterator;
@@ -1132,8 +1135,8 @@ public abstract class HTTPSamplerBase ex
}
// For concurrent get resources
- final List<Callable<HTTPSampleResult>> liste = new
ArrayList<Callable<HTTPSampleResult>>();
-
+ final List<Callable<AsynSamplerResultHolder>> liste = new
ArrayList<Callable<AsynSamplerResultHolder>>();
+
while (urls.hasNext()) {
Object binURL = urls.next(); // See catch clause below
try {
@@ -1159,7 +1162,7 @@ public abstract class HTTPSamplerBase ex
if (isConcurrentDwn()) {
// if concurrent download emb. resources, add to a
list for async gets later
- liste.add(new ASyncSample(url, GET, false,
frameDepth + 1, this));
+ liste.add(new ASyncSample(url, GET, false,
frameDepth + 1, getCookieManager(), this));
} else {
// default: serial download embedded resources
HTTPSampleResult binRes = sample(url, GET, false,
frameDepth + 1);
@@ -1174,7 +1177,6 @@ public abstract class HTTPSamplerBase ex
continue;
}
}
-
// IF for download concurrent embedded resources
if (isConcurrentDwn()) {
int poolSize = CONCURRENT_POOL_SIZE; // init with default value
@@ -1188,24 +1190,46 @@ public abstract class HTTPSamplerBase ex
// use a LinkedBlockingQueue, note: max pool size doesn't
effect
final ThreadPoolExecutor exec = new ThreadPoolExecutor(
poolSize, poolSize, KEEPALIVETIME, TimeUnit.SECONDS,
- new LinkedBlockingQueue<Runnable>());
+ new LinkedBlockingQueue<Runnable>(),
+ new ThreadFactory() {
+ public Thread newThread(final Runnable r) {
+ Thread t = new CleanerThread(new Runnable() {
+ public void run() {
+ try {
+ r.run();
+ } finally {
+
((CleanerThread)Thread.currentThread()).notifyThreadEnd();
+ }
+ }
+ });
+ return t;
+ }
+ });
boolean tasksCompleted = false;
try {
// sample all resources with threadpool
- final List<Future<HTTPSampleResult>> retExec =
exec.invokeAll(liste);
+ final List<Future<AsynSamplerResultHolder>> retExec =
exec.invokeAll(liste);
// call normal shutdown (wait ending all tasks)
exec.shutdown();
// put a timeout if tasks couldn't terminate
exec.awaitTermination(AWAIT_TERMINATION_TIMEOUT,
TimeUnit.SECONDS);
-
+ CookieManager cookieManager = getCookieManager();
// add result to main sampleResult
- for (Future<HTTPSampleResult> future : retExec) {
- HTTPSampleResult binRes;
+ for (Future<AsynSamplerResultHolder> future : retExec) {
+ AsynSamplerResultHolder binRes;
try {
binRes = future.get(1, TimeUnit.MILLISECONDS);
- res.addSubResult(binRes);
- res.setSuccessful(res.isSuccessful() &&
binRes.isSuccessful());
+ if(cookieManager != null) {
+ CollectionProperty cookies =
binRes.getCookies();
+ PropertyIterator iter = cookies.iterator();
+ while (iter.hasNext()) {
+ Cookie cookie = (Cookie)
iter.next().getObjectValue();
+ cookieManager.add(cookie) ;
+ }
+ }
+ res.addSubResult(binRes.getResult());
+ res.setSuccessful(res.isSuccessful() &&
binRes.getResult().isSuccessful());
} catch (TimeoutException e) {
errorResult(e, res);
}
@@ -1224,7 +1248,7 @@ public abstract class HTTPSamplerBase ex
}
return res;
}
-
+
/*
* @param res HTTPSampleResult to check
* @return parser class name (may be "") or null if entry does not exist
@@ -1692,28 +1716,102 @@ public abstract class HTTPSamplerBase ex
setProperty(CONCURRENT_POOL, poolSize, CONCURRENT_POOL_DEFAULT);
}
+
/**
* Callable class to sample asynchronously resources embedded
*
*/
- private static class ASyncSample implements Callable<HTTPSampleResult> {
+ private static class ASyncSample implements
Callable<AsynSamplerResultHolder> {
final private URL url;
final private String method;
final private boolean areFollowingRedirect;
final private int depth;
- private final HTTPSamplerBase base;
+ private final HTTPSamplerBase sampler;
ASyncSample(URL url, String method,
- boolean areFollowingRedirect, int depth, HTTPSamplerBase
base){
+ boolean areFollowingRedirect, int depth, CookieManager
cookieManager, HTTPSamplerBase base){
this.url = url;
this.method = method;
this.areFollowingRedirect = areFollowingRedirect;
this.depth = depth;
- this.base = base;
+ this.sampler = (HTTPSamplerBase) base.clone();
+
+ if(cookieManager != null) {
+ CookieManager clonedCookieManager = (CookieManager)
cookieManager.clone();
+ this.sampler.setCookieManager(clonedCookieManager);
+ }
}
- public HTTPSampleResult call() {
- return base.sample(url, method, areFollowingRedirect, depth);
+ public AsynSamplerResultHolder call() {
+ ((CleanerThread)
Thread.currentThread()).registerSamplerForEndNotification(sampler);
+ HTTPSampleResult httpSampleResult = sampler.sample(url, method,
areFollowingRedirect, depth);
+ if(sampler.getCookieManager() != null) {
+ CollectionProperty cookies =
sampler.getCookieManager().getCookies();
+ return new AsynSamplerResultHolder(httpSampleResult, cookies);
+ } else {
+ return new AsynSamplerResultHolder(httpSampleResult, new
CollectionProperty());
+ }
+ }
+ }
+
+ /**
+ * Custom thread implementation that
+ *
+ */
+ private static class CleanerThread extends Thread {
+ private List<HTTPSamplerBase> samplersToNotify = new
ArrayList<HTTPSamplerBase>();
+ /**
+ * @param runnable Runnable
+ */
+ public CleanerThread(Runnable runnable) {
+ super(runnable);
+ }
+
+ /**
+ * Notify of thread end
+ */
+ public void notifyThreadEnd() {
+ for (HTTPSamplerBase samplerBase : samplersToNotify) {
+ samplerBase.threadFinished();
+ }
+ samplersToNotify.clear();
+ }
+
+ /**
+ * Register sampler to be notify at end of thread
+ * @param sampler {@link HTTPSamplerBase}
+ */
+ public void registerSamplerForEndNotification(HTTPSamplerBase sampler)
{
+ this.samplersToNotify.add(sampler);
+ }
+ }
+
+ /**
+ * Holder of AsynSampler result
+ */
+ private static class AsynSamplerResultHolder {
+ private HTTPSampleResult result;
+ private CollectionProperty cookies;
+ /**
+ * @param result
+ * @param cookies
+ */
+ public AsynSamplerResultHolder(HTTPSampleResult result,
CollectionProperty cookies) {
+ super();
+ this.result = result;
+ this.cookies = cookies;
+ }
+ /**
+ * @return the result
+ */
+ public HTTPSampleResult getResult() {
+ return result;
+ }
+ /**
+ * @return the cookies
+ */
+ public CollectionProperty getCookies() {
+ return cookies;
}
}
Modified: jakarta/jmeter/trunk/xdocs/changes.xml
URL:
http://svn.apache.org/viewvc/jakarta/jmeter/trunk/xdocs/changes.xml?rev=1187939&r1=1187938&r2=1187939&view=diff
==============================================================================
--- jakarta/jmeter/trunk/xdocs/changes.xml (original)
+++ jakarta/jmeter/trunk/xdocs/changes.xml Sun Oct 23 17:38:38 2011
@@ -91,6 +91,7 @@ Mirror server now uses default port 8081
<li>Bug 51925 - Calling Stop on Test leaks executor threads when concurrent
download of resources is on</li>
<li>Bug 51980 - HtmlParserHTMLParser double-counts images used in links</li>
<li>Bug 52064 - OutOfMemory Risk in CacheManager</li>
+<li>Bug 51919 - Random ConcurrentModificationException or
NoSuchElementException in CookieManager#removeMatchingCookies when using
Concurrent Download</li>
</ul>
<h3>Other Samplers</h3>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]