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]

Reply via email to