James Moger
2011-07-20 dd6f08950e36694d9757adc84ed6805620d0c032
Fixed rename and delete repository (issue-10)

This was due to use of the FileResolver which caches repository objects.
Calling close() on a cached repository instance would decrement a
counter, but the repository would never really close and release all its
resources because the FileResolver "held" the first count/reference so
the object and refs databases were never closed.

The solution was to use reflection to determine the actual "useCnt" of
the repository and then call close() that number of times. In practice,
the "useCnt" is probably always 2, and that is the default value in case
reflection fails.

1 files modified
41 ■■■■■ changed files
src/com/gitblit/GitBlit.java 41 ●●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java
@@ -17,6 +17,7 @@
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
@@ -24,6 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@@ -141,7 +143,7 @@
    public static char getChar(String key, char defaultValue) {
        return self().settings.getChar(key, defaultValue);
    }
    /**
     * Returns the string value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a string, the
@@ -478,6 +480,41 @@
    }
    /**
     * Ensure that a cached repository is completely closed and its resources
     * are properly released.
     *
     * @param repositoryName
     */
    private void closeRepository(String repositoryName) {
        Repository repository = getRepository(repositoryName);
        // assume 2 uses in case reflection fails
        int uses = 2;
        try {
            // The FileResolver caches repositories which is very useful
            // for performance until you want to delete a repository.
            // I have to use reflection to call close() the correct
            // number of times to ensure that the object and ref databases
            // are properly closed before I can delete the repository from
            // the filesystem.
            Field useCnt = Repository.class.getDeclaredField("useCnt");
            useCnt.setAccessible(true);
            uses = ((AtomicInteger) useCnt.get(repository)).get();
        } catch (Exception e) {
            logger.warn(MessageFormat
                    .format("Failed to reflectively determine use count for repository {0}",
                            repositoryName), e);
        }
        if (uses > 0) {
            logger.info(MessageFormat
                    .format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
                            repositoryName, uses, uses));
            for (int i = 0; i < uses; i++) {
                repository.close();
            }
        }
    }
    /**
     * Returns the gitblit string vlaue for the specified key. If key is not
     * set, returns defaultValue.
     * 
@@ -540,6 +577,7 @@
        } else {
            // rename repository
            if (!repositoryName.equalsIgnoreCase(repository.name)) {
                closeRepository(repositoryName);
                File folder = new File(repositoriesFolder, repositoryName);
                File destFolder = new File(repositoriesFolder, repository.name);
                if (destFolder.exists()) {
@@ -615,6 +653,7 @@
     */
    public boolean deleteRepository(String repositoryName) {
        try {
            closeRepository(repositoryName);
            File folder = new File(repositoriesFolder, repositoryName);
            if (folder.exists() && folder.isDirectory()) {
                FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);