All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.plugins.segment.SegmentIdTable Maven / Gradle / Ivy

There is a newer version: 1.6.23
Show newest version
/*
 * 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.plugins.segment;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMapWithExpectedSize;
import static java.util.Collections.nCopies;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Hash table of weak references to segment identifiers.
 */
public class SegmentIdTable {

    /**
     * The list of weak references to segment identifiers that are currently
     * being accessed. This represents a hash table that uses open addressing
     * with linear probing. It is not a hash map, to speed up read access.
     * 

* The size of the table is always a power of two, so that we can use * bitwise "and" instead of modulo. *

* The table is indexed by the random identifier bits, which guarantees * uniform distribution of entries. *

* Open addressing with linear probing is used. Each table entry is either * null (when there are no matching identifiers), a weak references to the * matching identifier, or a weak reference to another identifier. * There are no tombstone entries as there is no explicit remove operation, * but a referent can become null if the entry is garbage collected. *

* The array is not sorted (we could; lookup might be faster, but adding * entries would be slower). */ private final ArrayList> references = newArrayList(nCopies(1024, (WeakReference) null)); private final SegmentTracker tracker; private static final Logger LOG = LoggerFactory.getLogger(SegmentIdTable.class); /** * The refresh count (for diagnostics and testing). */ private int rebuildCount; /** * The number of used entries (WeakReferences) in this table. */ private int entryCount; SegmentIdTable(SegmentTracker tracker) { this.tracker = tracker; } /** * Get the segment id, and reference it in the weak references map. * * @param msb * @param lsb * @return the segment id */ synchronized SegmentId getSegmentId(long msb, long lsb) { int first = getIndex(lsb); int index = first; boolean shouldRefresh = false; WeakReference reference = references.get(index); while (reference != null) { SegmentId id = reference.get(); if (id != null && id.getMostSignificantBits() == msb && id.getLeastSignificantBits() == lsb) { return id; } // shouldRefresh if we have a garbage collected entry shouldRefresh = shouldRefresh || id == null; // open addressing / linear probing index = (index + 1) % references.size(); reference = references.get(index); } SegmentId id = new SegmentId(tracker, msb, lsb); references.set(index, new WeakReference(id)); entryCount++; if (entryCount > references.size() * 0.75) { // more than 75% full shouldRefresh = true; } if (shouldRefresh) { refresh(); } return id; } /** * Returns all segment identifiers that are currently referenced in memory. * * @param ids referenced segment identifiers */ void collectReferencedIds(Collection ids) { ids.addAll(refresh()); } private synchronized Collection refresh() { int size = references.size(); Map> ids = newHashMapWithExpectedSize(size); boolean hashCollisions = false; boolean emptyReferences = false; for (int i = 0; i < size; i++) { WeakReference reference = references.get(i); if (reference != null) { SegmentId id = reference.get(); if (id != null) { ids.put(id, reference); hashCollisions = hashCollisions || (i != getIndex(id)); } else { references.set(i, null); entryCount--; emptyReferences = true; } } } if (entryCount != ids.size()) { // something is wrong, possibly a concurrency problem, a SegmentId // hashcode or equals bug, or a problem with this hash table // algorithm LOG.warn("Unexpected entry count mismatch, expected " + entryCount + " got " + ids.size()); // we fix the count, because having a wrong entry count would be // very problematic; even worse than having a concurrency problem entryCount = ids.size(); } while (2 * ids.size() > size) { size *= 2; } // we need to re-build the table if the new size is different, // but also if we removed some of the entries (because an entry was // garbage collected) and there is at least one entry at the "wrong" // location (due to open addressing) if ((hashCollisions && emptyReferences) || size != references.size()) { rebuildCount++; references.clear(); references.addAll(nCopies(size, (WeakReference) null)); for (Map.Entry> entry : ids.entrySet()) { int index = getIndex(entry.getKey()); while (references.get(index) != null) { index = (index + 1) % size; } references.set(index, entry.getValue()); } } return ids.keySet(); } private int getIndex(SegmentId id) { return getIndex(id.getLeastSignificantBits()); } private int getIndex(long lsb) { return ((int) lsb) & (references.size() - 1); } synchronized void clearSegmentIdTables(CompactionStrategy strategy) { int size = references.size(); boolean dirty = false; for (int i = 0; i < size; i++) { WeakReference reference = references.get(i); if (reference != null) { SegmentId id = reference.get(); if (id != null) { if (strategy.canRemove(id)) { // we clear the reference here, but we must not // remove the reference from the list, because // that could cause duplicate references // (there is a unit test for this case) reference.clear(); dirty = true; } } } } if (dirty) { refresh(); } } /** * Get the number of map rebuild operations (used for testing and diagnostics). * * @return the rebuild count */ int getMapRebuildCount() { return rebuildCount; } /** * Get the entry count (used for testing and diagnostics). * * @return the entry count */ int getEntryCount() { return entryCount; } /** * Get the size of the internal map (used for testing and diagnostics). * * @return the map size */ int getMapSize() { return references.size(); } /** * Get the raw list of segment ids (used for testing). * * @return the raw list */ List getRawSegmentIdList() { ArrayList list = new ArrayList(); for (WeakReference ref : references) { if (ref != null) { SegmentId id = ref.get(); if (id != null) { list.add(id); } } } return list; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy