]> source.dussan.org Git - jgit.git/commitdiff
Fix memory leak in dfs.DeltaBaseCase 69/47569/2
authorShawn Pearce <spearce@spearce.org>
Sun, 10 May 2015 01:11:20 +0000 (18:11 -0700)
committerShawn Pearce <spearce@spearce.org>
Mon, 11 May 2015 18:39:28 +0000 (11:39 -0700)
The LRU chain management code was broken leading to situations where
the chain was incomplete.  This prevented the cache from removing
items when it exceeded its memory target, causing a leak.

One case was repeated hit on the head of the chain. moveToHead(e)
was invoked linking the head back to itself in a cycle orphaning
the rest of the table.

Add some unit tests to cover this and a few other paths.

Change-Id: Ib27486eaa1b1d2bf1c745a56d0a5832bfb029322

org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java

diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java
new file mode 100644 (file)
index 0000000..5bef9fa
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.eclipse.jgit.internal.storage.dfs.DeltaBaseCache.Entry;
+import org.eclipse.jgit.junit.TestRng;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DeltaBaseCacheTest {
+       private static final int SZ = 512;
+
+       private DfsPackKey key;
+       private DeltaBaseCache cache;
+       private TestRng rng;
+
+       @Before
+       public void setUp() {
+               key = new DfsPackKey();
+               cache = new DeltaBaseCache(SZ);
+               rng = new TestRng(getClass().getSimpleName());
+       }
+
+       @Test
+       public void testObjectLargerThanCacheDoesNotEvict() {
+               byte[] obj12 = put(12, 32);
+               put(24, SZ + 5);
+               assertNull("does not store large object", cache.get(key, 24));
+               get(obj12, 12);
+       }
+
+       @Test
+       public void testCacheLruExpires1() {
+               byte[] obj1 = put(1, SZ / 4);
+               put(2, SZ / 4);
+               byte[] obj3 = put(3, SZ / 4);
+               put(4, SZ / 4);
+               assertEquals(SZ, cache.getMemoryUsed());
+
+               get(obj3, 3);
+               get(obj1, 1);
+               put(5, SZ / 2);
+               assertEquals(SZ, cache.getMemoryUsed());
+               assertEquals(SZ, cache.getMemoryUsedByTableForTest());
+               assertEquals(SZ, cache.getMemoryUsedByLruChainForTest());
+               assertNull(cache.get(key, 4));
+               assertNull(cache.get(key, 2));
+
+               get(obj1, 1);
+               get(obj3, 3);
+       }
+
+       @Test
+       public void testCacheLruExpires2() {
+               int pos0 = (0 << 10) | 2;
+               int pos1 = (1 << 10) | 2;
+               int pos2 = (2 << 10) | 2;
+               int pos5 = (5 << 10) | 2;
+               int pos6 = (6 << 10) | 2;
+
+               put(pos0, SZ / 4);
+               put(pos5, SZ / 4);
+               byte[] obj1 = put(pos1, SZ / 4);
+               byte[] obj2 = put(pos2, SZ / 4);
+               assertEquals(SZ, cache.getMemoryUsed());
+
+               byte[] obj6 = put(pos6, SZ / 2);
+               assertEquals(SZ, cache.getMemoryUsed());
+               assertEquals(SZ, cache.getMemoryUsedByTableForTest());
+               assertEquals(SZ, cache.getMemoryUsedByLruChainForTest());
+               assertNull(cache.get(key, pos0));
+               assertNull(cache.get(key, pos5));
+
+               get(obj1, pos1);
+               get(obj2, pos2);
+               get(obj6, pos6);
+       }
+
+       @Test
+       public void testCacheMemoryUsedConsistentWithExpectations() {
+               put(1, 32);
+               put(2, 32);
+               put(3, 32);
+
+               assertNotNull(cache.get(key, 1));
+               assertNotNull(cache.get(key, 1));
+
+               assertEquals(32 * 3, cache.getMemoryUsed());
+               assertEquals(32 * 3, cache.getMemoryUsedByTableForTest());
+               assertEquals(32 * 3, cache.getMemoryUsedByLruChainForTest());
+       }
+
+       private void get(byte[] data, int position) {
+               Entry e = cache.get(key, position);
+               assertNotNull("expected entry at " + position, e);
+               assertEquals("expected blob for " + position, OBJ_BLOB, e.type);
+               assertSame("expected data for " + position, data, e.data);
+       }
+
+       private byte[] put(int position, int sz) {
+               byte[] data = rng.nextBytes(sz);
+               cache.put(key, position, OBJ_BLOB, data);
+               return data;
+       }
+}
index 53c05f01381bc1e293e8f2ddeb2353e1ef99b49f..c7bdbff8a6f11da04390330bf4f318d5fe4e6c85 100644 (file)
@@ -70,8 +70,11 @@ final class DeltaBaseCache {
        private int curByteCount;
 
        DeltaBaseCache(DfsReader reader) {
-               DfsReaderOptions options = reader.getOptions();
-               maxByteCount = options.getDeltaBaseCacheLimit();
+               this(reader.getOptions().getDeltaBaseCacheLimit());
+       }
+
+       DeltaBaseCache(int maxBytes) {
+               maxByteCount = maxBytes;
                table = new Slot[1 << TABLE_BITS];
        }
 
@@ -84,6 +87,7 @@ final class DeltaBaseCache {
                                        moveToHead(e);
                                        return buf;
                                }
+                               return null;
                        }
                }
                return null;
@@ -101,23 +105,15 @@ final class DeltaBaseCache {
                e.data = new SoftReference<Entry>(new Entry(data, objectType));
                e.tableNext = table[tableIdx];
                table[tableIdx] = e;
-               moveToHead(e);
+               lruPushHead(e);
        }
 
        private void releaseMemory() {
                while (curByteCount > maxByteCount && lruTail != null) {
-                       Slot currOldest = lruTail;
-                       Slot nextOldest = currOldest.lruPrev;
-
-                       curByteCount -= currOldest.size;
-                       unlink(currOldest);
-                       removeFromTable(currOldest);
-
-                       if (nextOldest == null)
-                               lruHead = null;
-                       else
-                               nextOldest.lruNext = null;
-                       lruTail = nextOldest;
+                       Slot e = lruTail;
+                       curByteCount -= e.size;
+                       lruRemove(e);
+                       removeFromTable(e);
                }
        }
 
@@ -136,27 +132,68 @@ final class DeltaBaseCache {
                                return;
                        }
                }
+
+               throw new IllegalStateException(String.format(
+                               "entry for %s:%d not in table", //$NON-NLS-1$
+                               e.pack, Long.valueOf(e.offset)));
        }
 
        private void moveToHead(final Slot e) {
-               unlink(e);
-               e.lruPrev = null;
-               e.lruNext = lruHead;
-               if (lruHead != null)
-                       lruHead.lruPrev = e;
+               if (e != lruHead) {
+                       lruRemove(e);
+                       lruPushHead(e);
+               }
+       }
+
+       private void lruRemove(final Slot e) {
+               Slot p = e.lruPrev;
+               Slot n = e.lruNext;
+
+               if (p != null) {
+                       p.lruNext = n;
+               } else {
+                       lruHead = n;
+               }
+
+               if (n != null) {
+                       n.lruPrev = p;
+               } else {
+                       lruTail = p;
+               }
+       }
+
+       private void lruPushHead(final Slot e) {
+               Slot n = lruHead;
+               e.lruNext = n;
+               if (n != null)
+                       n.lruPrev = e;
                else
                        lruTail = e;
+
+               e.lruPrev = null;
                lruHead = e;
        }
 
-       private void unlink(final Slot e) {
-               Slot prev = e.lruPrev;
-               Slot next = e.lruNext;
+       int getMemoryUsed() {
+               return curByteCount;
+       }
+
+       int getMemoryUsedByLruChainForTest() {
+               int r = 0;
+               for (Slot e = lruHead; e != null; e = e.lruNext) {
+                       r += e.size;
+               }
+               return r;
+       }
 
-               if (prev != null)
-                       prev.lruNext = next;
-               if (next != null)
-                       next.lruPrev = prev;
+       int getMemoryUsedByTableForTest() {
+               int r = 0;
+               for (int i = 0; i < table.length; i++) {
+                       for (Slot e = table[i]; e != null; e = e.tableNext) {
+                               r += e.size;
+                       }
+               }
+               return r;
        }
 
        static class Entry {