]> source.dussan.org Git - gitblit.git/commitdiff
Cache repository models to improve performance (issue 103)
authorJames Moger <james.moger@gitblit.com>
Sat, 18 Aug 2012 19:36:11 +0000 (15:36 -0400)
committerJames Moger <james.moger@gitblit.com>
Sat, 18 Aug 2012 19:36:11 +0000 (15:36 -0400)
src/com/gitblit/GitBlit.java

index 0940c7682f75442f71c27d9841ecf536e32735a2..f8fbfefd14a107040ed55b82b30dcc1fb336c539 100644 (file)
@@ -39,7 +39,6 @@ import java.util.Set;
 import java.util.TimeZone;\r
 import java.util.TreeSet;\r
 import java.util.concurrent.ConcurrentHashMap;\r
-import java.util.concurrent.CopyOnWriteArrayList;\r
 import java.util.concurrent.Executors;\r
 import java.util.concurrent.ScheduledExecutorService;\r
 import java.util.concurrent.TimeUnit;\r
@@ -58,6 +57,7 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Repository;\r
 import org.eclipse.jgit.lib.RepositoryCache.FileKey;\r
 import org.eclipse.jgit.lib.StoredConfig;\r
+import org.eclipse.jgit.storage.file.FileBasedConfig;\r
 import org.eclipse.jgit.storage.file.WindowCache;\r
 import org.eclipse.jgit.storage.file.WindowCacheConfig;\r
 import org.eclipse.jgit.transport.ServiceMayNotContinueException;\r
@@ -88,6 +88,7 @@ import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;\r
 import com.gitblit.utils.ArrayUtils;\r
 import com.gitblit.utils.ByteFormat;\r
+import com.gitblit.utils.DeepCopier;\r
 import com.gitblit.utils.FederationUtils;\r
 import com.gitblit.utils.JGitUtils;\r
 import com.gitblit.utils.JsonUtils;\r
@@ -128,7 +129,7 @@ public class GitBlit implements ServletContextListener {
 \r
        private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();\r
        \r
-       private final List<String> repositoryListCache = new CopyOnWriteArrayList<String>();\r
+       private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>();\r
        \r
        private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");\r
 \r
@@ -736,25 +737,32 @@ public class GitBlit implements ServletContextListener {
         * \r
         * @param name\r
         */\r
-       private void addToCachedRepositoryList(String name) {\r
+       private void addToCachedRepositoryList(String name, RepositoryModel model) {\r
                if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {\r
-                       repositoryListCache.add(name);\r
+                       repositoryListCache.put(name, model);\r
                }\r
        }\r
+       \r
+       /**\r
+        * Removes the repository from the list of cached repositories.\r
+        * \r
+        * @param name\r
+        */\r
+       private void removeFromCachedRepositoryList(String name) {\r
+               if (StringUtils.isEmpty(name)) {\r
+                       return;\r
+               }\r
+               repositoryListCache.remove(name);\r
+       }\r
 \r
        /**\r
-        * Clears all the cached data for the specified repository.\r
+        * Clears all the cached metadata for the specified repository.\r
         * \r
         * @param repositoryName\r
-        * @param isDeleted\r
         */\r
-       private void clearRepositoryCache(String repositoryName, boolean isDeleted) {\r
+       private void clearRepositoryMetadataCache(String repositoryName) {\r
                repositorySizeCache.remove(repositoryName);\r
                repositoryMetricsCache.remove(repositoryName);\r
-               \r
-               if (isDeleted) {\r
-                       repositoryListCache.remove(repositoryName);\r
-               }\r
        }\r
        \r
        /**\r
@@ -831,18 +839,20 @@ public class GitBlit implements ServletContextListener {
                                                        calculateSize(model);\r
                                                }\r
                                        }\r
+                               } else {\r
+                                       // update cache\r
+                                       for (String repository : repositories) {\r
+                                               getRepositoryModel(repository);\r
+                                       }\r
                                }\r
                                \r
-                               // update cache\r
-                               repositoryListCache.addAll(repositories);\r
-                               \r
                                long duration = System.currentTimeMillis() - startTime;\r
                                logger.info(MessageFormat.format(msg, repositoryListCache.size(), duration));\r
                        }\r
                }\r
                \r
                // return sorted copy of cached list\r
-               List<String> list = new ArrayList<String>(repositoryListCache);         \r
+               List<String> list = new ArrayList<String>(repositoryListCache.keySet());                \r
                StringUtils.sortRepositorynames(list);\r
                return list;\r
        }\r
@@ -966,6 +976,79 @@ public class GitBlit implements ServletContextListener {
         * @return repository model or null\r
         */\r
        public RepositoryModel getRepositoryModel(String repositoryName) {\r
+               if (!repositoryListCache.containsKey(repositoryName)) {\r
+                       RepositoryModel model = loadRepositoryModel(repositoryName);\r
+                       if (model == null) {\r
+                               return null;\r
+                       }\r
+                       addToCachedRepositoryList(repositoryName, model);                       \r
+                       return model;\r
+               }\r
+               \r
+               // cached model\r
+               RepositoryModel model = repositoryListCache.get(repositoryName);\r
+               \r
+               // check for updates\r
+               Repository r = getRepository(repositoryName);\r
+               if (r == null) {\r
+                       // repository is missing\r
+                       removeFromCachedRepositoryList(repositoryName);\r
+                       logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName));\r
+                       return null;\r
+               }\r
+               \r
+               FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r);\r
+               if (config.isOutdated()) {\r
+                       // reload model\r
+                       logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));\r
+                       model = loadRepositoryModel(repositoryName);\r
+                       removeFromCachedRepositoryList(repositoryName);\r
+                       addToCachedRepositoryList(repositoryName, model);\r
+               } else {\r
+                       // update a few repository parameters \r
+                       if (!model.hasCommits) {\r
+                               // update hasCommits, assume a repository only gains commits :)\r
+                               model.hasCommits = JGitUtils.hasCommits(r);\r
+                       }\r
+\r
+                       model.lastChange = JGitUtils.getLastChange(r);\r
+               }\r
+               r.close();\r
+               \r
+               // return a copy of the cached model\r
+               return DeepCopier.copy(model);\r
+       }\r
+       \r
+       /**\r
+        * Workaround JGit.  I need to access the raw config object directly in order\r
+        * to see if the config is dirty so that I can reload a repository model.\r
+        * If I use the stock JGit method to get the config it already reloads the\r
+        * config.  If the config changes are made within Gitblit this is fine as\r
+        * the returned config will still be flagged as dirty.  BUT... if the config\r
+        * is manipulated outside Gitblit then it fails to recognize this as dirty.\r
+        *  \r
+        * @param r\r
+        * @return a config\r
+        */\r
+       private StoredConfig getRepositoryConfig(Repository r) {\r
+               try {\r
+                       Field f = r.getClass().getDeclaredField("repoConfig");\r
+                       f.setAccessible(true);\r
+                       StoredConfig config = (StoredConfig) f.get(r);\r
+                       return config;\r
+               } catch (Exception e) {\r
+                       logger.error("Failed to retrieve \"repoConfig\" via reflection", e);\r
+               }\r
+               return r.getConfig();\r
+       }\r
+       \r
+       /**\r
+        * Create a repository model from the configuration and repository data.\r
+        * \r
+        * @param repositoryName\r
+        * @return a repositoryModel or null if the repository does not exist\r
+        */\r
+       private RepositoryModel loadRepositoryModel(String repositoryName) {\r
                Repository r = getRepository(repositoryName);\r
                if (r == null) {\r
                        return null;\r
@@ -1025,6 +1108,11 @@ public class GitBlit implements ServletContextListener {
         * @return true if the repository exists\r
         */\r
        public boolean hasRepository(String repositoryName) {\r
+               if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {\r
+                       // if we are caching use the cache to determine availability\r
+                       // otherwise we end up adding a phantom repository to the cache\r
+                       return repositoryListCache.containsKey(repositoryName);\r
+               }               \r
                Repository r = getRepository(repositoryName, false);\r
                if (r == null) {\r
                        return false;\r
@@ -1169,9 +1257,6 @@ public class GitBlit implements ServletContextListener {
                        // create repository\r
                        logger.info("create repository " + repository.name);\r
                        r = JGitUtils.createRepository(repositoriesFolder, repository.name);\r
-                       \r
-                       // add name to cache\r
-                       addToCachedRepositoryList(repository.name);\r
                } else {\r
                        // rename repository\r
                        if (!repositoryName.equalsIgnoreCase(repository.name)) {\r
@@ -1211,10 +1296,7 @@ public class GitBlit implements ServletContextListener {
                                }\r
 \r
                                // clear the cache\r
-                               clearRepositoryCache(repositoryName, true);\r
-                               \r
-                               // add new name to repository list cache\r
-                               addToCachedRepositoryList(repository.name);\r
+                               clearRepositoryMetadataCache(repositoryName);\r
                        }\r
 \r
                        // load repository\r
@@ -1242,13 +1324,18 @@ public class GitBlit implements ServletContextListener {
                                                repository.name, currentRef, repository.HEAD));\r
                                if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {\r
                                        // clear the cache\r
-                                       clearRepositoryCache(repository.name, false);\r
+                                       clearRepositoryMetadataCache(repository.name);\r
                                }\r
                        }\r
 \r
                        // close the repository object\r
                        r.close();\r
                }\r
+               \r
+               // update repository cache\r
+               removeFromCachedRepositoryList(repositoryName);\r
+               // model will actually be replaced on next load because config is stale\r
+               addToCachedRepositoryList(repository.name, repository);\r
        }\r
        \r
        /**\r
@@ -1339,12 +1426,14 @@ public class GitBlit implements ServletContextListener {
                try {\r
                        closeRepository(repositoryName);\r
                        // clear the repository cache\r
-                       clearRepositoryCache(repositoryName, true);                     \r
+                       clearRepositoryMetadataCache(repositoryName);\r
+                       removeFromCachedRepositoryList(repositoryName);\r
 \r
                        File folder = new File(repositoriesFolder, repositoryName);\r
                        if (folder.exists() && folder.isDirectory()) {\r
                                FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);\r
                                if (userService.deleteRepositoryRole(repositoryName)) {\r
+                                       logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));\r
                                        return true;\r
                                }\r
                        }\r