blob: cc2c55678f4357bcd3e4481b8af0012b906359fc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.jackrabbit.oak.segment;
import static com.google.common.collect.Sets.newHashSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.jackrabbit.oak.segment.memory.MemoryStore;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
public class SegmentIdTableTest {
private static SegmentIdFactory newSegmentIdMaker(final SegmentStore store) {
return new SegmentIdFactory() {
@NotNull
@Override
public SegmentId newSegmentId(long msb, long lsb) {
return new SegmentId(store, msb, lsb);
}
};
}
private static SegmentIdFactory newSegmentIdMaker() throws IOException {
return newSegmentIdMaker(new MemoryStore());
}
/**
* OAK-2752
*/
@Test
public void endlessSearchLoop() throws IOException {
final SegmentIdFactory maker = newSegmentIdMaker();
final SegmentIdTable tbl = new SegmentIdTable();
List<SegmentId> refs = new ArrayList<SegmentId>();
for (int i = 0; i < 1024; i++) {
refs.add(tbl.newSegmentId(i, i % 64, maker));
}
Callable<SegmentId> c = new Callable<SegmentId>() {
@Override
public SegmentId call() throws Exception {
// (2,1) doesn't exist
return tbl.newSegmentId(2, 1, maker);
}
};
Future<SegmentId> f = Executors.newSingleThreadExecutor().submit(c);
SegmentId s = null;
try {
s = f.get(5, TimeUnit.SECONDS);
} catch (Exception e) {
fail(e.getMessage());
}
assertNotNull(s);
assertEquals(2, s.getMostSignificantBits());
assertEquals(1, s.getLeastSignificantBits());
}
@Test
public void randomized() throws IOException {
SegmentIdFactory maker = newSegmentIdMaker();
final SegmentIdTable tbl = new SegmentIdTable();
List<SegmentId> refs = new ArrayList<SegmentId>();
Random r = new Random(1);
for (int i = 0; i < 16 * 1024; i++) {
refs.add(tbl.newSegmentId(r.nextLong(), r.nextLong(), maker));
}
assertEquals(16 * 1024, tbl.getEntryCount());
assertEquals(16 * 2048, tbl.getMapSize());
assertEquals(5, tbl.getMapRebuildCount());
r = new Random(1);
for (int i = 0; i < 16 * 1024; i++) {
refs.add(tbl.newSegmentId(r.nextLong(), r.nextLong(), maker));
assertEquals(16 * 1024, tbl.getEntryCount());
assertEquals(16 * 2048, tbl.getMapSize());
assertEquals(5, tbl.getMapRebuildCount());
}
}
@Test
public void clearTable() throws IOException {
SegmentIdFactory maker = newSegmentIdMaker();
final SegmentIdTable tbl = new SegmentIdTable();
List<SegmentId> refs = new ArrayList<SegmentId>();
for (int i = 0; i < 8; i++) {
refs.add(tbl.newSegmentId(i, i % 2, maker));
}
Set<UUID> reclaimed = newHashSet();
for (SegmentId id : refs) {
if (id.getMostSignificantBits() < 4) {
reclaimed.add(id.asUUID());
}
}
assertEquals(0, tbl.getMapRebuildCount());
tbl.clearSegmentIdTables(reclaimed, "TestGcInfo");
for (SegmentId id : refs) {
if (id.getMostSignificantBits() < 4) {
assertEquals("TestGcInfo", id.getGcInfo());
} else {
assertNull(id.getGcInfo());
}
}
}
@Test
public void justHashCollisions() throws IOException {
SegmentIdFactory maker = newSegmentIdMaker();
final SegmentIdTable tbl = new SegmentIdTable();
List<SegmentId> refs = new ArrayList<SegmentId>();
int originalCount = 1024;
for (int i = 0; i < originalCount; i++) {
// modulo 128 to ensure we have conflicts
refs.add(tbl.newSegmentId(i, i % 128, maker));
}
assertEquals(originalCount, tbl.getEntryCount());
assertEquals(1, tbl.getMapRebuildCount());
List<SegmentId> refs2 = new ArrayList<SegmentId>();
tbl.collectReferencedIds(refs2);
assertEquals(refs.size(), refs2.size());
assertEquals(originalCount, tbl.getEntryCount());
// we don't expect that there was a refresh,
// because there were just hash collisions
assertEquals(1, tbl.getMapRebuildCount());
}
@Test
public void gc() throws IOException {
SegmentIdFactory maker = newSegmentIdMaker();
final SegmentIdTable tbl = new SegmentIdTable();
List<SegmentId> refs = new ArrayList<SegmentId>();
int originalCount = 1024;
for (int i = 0; i < originalCount; i++) {
// modulo 128 to ensure we have conflicts
refs.add(tbl.newSegmentId(i, i % 128, maker));
}
assertEquals(originalCount, tbl.getEntryCount());
assertEquals(1, tbl.getMapRebuildCount());
for (int i = 0; i < refs.size() / 2; i++) {
// we need to remove the first entries,
// because if we remove the last entries, then
// getSegmentId would not detect that entries were freed up
refs.remove(0);
}
for (int gcCalls = 0;; gcCalls++) {
// needed here, so some entries can be garbage collected
System.gc();
for (SegmentId id : refs) {
long msb = id.getMostSignificantBits();
long lsb = id.getLeastSignificantBits();
SegmentId id2 = tbl.newSegmentId(msb, lsb, maker);
assertTrue(id2 == id);
}
// because we found each entry, we expect the refresh count is the same
assertEquals(1, tbl.getMapRebuildCount());
// even thought this does not increase the entry count a lot,
// it is supposed to detect that entries were removed,
// and force a refresh, which would get rid of the unreferenced ids
for (int i = 0; i < 10; i++) {
tbl.newSegmentId(i, i, maker);
}
if (tbl.getEntryCount() < originalCount) {
break;
} else if (gcCalls > 10) {
fail("No entries were garbage collected after 10 times System.gc()");
}
}
assertEquals(2, tbl.getMapRebuildCount());
}
}