changeset 21:c76d6a2b27f6

proper synchronization for working directories cleanup unused working folders
author Pavel.Sher
date Thu, 17 Jul 2008 00:44:10 +0400
parents 90f5e574fb73
children 0d6f27953b30
files mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java mercurial.ipr mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java
diffstat 5 files changed, 117 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Wed Jul 16 19:22:52 2008 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupportTest.java	Thu Jul 17 00:44:10 2008 +0400
@@ -1,7 +1,10 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.MockSupport;
 import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.serverSide.SBuildServer;
 import jetbrains.buildServer.serverSide.ServerPaths;
+import jetbrains.buildServer.util.SimpleExecutor;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import jetbrains.buildServer.vcs.patches.PatchBuilderImpl;
@@ -15,27 +18,37 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 
 @Test
 public class MercurialVcsSupportTest extends PatchTestCase {
   private MercurialVcsSupport myVcs;
   private TempFiles myTempFiles;
+  private MockSupport myMockSupport;
 
   @BeforeMethod
   protected void setUp() throws Exception {
     super.setUp();
+    myMockSupport = new MockSupport();
+    myMockSupport.setUpMocks();
+
     Mock vcsManagerMock = new Mock(VcsManager.class);
     vcsManagerMock.stubs().method("registerVcsSupport");
+    Mock serverMock = new Mock(SBuildServer.class);
+    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+    serverMock.stubs().method("getExecutor").will(myMockSupport.returnValue(executor));
 
     myTempFiles = new TempFiles();
     File systemDir = myTempFiles.createTempDir();
     ServerPaths sp = new ServerPaths(systemDir.getAbsolutePath(), systemDir.getAbsolutePath());
     assertTrue(new File(sp.getCachesDir()).mkdirs());
-    myVcs = new MercurialVcsSupport((VcsManager)vcsManagerMock.proxy(), sp);
+    myVcs = new MercurialVcsSupport((VcsManager)vcsManagerMock.proxy(), sp, (SBuildServer)serverMock.proxy());
   }
 
   @AfterMethod
-  protected void tearDown() {
+  protected void tearDown() throws Exception {
+    myMockSupport.tearDownMocks();
     myTempFiles.cleanup();
   }
 
--- a/mercurial.ipr	Wed Jul 16 19:22:52 2008 +0400
+++ b/mercurial.ipr	Thu Jul 17 00:44:10 2008 +0400
@@ -96,10 +96,6 @@
       <SplitterProportionsDataImpl />
     </option>
   </component>
-  <component name="ErrorTreeViewConfiguration">
-    <option name="IS_AUTOSCROLL_TO_SOURCE" value="false" />
-    <option name="HIDE_WARNINGS" value="false" />
-  </component>
   <component name="ExportToHTMLSettings">
     <option name="PRINT_LINE_NUMBERS" value="false" />
     <option name="OPEN_IN_BROWSER" value="false" />
--- a/mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Wed Jul 16 19:22:52 2008 +0400
+++ b/mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Thu Jul 17 00:44:10 2008 +0400
@@ -7,6 +7,7 @@
 import jetbrains.buildServer.log.Loggers;
 import jetbrains.buildServer.serverSide.InvalidProperty;
 import jetbrains.buildServer.serverSide.PropertiesProcessor;
+import jetbrains.buildServer.serverSide.SBuildServer;
 import jetbrains.buildServer.serverSide.ServerPaths;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.vcs.*;
@@ -19,6 +20,11 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * Mercurial VCS plugin for TeamCity works as follows:
@@ -34,10 +40,50 @@
  */
 public class MercurialVcsSupport extends VcsSupport implements CollectChangesByIncludeRule {
   private ServerPaths myServerPaths;
+  private ConcurrentMap<String, Lock> myWorkDirLocks= new ConcurrentHashMap<String, Lock>();
+  private static final int OLD_WORK_DIRS_CLEANUP_PERIOD = 600;
+  private VcsManager myVcsManager;
 
-  public MercurialVcsSupport(@NotNull VcsManager vcsManager, @NotNull ServerPaths paths) {
+  public MercurialVcsSupport(@NotNull final VcsManager vcsManager,
+                             @NotNull ServerPaths paths,
+                             @NotNull SBuildServer server) {
     vcsManager.registerVcsSupport(this);
     myServerPaths = paths;
+    myVcsManager = vcsManager;
+    server.getExecutor().scheduleAtFixedRate(new Runnable() {
+      public void run() {
+        removeOldWorkFolders();
+      }
+    }, 0, OLD_WORK_DIRS_CLEANUP_PERIOD, TimeUnit.SECONDS);
+
+  }
+
+  private void removeOldWorkFolders() {
+    File workFoldersParent = new File(myServerPaths.getCachesDir(), "mercurial");
+    if (!workFoldersParent.isDirectory()) return;
+
+    Set<File> dirNames = new HashSet<File>();
+    File[] files = workFoldersParent.listFiles(new FileFilter() {
+      public boolean accept(final File file) {
+        return file.isDirectory() && file.getName().startsWith(Settings.DEFAULT_WORK_DIR_PREFIX);
+      }
+    });
+    if (files != null) {
+      for (File f: files) {
+        dirNames.add(FileUtil.getCanonicalFile(f));
+      }
+    }
+
+    for (VcsRoot vcsRoot: myVcsManager.getAllRegisteredVcsRoots()) {
+      if (getName().equals(vcsRoot.getVcsName())) {
+        Settings s = new Settings(myServerPaths, vcsRoot);
+        dirNames.remove(FileUtil.getCanonicalFile(new File(s.getWorkingDir())));
+      }
+    }
+
+    for (File f: dirNames) {
+      FileUtil.delete(f);
+    }
   }
 
   public List<ModificationData> collectBuildChanges(final VcsRoot root,
@@ -87,7 +133,7 @@
   private List<VcsChange> toVcsChanges(final List<ModifiedFile> modifiedFiles, String prevVer, String curVer, final IncludeRule includeRule) {
     List<VcsChange> files = new ArrayList<VcsChange>();
     for (ModifiedFile mf: modifiedFiles) {
-      String normalizedPath = normalizePath(mf.getPath());
+      String normalizedPath = PathUtil.normalizeSeparator(mf.getPath());
       if (!normalizedPath.startsWith(includeRule.getFrom())) continue; // skip files which do not match include rule
 
       VcsChangeInfo.Type changeType = getChangeType(mf.getStatus());
@@ -100,10 +146,6 @@
     return files;
   }
 
-  private @NotNull String normalizePath(@NotNull String repPath) {
-    return repPath.replace('\\', '/');
-  }
-
   private VcsChangeInfo.Type getChangeType(final ModifiedFile.Status status) {
     switch (status) {
       case ADDED:return VcsChangeInfo.Type.ADDED;
@@ -337,7 +379,8 @@
   private void updateWorkingDirectory(final VcsRoot root) throws VcsException {
     Settings settings = new Settings(myServerPaths, root);
     String workDir = settings.getWorkingDir();
-    synchronized (root) {
+    lockWorkDir(workDir);
+    try {
       if (hasRepositoryCopy(new File(workDir))) {
         // update
         PullCommand pull = new PullCommand(settings);
@@ -349,9 +392,31 @@
         cl.setUpdateWorkingDir(false);
         cl.execute();
       }
+    } finally {
+      unlockWorkDir(workDir);
     }
   }
 
+  private void lockWorkDir(@NotNull String workDir) {
+    getWorkDirLock(workDir).lock();
+  }
+
+  private void unlockWorkDir(@NotNull String workDir) {
+    getWorkDirLock(workDir).unlock();
+  }
+
+  private Lock getWorkDirLock(final String workDir) {
+    Lock lock = myWorkDirLocks.get(workDir);
+    if (lock == null) {
+      lock = new ReentrantLock();
+      Lock curLock = myWorkDirLocks.putIfAbsent(workDir, lock);
+      if (curLock != null) {
+        lock = curLock;
+      }
+    }
+    return lock;
+  }
+
   private boolean hasRepositoryCopy(final File workDir) {
     // need better way to check that repository copy is ok
     return workDir.isDirectory() && new File(workDir, ".hg").isDirectory();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java	Thu Jul 17 00:44:10 2008 +0400
@@ -0,0 +1,10 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+public class PathUtil {
+  @NotNull
+  public static String normalizeSeparator(@NotNull String repPath) {
+    return repPath.replace('\\', '/');
+  }
+  }
--- a/mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java	Wed Jul 16 19:22:52 2008 +0400
+++ b/mercurial/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/Settings.java	Thu Jul 17 00:44:10 2008 +0400
@@ -1,8 +1,11 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import jetbrains.buildServer.buildTriggers.vcs.mercurial.Constants;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.PathUtil;
 import jetbrains.buildServer.serverSide.ServerPaths;
 import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.util.Hash;
+import jetbrains.buildServer.util.FileUtil;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
@@ -65,11 +68,25 @@
       return myWorkingDir;
     }
 
-    String workingDirname = String.valueOf(myRepository.hashCode());
-    File workFoldersRootDir = new File(myServerPaths.getCachesDir(), "mercurial");
+    return getDefaultWorkDir(myServerPaths, myRepository);
+  }
+
+  public static String DEFAULT_WORK_DIR_PREFIX = "hg_";
+
+  private static String getDefaultWorkDir(@NotNull ServerPaths serverPaths, @NotNull String repPath) {
+    String workingDirname = DEFAULT_WORK_DIR_PREFIX + String.valueOf(Hash.calc(normalize(repPath)));
+    File workFoldersRootDir = new File(serverPaths.getCachesDir(), "mercurial");
     if (!workFoldersRootDir.mkdirs() && !workFoldersRootDir.isDirectory()) {
       throw new RuntimeException("Cannot create directory: " + workFoldersRootDir.getAbsolutePath());
     }
-    return new File(workFoldersRootDir, workingDirname).getAbsolutePath();
+    return FileUtil.getCanonicalFile(new File(workFoldersRootDir, workingDirname)).getAbsolutePath();
+  }
+
+  private static String normalize(final String path) {
+    String normalized = PathUtil.normalizeSeparator(path);
+    if (path.endsWith("/")) {
+      return normalized.substring(0, normalized.length()-1);
+    }
+    return normalized;
   }
 }