Make manual retries also append parameters so that failed downloads are not cached

Review by: zundel@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10400 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/impl/LoadingStrategyBase.java b/user/src/com/google/gwt/core/client/impl/LoadingStrategyBase.java
index 2cf9495..130200c 100644
--- a/user/src/com/google/gwt/core/client/impl/LoadingStrategyBase.java
+++ b/user/src/com/google/gwt/core/client/impl/LoadingStrategyBase.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.core.client.impl;
 
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadTerminatedHandler;
 import com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy;
 import com.google.gwt.core.client.impl.AsyncFragmentLoader.HttpInstallFailure;
@@ -59,7 +60,27 @@
   interface DownloadStrategy {
     void tryDownload(final RequestData request);
   }
+  
+  /**
+   * A trivial JavaScript map from ints to ints.  Used to keep a global count of
+   * how many times a user has manually retried fetching a fragment.
+   */
+  private static final class FragmentReloadTracker extends JavaScriptObject {
+    public static FragmentReloadTracker create() {
+      return (FragmentReloadTracker) JavaScriptObject.createArray();
+    }
 
+    protected FragmentReloadTracker() { }
+    
+    public native int get(int x) /*-{
+      return this[x] ? this[x] : 0;
+    }-*/;
+    
+    public native void put(int x, int y) /*-{
+      this[x] = y;
+    }-*/;
+  }
+  
   /**    
    * Since LoadingStrategy must support concurrent requests, we keep most of the
    * relevant info in the RequestData, and pass it around.  Once created, a
@@ -97,7 +118,8 @@
       if (mayRetry) {
         retryCount++;
         if (retryCount <= maxRetryCount) {
-          url = originalUrl + "?serial=" + retryCount;
+          char connector = originalUrl.contains("?") ? '&' : '?';
+          url = originalUrl + connector + "autoRetry=" + retryCount;
           downloadStrategy.tryDownload(this);
           return;
         }
@@ -126,7 +148,7 @@
    * The number of times that we will retry a download. Note that if the install
    * fails, we do not retry, since there's no reason to expect a different result.
    */
-  public static int MAX_RETRY_COUNT = 3;
+  public static int MAX_AUTO_RETRY_COUNT = 3;
   
   /**
    * Call the linker-supplied <code>__gwtInstallCode</code> method. This method
@@ -153,7 +175,8 @@
   }-*/;
   
   private DownloadStrategy downloadStrategy;
-  
+  private final FragmentReloadTracker manualRetryNumbers = FragmentReloadTracker.create();
+
   /**
    * Subclasses should create a DownloadStrategy and pass it into this constructor.
    */
@@ -161,8 +184,6 @@
     this.downloadStrategy = downloadStrategy;
   }
 
-  protected int getMaxRetryCount() { return MAX_RETRY_COUNT; }
-
   @Override
   public void startLoadingFragment(int fragment,
       final LoadTerminatedHandler loadErrorHandler) {
@@ -171,8 +192,24 @@
       // The linker is going to handle this fetch - nothing more to do
       return;
     }
+    // Browsers will ignore too many script tags if it has previously failed
+    // to download that url, so we add a parameter to the url if
+    // this is not the first time we've tried to download this fragment.
+    int manualRetry = getManualRetryNum(fragment);
+    if (manualRetry > 0) {
+      char connector = url.contains("?") ? '&' : '?';
+      url += connector + "manualRetry=" + manualRetry; 
+    }
     RequestData request = new RequestData(url, loadErrorHandler, 
-        fragment, downloadStrategy, getMaxRetryCount());
+        fragment, downloadStrategy, getMaxAutoRetryCount());
     request.tryDownload();
   }
+
+  protected int getMaxAutoRetryCount() { return MAX_AUTO_RETRY_COUNT; }
+
+  private int getManualRetryNum(int fragment) {
+    int ser = manualRetryNumbers.get(fragment);
+    manualRetryNumbers.put(fragment, ser + 1);
+    return ser;
+  }
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java b/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
index 77267d9..65d6fae 100644
--- a/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
@@ -100,8 +100,6 @@
   // otherwise the runAsync code will cache the fragment and later tests will
   // always succeed on the first try.
   
-  // Fragment for this call (5) should be on downloadErrorFragments list in the
-  // RunAsyncFailureServlet
   private void runAsync1(final int attempt, final int expectedSuccessfulAttempt) {
     GWT.runAsync(new MyRunAsyncCallback(attempt, expectedSuccessfulAttempt) {
       public void onFailure(Throwable caught) {
@@ -119,8 +117,6 @@
     });
   }
   
-  // Fragment for this call (2) should be on downloadErrorFragments list in the
-  // RunAsyncFailureServlet
   private void runAsync2(final int attempt, final int expectedSuccessfulAttempt) {
     GWT.runAsync(new MyRunAsyncCallback(attempt, expectedSuccessfulAttempt) {
       public void onFailure(Throwable caught) {
@@ -138,8 +134,6 @@
     });
   }
   
-  // Fragment for this call (3) should be on downloadErrorFragments list in the
-  // RunAsyncFailureServlet
   private void runAsync3(final int attempt, final int expectedSuccessfulAttempt) {
     GWT.runAsync(new MyRunAsyncCallback(attempt, expectedSuccessfulAttempt) {
       public void onFailure(Throwable caught) {
@@ -157,8 +151,6 @@
     });
   }
   
-  // Fragment for this call (4) should be on installErrorFragments list in the
-  // RunAsyncFailureServlet
   private void runAsync4() {
     GWT.runAsync(new RunAsyncCallback() {
       public void onFailure(Throwable caught) {
@@ -181,7 +173,7 @@
   public void testHttpFailureRetries() {
     delayTestFinish(RUNASYNC_TIMEOUT);
     // Default is 3, but we set it explicitly, since other tests may run first
-    LoadingStrategyBase.MAX_RETRY_COUNT = 2;
+    LoadingStrategyBase.MAX_AUTO_RETRY_COUNT = 2;
     // In RunAsyncFailureServlet, the 5th time is the charm, but the code
     // by default retries 3 times every time we call runAsync, so this
     // should succeed on the second runAsync call, which is attempt #1.
@@ -190,7 +182,7 @@
   
   public void testHttpFailureRetries2() {
     delayTestFinish(RUNASYNC_TIMEOUT);
-    LoadingStrategyBase.MAX_RETRY_COUNT = 0;
+    LoadingStrategyBase.MAX_AUTO_RETRY_COUNT = 0;
     // In RunAsyncFailureServlet, the 5th time is the charm, so if we do not
     // let the code do any retries, we'll need to retry 4 times before succeeding
     runAsync2(0, 4);
@@ -198,7 +190,7 @@
   
   public void testBuiltInRetries() {
     delayTestFinish(RUNASYNC_TIMEOUT);
-    LoadingStrategyBase.MAX_RETRY_COUNT = 4;
+    LoadingStrategyBase.MAX_AUTO_RETRY_COUNT = 4;
     // In RunAsyncFailureServlet, the 5th time is the charm, so retrying 4 times
     // should be enough to get it on the first try.
     runAsync3(0, supportsRetries() ? 0 : 4);
@@ -206,7 +198,7 @@
   
   public void testDownloadSuccessButInstallFailure() {
     delayTestFinish(RUNASYNC_TIMEOUT);
-    LoadingStrategyBase.MAX_RETRY_COUNT = 3;
+    LoadingStrategyBase.MAX_AUTO_RETRY_COUNT = 3;
     runAsync4();
   }
 }
diff --git a/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureServlet.java b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureServlet.java
index ad281de..5ecb49e 100644
--- a/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureServlet.java
+++ b/user/test/com/google/gwt/user/server/runasync/RunAsyncFailureServlet.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.user.server.runasync;
 
+import com.google.gwt.dev.util.Util;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.InputStream;
@@ -32,16 +34,8 @@
 public class RunAsyncFailureServlet extends HttpServlet {
 
   private static final boolean DEBUG = false;
-  private static final HashMap<String, RealContents> realContentsCache = new HashMap<String, RealContents>();
+  private static final HashMap<String, String> realContentsCache = new HashMap<String, String>();
 
-  private static class RealContents {
-    public RealContents(int numBytes, byte[] bytes) {
-      this.numBytes = numBytes;
-      this.bytes = bytes;
-    }
-    public int numBytes;
-    public byte[] bytes;
-  }
   /**
    * Sequence of response codes to send back. SC_OK must be last.
    */
@@ -67,19 +61,16 @@
     String originalUri = req.getRequestURI();
     debug("doGet Original: " + originalUri);
     String uri = originalUri.replace("/runAsyncFailure", "");
-
     int response = getDesiredResponse(uri);
-    RealContents rc = this.getRealContents(req, uri);
-    String realContentsAsString = new String(rc.bytes);
-
+    String realContents = getRealContents(req, uri);
     String fragment = uri.substring(uri.lastIndexOf('/') + 1);
-      if (!realContentsAsString.contains("DOWNLOAD_FAILURE_TEST")
+      if (!realContents.contains("DOWNLOAD_FAILURE_TEST")
           || response == HttpServletResponse.SC_OK) {      
       int bytes = 0;
-      if (!realContentsAsString.contains("INSTALL_FAILURE_TEST")) {
+      if (!realContents.contains("INSTALL_FAILURE_TEST")) {
         OutputStream os = resp.getOutputStream();
-        os.write(rc.bytes, 0, rc.numBytes);
-        bytes = rc.numBytes;
+        os.write(realContents.getBytes());
+        bytes = realContents.getBytes().length;
         os.close();
       }
       
@@ -105,7 +96,7 @@
     return responses[tries % responses.length];
   }
 
-  private RealContents getRealContents(HttpServletRequest req, String uri) throws IOException {
+  private String getRealContents(HttpServletRequest req, String uri) throws IOException {
     if (realContentsCache.containsKey(uri)) {
       return realContentsCache.get(uri);
     }
@@ -115,21 +106,13 @@
     int port = req.getLocalPort();
     String realUrl = "http://" + host + ":" + port + uri;
     debug("Fetching: " + realUrl);
-    int bytes = 0;
-    byte[] data = new byte[32768];
-    try {
-      URL url = new URL(realUrl);
-      InputStream is = url.openStream();
 
-      bytes = is.read(data);
-      is.close();
-    } catch (IOException e) {
-      debug("IOException fetching real data: " + e);
-      throw e;
-    }
+    URL url = new URL(realUrl);
+    InputStream is = url.openStream();
+    String data = Util.readStreamAsString(is);
+    is.close();
 
-    RealContents rc = new RealContents(bytes, data);
-    realContentsCache.put(uri, rc);
-    return rc;
+    realContentsCache.put(uri, data);
+    return data;
   }
 }