com.gitblit.service.GarbageCollectorService.java Source code

Java tutorial

Introduction

Here is the source code for com.gitblit.service.GarbageCollectorService.java

Source

/*
 * Copyright 2012 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.service;

import java.text.MessageFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.jgit.api.GarbageCollectCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.FileUtils;

/**
 * The Garbage Collector Service handles periodic garbage collection in repositories.
 *
 * @author James Moger
 *
 */
public class GarbageCollectorService implements Runnable {

    public static enum GCStatus {
        READY, COLLECTING;

        public boolean exceeds(GCStatus s) {
            return ordinal() > s.ordinal();
        }
    }

    private final Logger logger = LoggerFactory.getLogger(GarbageCollectorService.class);

    private final IStoredSettings settings;

    private final IRepositoryManager repositoryManager;

    private AtomicBoolean running = new AtomicBoolean(false);

    private AtomicBoolean forceClose = new AtomicBoolean(false);

    private final Map<String, GCStatus> gcCache = new ConcurrentHashMap<String, GCStatus>();

    public GarbageCollectorService(IStoredSettings settings, IRepositoryManager repositoryManager) {

        this.settings = settings;
        this.repositoryManager = repositoryManager;
    }

    /**
     * Indicates if the GC executor is ready to process repositories.
     *
     * @return true if the GC executor is ready to process repositories
     */
    public boolean isReady() {
        return settings.getBoolean(Keys.git.enableGarbageCollection, false);
    }

    public boolean isRunning() {
        return running.get();
    }

    public boolean lock(String repositoryName) {
        return setGCStatus(repositoryName, GCStatus.COLLECTING);
    }

    /**
     * Tries to set a GCStatus for the specified repository.
     *
     * @param repositoryName
     * @return true if the status has been set
     */
    private boolean setGCStatus(String repositoryName, GCStatus status) {
        String key = repositoryName.toLowerCase();
        if (gcCache.containsKey(key)) {
            if (gcCache.get(key).exceeds(GCStatus.READY)) {
                // already collecting or blocked
                return false;
            }
        }
        gcCache.put(key, status);
        return true;
    }

    /**
     * Returns true if Gitblit is actively collecting garbage in this repository.
     *
     * @param repositoryName
     * @return true if actively collecting garbage
     */
    public boolean isCollectingGarbage(String repositoryName) {
        String key = repositoryName.toLowerCase();
        return gcCache.containsKey(key) && GCStatus.COLLECTING.equals(gcCache.get(key));
    }

    /**
     * Resets the GC status to ready.
     *
     * @param repositoryName
     */
    public void releaseLock(String repositoryName) {
        gcCache.put(repositoryName.toLowerCase(), GCStatus.READY);
    }

    public void close() {
        forceClose.set(true);
    }

    @Override
    public void run() {
        if (!isReady()) {
            return;
        }

        running.set(true);
        Date now = new Date();

        for (String repositoryName : repositoryManager.getRepositoryList()) {
            if (forceClose.get()) {
                break;
            }
            if (isCollectingGarbage(repositoryName)) {
                logger.warn(MessageFormat.format("Already collecting garbage from {0}?!?", repositoryName));
                continue;
            }
            boolean garbageCollected = false;
            RepositoryModel model = null;
            Repository repository = null;
            try {
                model = repositoryManager.getRepositoryModel(repositoryName);
                repository = repositoryManager.getRepository(repositoryName);
                if (repository == null) {
                    logger.warn(MessageFormat.format("GCExecutor is missing repository {0}?!?", repositoryName));
                    continue;
                }

                if (!repositoryManager.isIdle(repository)) {
                    logger.debug(MessageFormat.format("GCExecutor is skipping {0} because it is not idle",
                            repositoryName));
                    continue;
                }

                // By setting the GCStatus to COLLECTING we are
                // disabling *all* access to this repository from Gitblit.
                // Think of this as a clutch in a manual transmission vehicle.
                if (!setGCStatus(repositoryName, GCStatus.COLLECTING)) {
                    logger.warn(MessageFormat.format("Can not acquire GC lock for {0}, skipping", repositoryName));
                    continue;
                }

                logger.debug(MessageFormat.format("GCExecutor locked idle repository {0}", repositoryName));

                Git git = new Git(repository);
                GarbageCollectCommand gc = git.gc();
                Properties stats = gc.getStatistics();

                // determine if this is a scheduled GC
                Calendar cal = Calendar.getInstance();
                cal.setTime(model.lastGC);
                cal.set(Calendar.HOUR_OF_DAY, 0);
                cal.set(Calendar.MINUTE, 0);
                cal.set(Calendar.SECOND, 0);
                cal.set(Calendar.MILLISECOND, 0);
                cal.add(Calendar.DATE, model.gcPeriod);
                Date gcDate = cal.getTime();
                boolean shouldCollectGarbage = now.after(gcDate);

                // determine if filesize triggered GC
                long gcThreshold = FileUtils.convertSizeToLong(model.gcThreshold, 500 * 1024L);
                long sizeOfLooseObjects = (Long) stats.get("sizeOfLooseObjects");
                boolean hasEnoughGarbage = sizeOfLooseObjects >= gcThreshold;

                // if we satisfy one of the requirements, GC
                boolean hasGarbage = sizeOfLooseObjects > 0;
                if (hasGarbage && (hasEnoughGarbage || shouldCollectGarbage)) {
                    long looseKB = sizeOfLooseObjects / 1024L;
                    logger.info(MessageFormat.format("Collecting {1} KB of loose objects from {0}", repositoryName,
                            looseKB));

                    // do the deed
                    gc.call();

                    garbageCollected = true;
                }
            } catch (Exception e) {
                logger.error("Error collecting garbage in " + repositoryName, e);
            } finally {
                // cleanup
                if (repository != null) {
                    if (garbageCollected) {
                        // update the last GC date
                        model.lastGC = new Date();
                        repositoryManager.updateConfiguration(repository, model);
                    }

                    repository.close();
                }

                // reset the GC lock
                releaseLock(repositoryName);
                logger.debug(MessageFormat.format("GCExecutor released GC lock for {0}", repositoryName));
            }
        }

        running.set(false);
    }
}