Fixes workDir directory contention between multiple concurrent hosted mode sessions.

- each StandardLinkerContext gets a unique directory in the workDir
- each session gets its own linker stack
- outputing files into war directory is serialized

Review by: jat

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6919 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/DevMode.java b/dev/core/src/com/google/gwt/dev/DevMode.java
index 5dd351b..665f924 100644
--- a/dev/core/src/com/google/gwt/dev/DevMode.java
+++ b/dev/core/src/com/google/gwt/dev/DevMode.java
@@ -41,8 +41,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.net.BindException;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * The main executable class for the hosted mode shell. NOTE: the public API for
@@ -204,10 +202,6 @@
       return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
     }
 
-    public File getShellPublicGenDir(ModuleDef moduleDef) {
-      return new File(getShellBaseWorkDir(moduleDef), "public");
-    }
-
     public File getWarDir() {
       return warDir;
     }
@@ -259,17 +253,6 @@
   protected final HostedModeOptionsImpl options = (HostedModeOptionsImpl) super.options;
 
   /**
-   * Maps each active linker stack by module.
-   */
-  private final Map<String, StandardLinkerContext> linkerStacks = new HashMap<String, StandardLinkerContext>();
-
-  /**
-   * The set of specified modules by name; the keys represent the renamed name
-   * of each module rather than the canonical name.
-   */
-  private Map<String, ModuleDef> modulesByName = new HashMap<String, ModuleDef>();
-
-  /**
    * The server that was started.
    */
   private ServletContainer server;
@@ -302,11 +285,13 @@
   }
 
   @Override
-  protected ArtifactAcceptor doCreateArtifactAcceptor(final ModuleDef module) {
+  protected ArtifactAcceptor doCreateArtifactAcceptor(TreeLogger logger,
+      final ModuleDef module) throws UnableToCompleteException {
+    final StandardLinkerContext linkerContext = link(logger, module);
     return new ArtifactAcceptor() {
-      public void accept(TreeLogger logger, ArtifactSet newlyGeneratedArtifacts)
+      public void accept(TreeLogger relinkLogger, ArtifactSet newArtifacts)
           throws UnableToCompleteException {
-        relink(logger, module, newlyGeneratedArtifacts);
+        relink(relinkLogger, linkerContext, module, newArtifacts);
       }
     };
   }
@@ -355,6 +340,7 @@
       TreeLogger moduleBranch = branch.branch(TreeLogger.INFO, moduleName);
       try {
         ModuleDef module = loadModule(moduleBranch, moduleName, false);
+        Util.recursiveDelete(options.getShellBaseWorkDir(module), false);
         validateServletTags(moduleBranch, servletValidator, module, webXml);
         TreeLogger loadLogger = moduleBranch.branch(TreeLogger.DEBUG,
             "Bootstrap link for command-line module '" + moduleName + "'");
@@ -404,22 +390,6 @@
     return options.getServletContainerLauncher().getName();
   }
 
-  /*
-   * Overridden to keep our map up to date.
-   */
-  @Override
-  protected ModuleDef loadModule(TreeLogger logger, String moduleName,
-      boolean refresh) throws UnableToCompleteException {
-    ModuleDef module = super.loadModule(logger, moduleName, refresh);
-    modulesByName.put(module.getName(), module);
-    return module;
-  }
-
-  protected void restartServer(TreeLogger logger)
-      throws UnableToCompleteException {
-    server.refresh();
-  }
-
   /**
    * Perform an initial hosted mode link, without overwriting newer or
    * unmodified files in the output folder.
@@ -428,7 +398,7 @@
    * @param module the module to link
    * @throws UnableToCompleteException
    */
-  private void link(TreeLogger logger, ModuleDef module)
+  private StandardLinkerContext link(TreeLogger logger, ModuleDef module)
       throws UnableToCompleteException {
     TreeLogger linkLogger = logger.branch(TreeLogger.DEBUG, "Linking module '"
         + module.getName() + "'");
@@ -436,13 +406,12 @@
     // Create a new active linker stack for the fresh link.
     StandardLinkerContext linkerStack = new StandardLinkerContext(linkLogger,
         module, options);
-    linkerStacks.put(module.getName(), linkerStack);
-
     ArtifactSet artifacts = linkerStack.invokeLink(linkLogger);
     produceOutput(linkLogger, linkerStack, artifacts, module);
+    return linkerStack;
   }
 
-  private void produceOutput(TreeLogger logger,
+  private synchronized void produceOutput(TreeLogger logger,
       StandardLinkerContext linkerStack, ArtifactSet artifacts, ModuleDef module)
       throws UnableToCompleteException {
     TreeLogger linkLogger = logger.branch(TreeLogger.DEBUG, "Linking module '"
@@ -477,18 +446,15 @@
    * @param newlyGeneratedArtifacts the set of new artifacts
    * @throws UnableToCompleteException
    */
-  private void relink(TreeLogger logger, ModuleDef module,
-      ArtifactSet newlyGeneratedArtifacts) throws UnableToCompleteException {
+  private void relink(TreeLogger logger, StandardLinkerContext linkerContext,
+      ModuleDef module, ArtifactSet newlyGeneratedArtifacts)
+      throws UnableToCompleteException {
     TreeLogger linkLogger = logger.branch(TreeLogger.DEBUG,
         "Relinking module '" + module.getName() + "'");
 
-    // Find the existing linker stack.
-    StandardLinkerContext linkerStack = linkerStacks.get(module.getName());
-    assert linkerStack != null;
-
-    ArtifactSet artifacts = linkerStack.invokeRelink(linkLogger,
+    ArtifactSet artifacts = linkerContext.invokeRelink(linkLogger,
         newlyGeneratedArtifacts);
-    produceOutput(linkLogger, linkerStack, artifacts, module);
+    produceOutput(linkLogger, linkerContext, artifacts, module);
   }
 
   private void validateServletTags(TreeLogger logger,
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index efd998a..971ef98 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -60,6 +60,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * The main executable class for the hosted mode shell. This class must not have
@@ -400,10 +401,10 @@
     private File logDir;
     private int port;
     private int portHosted;
-    private final List<String> startupURLs = new ArrayList<String>();
     private String remoteUIClientId;
     private String remoteUIHost;
     private int remoteUIHostPort;
+    private final List<String> startupURLs = new ArrayList<String>();
 
     public void addStartupURL(String url) {
       startupURLs.add(url);
@@ -575,6 +576,8 @@
 
   private static final Random RNG = new Random();
 
+  private static final AtomicLong uniqueId = new AtomicLong();
+
   public static String normalizeURL(String unknownUrlText, int port, String host) {
     if (unknownUrlText.indexOf(":") != -1) {
       // Assume it's a full url.
@@ -722,7 +725,7 @@
     try {
       // Eager AWT init for OS X to ensure safe coexistence with SWT.
       BootStrapPlatform.initGui();
-        
+
       if (startUp()) {
         // The web server is running now, so launch browsers for startup urls.
         launchStartupUrls(getTopLogger());
@@ -753,7 +756,8 @@
 
   protected abstract HostedModeBaseOptions createOptions();
 
-  protected abstract ArtifactAcceptor doCreateArtifactAcceptor(ModuleDef module);
+  protected abstract ArtifactAcceptor doCreateArtifactAcceptor(
+      TreeLogger logger, ModuleDef module) throws UnableToCompleteException;
 
   /**
    * Creates an instance of ShellModuleSpaceHost (or a derived class) using the
@@ -766,12 +770,16 @@
    * @return ShellModuleSpaceHost instance
    */
   protected final ShellModuleSpaceHost doCreateShellModuleSpaceHost(
-      TreeLogger logger, CompilationState compilationState, ModuleDef moduleDef) {
+      TreeLogger logger, CompilationState compilationState, ModuleDef moduleDef)
+      throws UnableToCompleteException {
     // Clear out the shell temp directory.
-    Util.recursiveDelete(options.getShellBaseWorkDir(moduleDef), true);
+    File shellBaseWorkDir = options.getShellBaseWorkDir(moduleDef);
+    File sessionWorkDir = new File(shellBaseWorkDir,
+        String.valueOf(uniqueId.getAndIncrement()));
+    Util.recursiveDelete(sessionWorkDir, true);
     return new ShellModuleSpaceHost(logger, compilationState, moduleDef,
-        options.getGenDir(), new File(options.getShellBaseWorkDir(moduleDef),
-            "gen"), doCreateArtifactAcceptor(moduleDef));
+        options.getGenDir(), new File(sessionWorkDir, "gen"),
+        doCreateArtifactAcceptor(logger, moduleDef));
   }
 
   protected abstract void doShutDownServer();
@@ -847,8 +855,8 @@
 
   protected void launchURL(String url) throws UnableToCompleteException {
     /*
-     * TODO(jat): properly support launching arbitrary browsers -- need some
-     * UI API tweaks to support that.
+     * TODO(jat): properly support launching arbitrary browsers -- need some UI
+     * API tweaks to support that.
      */
     URL parsedUrl = null;
     try {
@@ -883,7 +891,7 @@
           public String getAnchorText() {
             return "Launch default browser";
           }
-          
+
           @Override
           public String getPrefix() {
             return "";
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index 9df74f6..3bf1332 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -192,7 +192,8 @@
   }
 
   @Override
-  protected ArtifactAcceptor doCreateArtifactAcceptor(final ModuleDef module) {
+  protected ArtifactAcceptor doCreateArtifactAcceptor(TreeLogger logger,
+      final ModuleDef module) {
     return new ArtifactAcceptor() {
       public void accept(TreeLogger logger, ArtifactSet artifacts)
           throws UnableToCompleteException {