You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GcConcurrentTest.java 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*
  2. * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.storage.file;
  11. import static java.lang.Integer.valueOf;
  12. import static org.junit.Assert.assertEquals;
  13. import static org.junit.Assert.assertNotEquals;
  14. import static org.junit.Assert.assertNotNull;
  15. import static org.junit.Assert.assertTrue;
  16. import static org.junit.Assert.fail;
  17. import java.io.IOException;
  18. import java.util.Collection;
  19. import java.util.Collections;
  20. import java.util.concurrent.BrokenBarrierException;
  21. import java.util.concurrent.Callable;
  22. import java.util.concurrent.CountDownLatch;
  23. import java.util.concurrent.CyclicBarrier;
  24. import java.util.concurrent.ExecutionException;
  25. import java.util.concurrent.ExecutorService;
  26. import java.util.concurrent.Executors;
  27. import java.util.concurrent.Future;
  28. import java.util.concurrent.TimeUnit;
  29. import org.eclipse.jgit.errors.CancelledException;
  30. import org.eclipse.jgit.internal.JGitText;
  31. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  32. import org.eclipse.jgit.junit.TestRepository;
  33. import org.eclipse.jgit.lib.ConfigConstants;
  34. import org.eclipse.jgit.lib.EmptyProgressMonitor;
  35. import org.eclipse.jgit.lib.NullProgressMonitor;
  36. import org.eclipse.jgit.lib.ObjectId;
  37. import org.eclipse.jgit.lib.Sets;
  38. import org.eclipse.jgit.revwalk.RevBlob;
  39. import org.eclipse.jgit.revwalk.RevCommit;
  40. import org.eclipse.jgit.storage.file.FileBasedConfig;
  41. import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
  42. import org.junit.Test;
  43. public class GcConcurrentTest extends GcTestCase {
  44. @Test
  45. public void concurrentRepack() throws Exception {
  46. final CyclicBarrier syncPoint = new CyclicBarrier(2);
  47. class DoRepack extends EmptyProgressMonitor implements
  48. Callable<Integer> {
  49. @Override
  50. public void beginTask(String title, int totalWork) {
  51. if (title.equals(JGitText.get().writingObjects)) {
  52. try {
  53. syncPoint.await();
  54. } catch (InterruptedException e) {
  55. Thread.currentThread().interrupt();
  56. } catch (BrokenBarrierException ignored) {
  57. //
  58. }
  59. }
  60. }
  61. /** @return 0 for success, 1 in case of error when writing pack */
  62. @Override
  63. public Integer call() throws Exception {
  64. try {
  65. gc.setProgressMonitor(this);
  66. gc.repack();
  67. return valueOf(0);
  68. } catch (IOException e) {
  69. // leave the syncPoint in broken state so any awaiting
  70. // threads and any threads that call await in the future get
  71. // the BrokenBarrierException
  72. Thread.currentThread().interrupt();
  73. try {
  74. syncPoint.await();
  75. } catch (InterruptedException ignored) {
  76. //
  77. }
  78. return valueOf(1);
  79. }
  80. }
  81. }
  82. RevBlob a = tr.blob("a");
  83. tr.lightweightTag("t", a);
  84. ExecutorService pool = Executors.newFixedThreadPool(2);
  85. try {
  86. DoRepack repack1 = new DoRepack();
  87. DoRepack repack2 = new DoRepack();
  88. Future<Integer> result1 = pool.submit(repack1);
  89. Future<Integer> result2 = pool.submit(repack2);
  90. assertEquals(0, result1.get().intValue() + result2.get().intValue());
  91. } finally {
  92. pool.shutdown();
  93. pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
  94. }
  95. }
  96. @Test
  97. public void repackAndGetStats() throws Exception {
  98. TestRepository<FileRepository>.BranchBuilder test = tr.branch("test");
  99. test.commit().add("a", "a").create();
  100. GC gc1 = new GC(tr.getRepository());
  101. gc1.setPackExpireAgeMillis(0);
  102. gc1.gc();
  103. test.commit().add("b", "b").create();
  104. // Create a new Repository instance and trigger a gc
  105. // from that instance. Reusing the existing repo instance
  106. // tr.getRepository() would not show the problem.
  107. FileRepository r2 = new FileRepository(
  108. tr.getRepository().getDirectory());
  109. GC gc2 = new GC(r2);
  110. gc2.setPackExpireAgeMillis(0);
  111. gc2.gc();
  112. new GC(tr.getRepository()).getStatistics();
  113. }
  114. @Test
  115. public void repackAndUploadPack() throws Exception {
  116. TestRepository<FileRepository>.BranchBuilder test = tr.branch("test");
  117. // RevCommit a = test.commit().add("a", "a").create();
  118. test.commit().add("a", "a").create();
  119. GC gc1 = new GC(tr.getRepository());
  120. gc1.setPackExpireAgeMillis(0);
  121. gc1.gc();
  122. RevCommit b = test.commit().add("b", "b").create();
  123. FileRepository r2 = new FileRepository(
  124. tr.getRepository().getDirectory());
  125. GC gc2 = new GC(r2);
  126. gc2.setPackExpireAgeMillis(0);
  127. gc2.gc();
  128. // Simulate parts of an UploadPack. This is the situation on
  129. // server side (e.g. gerrit) when clients are
  130. // cloning/fetching while the server side repo's
  131. // are gc'ed by an external process (e.g. scheduled
  132. // native git gc)
  133. try (PackWriter pw = new PackWriter(tr.getRepository())) {
  134. pw.setUseBitmaps(true);
  135. pw.preparePack(NullProgressMonitor.INSTANCE, Sets.of(b),
  136. Collections.<ObjectId> emptySet());
  137. new GC(tr.getRepository()).getStatistics();
  138. }
  139. }
  140. Pack getSinglePack(FileRepository r) {
  141. Collection<Pack> packs = r.getObjectDatabase().getPacks();
  142. assertEquals(1, packs.size());
  143. return packs.iterator().next();
  144. }
  145. @Test
  146. public void repackAndCheckBitmapUsage() throws Exception {
  147. // create a test repository with one commit and pack all objects. After
  148. // packing create loose objects to trigger creation of a new packfile on
  149. // the next gc
  150. TestRepository<FileRepository>.BranchBuilder test = tr.branch("test");
  151. test.commit().add("a", "a").create();
  152. FileRepository repository = tr.getRepository();
  153. GC gc1 = new GC(repository);
  154. gc1.setPackExpireAgeMillis(0);
  155. gc1.gc();
  156. String oldPackName = getSinglePack(repository).getPackName();
  157. RevCommit b = test.commit().add("b", "b").create();
  158. // start the garbage collection on a new repository instance,
  159. FileRepository repository2 = new FileRepository(repository.getDirectory());
  160. GC gc2 = new GC(repository2);
  161. gc2.setPackExpireAgeMillis(0);
  162. gc2.gc();
  163. String newPackName = getSinglePack(repository2).getPackName();
  164. // make sure gc() has caused creation of a new packfile
  165. assertNotEquals(oldPackName, newPackName);
  166. // Even when asking again for the set of packfiles outdated data
  167. // will be returned. As long as the repository can work on cached data
  168. // it will do so and not detect that a new packfile exists.
  169. assertNotEquals(getSinglePack(repository).getPackName(), newPackName);
  170. // Only when accessing object content it is required to rescan the pack
  171. // directory and the new packfile will be detected.
  172. repository.getObjectDatabase().open(b).getSize();
  173. assertEquals(getSinglePack(repository).getPackName(), newPackName);
  174. assertNotNull(getSinglePack(repository).getBitmapIndex());
  175. }
  176. @Test
  177. public void testInterruptGc() throws Exception {
  178. FileBasedConfig c = repo.getConfig();
  179. c.setInt(ConfigConstants.CONFIG_GC_SECTION, null,
  180. ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, 1);
  181. c.save();
  182. SampleDataRepositoryTestCase.copyCGitTestPacks(repo);
  183. ExecutorService executor = Executors.newSingleThreadExecutor();
  184. final CountDownLatch latch = new CountDownLatch(1);
  185. Future<Collection<Pack>> result = executor.submit(() -> {
  186. long start = System.currentTimeMillis();
  187. System.out.println("starting gc");
  188. latch.countDown();
  189. Collection<Pack> r = gc.gc();
  190. System.out.println(
  191. "gc took " + (System.currentTimeMillis() - start) + " ms");
  192. return r;
  193. });
  194. try {
  195. latch.await();
  196. Thread.sleep(5);
  197. executor.shutdownNow();
  198. result.get();
  199. fail("thread wasn't interrupted");
  200. } catch (ExecutionException e) {
  201. Throwable cause = e.getCause();
  202. if (cause instanceof CancelledException) {
  203. assertEquals(JGitText.get().operationCanceled,
  204. cause.getMessage());
  205. } else if (cause instanceof IOException) {
  206. Throwable cause2 = cause.getCause();
  207. assertTrue(cause2 instanceof InterruptedException
  208. || cause2 instanceof ExecutionException);
  209. } else {
  210. fail("unexpected exception " + e);
  211. }
  212. }
  213. }
  214. }