View Javadoc
1   package org.eclipse.jgit.internal.storage.dfs;
2   
3   import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
4   import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
5   import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
6   import static org.junit.Assert.assertEquals;
7   import static org.junit.Assert.assertFalse;
8   import static org.junit.Assert.assertNotEquals;
9   import static org.junit.Assert.assertNotNull;
10  import static org.junit.Assert.assertTrue;
11  import static org.junit.Assert.fail;
12  
13  import java.io.IOException;
14  import java.util.concurrent.TimeUnit;
15  
16  import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
17  import org.eclipse.jgit.junit.TestRepository;
18  import org.eclipse.jgit.lib.AnyObjectId;
19  import org.eclipse.jgit.lib.Ref;
20  import org.eclipse.jgit.lib.Repository;
21  import org.eclipse.jgit.revwalk.RevCommit;
22  import org.eclipse.jgit.revwalk.RevWalk;
23  import org.junit.Before;
24  import org.junit.Test;
25  
26  public class DfsGarbageCollectorTest {
27  	private TestRepository<InMemoryRepository> git;
28  	private InMemoryRepository repo;
29  	private DfsObjDatabase odb;
30  
31  	@Before
32  	public void setUp() throws IOException {
33  		DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
34  		git = new TestRepository<>(new InMemoryRepository(desc));
35  		repo = git.getRepository();
36  		odb = repo.getObjectDatabase();
37  	}
38  
39  	@Test
40  	public void testCollectionWithNoGarbage() throws Exception {
41  		RevCommit commit0 = commit().message("0").create();
42  		RevCommit commit1 = commit().message("1").parent(commit0).create();
43  		git.update("master", commit1);
44  
45  		assertTrue("commit0 reachable", isReachable(repo, commit0));
46  		assertTrue("commit1 reachable", isReachable(repo, commit1));
47  
48  		// Packs start out as INSERT.
49  		assertEquals(2, odb.getPacks().length);
50  		for (DfsPackFile pack : odb.getPacks()) {
51  			assertEquals(INSERT, pack.getPackDescription().getPackSource());
52  		}
53  
54  		gcNoTtl();
55  
56  		// Single GC pack present with all objects.
57  		assertEquals(1, odb.getPacks().length);
58  		DfsPackFile pack = odb.getPacks()[0];
59  		assertEquals(GC, pack.getPackDescription().getPackSource());
60  		assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
61  		assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
62  	}
63  
64  	@Test
65  	public void testCollectionWithGarbage() throws Exception {
66  		RevCommit commit0 = commit().message("0").create();
67  		RevCommit commit1 = commit().message("1").parent(commit0).create();
68  		git.update("master", commit0);
69  
70  		assertTrue("commit0 reachable", isReachable(repo, commit0));
71  		assertFalse("commit1 garbage", isReachable(repo, commit1));
72  		gcNoTtl();
73  
74  		assertEquals(2, odb.getPacks().length);
75  		DfsPackFile gc = null;
76  		DfsPackFile garbage = null;
77  		for (DfsPackFile pack : odb.getPacks()) {
78  			DfsPackDescription d = pack.getPackDescription();
79  			if (d.getPackSource() == GC) {
80  				gc = pack;
81  			} else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
82  				garbage = pack;
83  			} else {
84  				fail("unexpected " + d.getPackSource());
85  			}
86  		}
87  
88  		assertNotNull("created GC pack", gc);
89  		assertTrue(isObjectInPack(commit0, gc));
90  
91  		assertNotNull("created UNREACHABLE_GARBAGE pack", garbage);
92  		assertTrue(isObjectInPack(commit1, garbage));
93  	}
94  
95  	@Test
96  	public void testCollectionWithGarbageAndGarbagePacksPurged()
97  			throws Exception {
98  		RevCommit commit0 = commit().message("0").create();
99  		RevCommit commit1 = commit().message("1").parent(commit0).create();
100 		git.update("master", commit0);
101 
102 		gcNoTtl();
103 		gcWithTtl();
104 
105 		// The repository has an UNREACHABLE_GARBAGE pack that could have
106 		// expired, but since we never purge the most recent UNREACHABLE_GARBAGE
107 		// pack, it must have survived the GC.
108 		boolean commit1Found = false;
109 		for (DfsPackFile pack : odb.getPacks()) {
110 			DfsPackDescription d = pack.getPackDescription();
111 			if (d.getPackSource() == GC) {
112 				assertTrue("has commit0", isObjectInPack(commit0, pack));
113 				assertFalse("no commit1", isObjectInPack(commit1, pack));
114 			} else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
115 				commit1Found |= isObjectInPack(commit1, pack);
116 			} else {
117 				fail("unexpected " + d.getPackSource());
118 			}
119 		}
120 		assertTrue("garbage commit1 still readable", commit1Found);
121 
122 		// Find oldest UNREACHABLE_GARBAGE; it will be pruned by next GC.
123 		DfsPackDescription oldestGarbagePack = null;
124 		for (DfsPackFile pack : odb.getPacks()) {
125 			DfsPackDescription d = pack.getPackDescription();
126 			if (d.getPackSource() == UNREACHABLE_GARBAGE) {
127 				oldestGarbagePack = oldestPack(oldestGarbagePack, d);
128 			}
129 		}
130 		assertNotNull("has UNREACHABLE_GARBAGE", oldestGarbagePack);
131 
132 		gcWithTtl();
133 		assertTrue("has packs", odb.getPacks().length > 0);
134 		for (DfsPackFile pack : odb.getPacks()) {
135 			assertNotEquals(oldestGarbagePack, pack.getPackDescription());
136 		}
137 	}
138 
139 	@Test
140 	public void testCollectionWithGarbageCoalescence() throws Exception {
141 		RevCommit commit0 = commit().message("0").create();
142 		RevCommit commit1 = commit().message("1").parent(commit0).create();
143 		git.update("master", commit0);
144 
145 		for (int i = 0; i < 3; i++) {
146 			commit1 = commit().message("g" + i).parent(commit1).create();
147 
148 			// Make sure we don't have more than 1 UNREACHABLE_GARBAGE pack
149 			// because they're coalesced.
150 			gcNoTtl();
151 			assertEquals(1, countPacks(UNREACHABLE_GARBAGE));
152 		}
153 	}
154 
155 	@Test
156 	public void testCollectionWithGarbageNoCoalescence() throws Exception {
157 		RevCommit commit0 = commit().message("0").create();
158 		RevCommit commit1 = commit().message("1").parent(commit0).create();
159 		git.update("master", commit0);
160 
161 		for (int i = 0; i < 3; i++) {
162 			commit1 = commit().message("g" + i).parent(commit1).create();
163 
164 			DfsGarbageCollector gc = new DfsGarbageCollector(repo);
165 			gc.setCoalesceGarbageLimit(0);
166 			gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
167 			run(gc);
168 			assertEquals(1 + i, countPacks(UNREACHABLE_GARBAGE));
169 		}
170 	}
171 
172 	private TestRepository<InMemoryRepository>.CommitBuilder commit() {
173 		return git.commit();
174 	}
175 
176 	private void gcNoTtl() throws IOException {
177 		DfsGarbageCollector gc = new DfsGarbageCollector(repo);
178 		gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
179 		run(gc);
180 	}
181 
182 	private void gcWithTtl() throws InterruptedException, IOException {
183 		// Wait for the system clock to move by at least 1 millisecond.
184 		// This allows the DfsGarbageCollector to recognize the boundary.
185 		long start = System.currentTimeMillis();
186 		do {
187 			Thread.sleep(10);
188 		} while (System.currentTimeMillis() <= start);
189 
190 		DfsGarbageCollector gc = new DfsGarbageCollector(repo);
191 		gc.setGarbageTtl(1, TimeUnit.MILLISECONDS);
192 		run(gc);
193 	}
194 
195 	private void run(DfsGarbageCollector gc) throws IOException {
196 		assertTrue("gc repacked", gc.pack(null));
197 		odb.clearCache();
198 	}
199 
200 	private static boolean isReachable(Repository repo, AnyObjectId id)
201 			throws IOException {
202 		try (RevWalk rw = new RevWalk(repo)) {
203 			for (Ref ref : repo.getAllRefs().values()) {
204 				rw.markStart(rw.parseCommit(ref.getObjectId()));
205 			}
206 			for (RevCommit next; (next = rw.next()) != null;) {
207 				if (AnyObjectId.equals(next, id)) {
208 					return true;
209 				}
210 			}
211 		}
212 		return false;
213 	}
214 
215 	private boolean isObjectInPack(AnyObjectId id, DfsPackFile pack)
216 			throws IOException {
217 		try (DfsReader reader = new DfsReader(odb)) {
218 			return pack.hasObject(reader, id);
219 		}
220 	}
221 
222 	private static DfsPackDescription oldestPack(DfsPackDescription a,
223 			DfsPackDescription b) {
224 		if (a != null && a.getLastModified() < b.getLastModified()) {
225 			return a;
226 		}
227 		return b;
228 	}
229 
230 	private int countPacks(PackSource source) throws IOException {
231 		int cnt = 0;
232 		for (DfsPackFile pack : odb.getPacks()) {
233 			if (pack.getPackDescription().getPackSource() == source) {
234 				cnt++;
235 			}
236 		}
237 		return cnt;
238 	}
239 }