| /* |
| * 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()); |
| } |
| } |