Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

GarbageCollectorService.java 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * Copyright 2012 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.service;
  17. import java.util.Calendar;
  18. import java.util.Date;
  19. import java.util.Map;
  20. import java.util.Properties;
  21. import java.util.concurrent.ConcurrentHashMap;
  22. import java.util.concurrent.atomic.AtomicBoolean;
  23. import org.eclipse.jgit.api.GarbageCollectCommand;
  24. import org.eclipse.jgit.api.Git;
  25. import org.eclipse.jgit.lib.Repository;
  26. import org.slf4j.Logger;
  27. import org.slf4j.LoggerFactory;
  28. import com.gitblit.IStoredSettings;
  29. import com.gitblit.Keys;
  30. import com.gitblit.manager.IRepositoryManager;
  31. import com.gitblit.models.RepositoryModel;
  32. import com.gitblit.utils.FileUtils;
  33. /**
  34. * The Garbage Collector Service handles periodic garbage collection in repositories.
  35. *
  36. * @author James Moger
  37. *
  38. */
  39. public class GarbageCollectorService implements Runnable {
  40. public static enum GCStatus {
  41. READY, COLLECTING;
  42. public boolean exceeds(GCStatus s) {
  43. return ordinal() > s.ordinal();
  44. }
  45. }
  46. private final Logger logger = LoggerFactory.getLogger(GarbageCollectorService.class);
  47. private final IStoredSettings settings;
  48. private final IRepositoryManager repositoryManager;
  49. private AtomicBoolean running = new AtomicBoolean(false);
  50. private AtomicBoolean forceClose = new AtomicBoolean(false);
  51. private final Map<String, GCStatus> gcCache = new ConcurrentHashMap<String, GCStatus>();
  52. public GarbageCollectorService(
  53. IStoredSettings settings,
  54. IRepositoryManager repositoryManager) {
  55. this.settings = settings;
  56. this.repositoryManager = repositoryManager;
  57. }
  58. /**
  59. * Indicates if the GC executor is ready to process repositories.
  60. *
  61. * @return true if the GC executor is ready to process repositories
  62. */
  63. public boolean isReady() {
  64. return settings.getBoolean(Keys.git.enableGarbageCollection, false);
  65. }
  66. public boolean isRunning() {
  67. return running.get();
  68. }
  69. public boolean lock(String repositoryName) {
  70. return setGCStatus(repositoryName, GCStatus.COLLECTING);
  71. }
  72. /**
  73. * Tries to set a GCStatus for the specified repository.
  74. *
  75. * @param repositoryName
  76. * @return true if the status has been set
  77. */
  78. private boolean setGCStatus(String repositoryName, GCStatus status) {
  79. String key = repositoryName.toLowerCase();
  80. if (gcCache.containsKey(key)) {
  81. if (gcCache.get(key).exceeds(GCStatus.READY)) {
  82. // already collecting or blocked
  83. return false;
  84. }
  85. }
  86. gcCache.put(key, status);
  87. return true;
  88. }
  89. /**
  90. * Returns true if Gitblit is actively collecting garbage in this repository.
  91. *
  92. * @param repositoryName
  93. * @return true if actively collecting garbage
  94. */
  95. public boolean isCollectingGarbage(String repositoryName) {
  96. String key = repositoryName.toLowerCase();
  97. return gcCache.containsKey(key) && GCStatus.COLLECTING.equals(gcCache.get(key));
  98. }
  99. /**
  100. * Resets the GC status to ready.
  101. *
  102. * @param repositoryName
  103. */
  104. public void releaseLock(String repositoryName) {
  105. gcCache.put(repositoryName.toLowerCase(), GCStatus.READY);
  106. }
  107. public void close() {
  108. forceClose.set(true);
  109. }
  110. @Override
  111. public void run() {
  112. if (!isReady()) {
  113. return;
  114. }
  115. running.set(true);
  116. Date now = new Date();
  117. for (String repositoryName : repositoryManager.getRepositoryList()) {
  118. if (forceClose.get()) {
  119. break;
  120. }
  121. if (isCollectingGarbage(repositoryName)) {
  122. logger.warn("Already collecting garbage from {}?!?", repositoryName);
  123. continue;
  124. }
  125. boolean garbageCollected = false;
  126. RepositoryModel model = null;
  127. Repository repository = null;
  128. try {
  129. model = repositoryManager.getRepositoryModel(repositoryName);
  130. repository = repositoryManager.getRepository(repositoryName);
  131. if (repository == null) {
  132. logger.warn("GCExecutor is missing repository {}?!?", repositoryName);
  133. continue;
  134. }
  135. if (!repositoryManager.isIdle(repository)) {
  136. logger.debug("GCExecutor is skipping {} because it is not idle", repositoryName);
  137. continue;
  138. }
  139. // By setting the GCStatus to COLLECTING we are
  140. // disabling *all* access to this repository from Gitblit.
  141. // Think of this as a clutch in a manual transmission vehicle.
  142. if (!setGCStatus(repositoryName, GCStatus.COLLECTING)) {
  143. logger.warn("Can not acquire GC lock for {}, skipping", repositoryName);
  144. continue;
  145. }
  146. logger.debug("GCExecutor locked idle repository {}", repositoryName);
  147. Git git = new Git(repository);
  148. GarbageCollectCommand gc = git.gc();
  149. Properties stats = gc.getStatistics();
  150. // determine if this is a scheduled GC
  151. Calendar cal = Calendar.getInstance();
  152. cal.setTime(model.lastGC);
  153. cal.set(Calendar.HOUR_OF_DAY, 0);
  154. cal.set(Calendar.MINUTE, 0);
  155. cal.set(Calendar.SECOND, 0);
  156. cal.set(Calendar.MILLISECOND, 0);
  157. cal.add(Calendar.DATE, model.gcPeriod);
  158. Date gcDate = cal.getTime();
  159. boolean shouldCollectGarbage = now.after(gcDate);
  160. // determine if filesize triggered GC
  161. long gcThreshold = FileUtils.convertSizeToLong(model.gcThreshold, 500*1024L);
  162. long sizeOfLooseObjects = (Long) stats.get("sizeOfLooseObjects");
  163. boolean hasEnoughGarbage = sizeOfLooseObjects >= gcThreshold;
  164. // if we satisfy one of the requirements, GC
  165. boolean hasGarbage = sizeOfLooseObjects > 0;
  166. if (hasGarbage && (hasEnoughGarbage || shouldCollectGarbage)) {
  167. long looseKB = sizeOfLooseObjects/1024L;
  168. logger.info("Collecting {} KB of loose objects from {}", looseKB, repositoryName );
  169. // do the deed
  170. gc.call();
  171. garbageCollected = true;
  172. }
  173. } catch (Exception e) {
  174. logger.error("Error collecting garbage in {}", repositoryName, e);
  175. } finally {
  176. // cleanup
  177. if (repository != null) {
  178. if (garbageCollected) {
  179. // update the last GC date
  180. model.lastGC = new Date();
  181. repositoryManager.updateConfiguration(repository, model);
  182. }
  183. repository.close();
  184. }
  185. // reset the GC lock
  186. releaseLock(repositoryName);
  187. logger.debug("GCExecutor released GC lock for {}", repositoryName);
  188. }
  189. }
  190. running.set(false);
  191. }
  192. }