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

com.sleepycat.je.rep.utilint.SimpleTxnMap Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.rep.utilint;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import com.sleepycat.je.txn.Txn;

/**
 * SimpleTxnMap provides a customized (but limited functionality) map that's
 * well suited to the tracking of open transactions. Transactions are entered
 * into this map when they are first created, referenced while they are alive
 * via their transaction id and subsequently, removed upon commit or abort. So
 * the map access pattern for each transaction looks like the sequence:
 *
 * put [get]* remove
 *
 * For JE applications, like KVS, transactions can pass through this map at the
 * rate of 30K to 60K transactions/sec, so the map needs to process these
 * operations efficiently.
 *
 * This map tries to be efficient for the put, get, remove operations by:
 *
 * 1) Avoiding any memory allocation for the typical: put, get, remove
 * sequence. In contrast, a heap entry uses 24 bytes for each entry plus 16
 * bytes for the long object argument when using compressed oops. It could be
 * that the heap storage could be replaced by stack storage for the long object
 * argument since it's a downward lexical funarg, but I don't know if the jvm
 * does such analysis.
 *
 * 2) Having a very short instruction code path for the typical case.
 *
 * The data structure used here is very simple, and consists of two maps.
 *
 * 1) An array based map indexed by the low bits of the transaction id.
 *
 * 2) A regular java Map
 *
 * The array based map is the preferred location for map entries. If the slot
 * associated with the transaction id is occupied, we fall back to the the java
 * Map.
 *
 * So the best case behavior is as if the map were implemented entirely as an
 * array and the worst case is that we will do an extra integer mask, array
 * index and compare operation before we resort to using the java Map. Given
 * the behavior of transactions, we expect that the vast majority of the
 * operations will be implemented by the array map.
 *
 * This class provides a minimal subset of the operations provided by Map. All
 * methods are synchronized. This works well for replica replay in conjunction
 * with a jvm's thread biased locking strategy, but we may need explicit locks
 * for other usage.
 *
 * @param  the type of Txn object stored as values in the map
 */
public class SimpleTxnMap  {

    /* The low order bit mask used to mask the transaction Id */
    private final int cacheMask;

    /*
     * The preferred array map.
     *
     * Invariant: The txn with a given id can be in exactly one of the maps,
     * never in both.
     *
     */
    private final Txn arrayMap[];

    /* The number of entries in just the array map. */
    private int arrayMapSize = 0;

    /* The backup map. */
    private final HashMap backupMap = new HashMap<>();

    public SimpleTxnMap(int arrayMapSize) {
        if (Integer.bitCount(arrayMapSize) != 1) {
            throw new IllegalArgumentException("argument:" + arrayMapSize +
                                               " must be a power of two");
        }
        arrayMap = new Txn[arrayMapSize];
        cacheMask = arrayMapSize - 1;
    }

    /**
     * Adds a txn to the map. Note that the "put" operation in keeping with
     * transaction behavior does not expect to be called while a txn with that
     * ID is already in the map.
     */
    public synchronized void put(T txn) {
        assert get(txn.getId()) == null;
        final long txnId = txn.getId();
        int i = (int)txn.getId() & cacheMask;
        final Txn cachedTxn = arrayMap[i];
        if (cachedTxn == null) {
            /* Free slot use it. */
            arrayMap[i] = txn;
            arrayMapSize++;
            return;
        }

        /* Array slot occupied by a transaction, fall back to the map. */
        backupMap.put(txnId, txn);
    }

    synchronized public T get(long txnId) {
        @SuppressWarnings("unchecked")
        T cachedTxn = (T)arrayMap[(int)txnId & cacheMask];
        if ((cachedTxn != null) && (cachedTxn.getId() == txnId)) {
            assert ! backupMap.containsKey(txnId);
            return cachedTxn;
        }
        return backupMap.get(txnId);
    }

    /**
     * Removes the txn with that key, if it exists.
     *
     * @return the Txn that was removed, or empty if it did not exist.
     */
    synchronized public T remove(long txnId) {
        final int i = (int)txnId & cacheMask;
        @SuppressWarnings("unchecked")
        T cachedTxn = (T)arrayMap[i];
        if ((cachedTxn != null) && (cachedTxn.getId() == txnId)) {
            arrayMap[i] = null;
            arrayMapSize--;
            assert ! backupMap.containsKey(txnId);
            return cachedTxn;
        }

        /*
         * Array slot empty, or occupied by a different transaction,
         * check backup.
         */
        return backupMap.remove(txnId);
    }

    public synchronized int size() {
        return backupMap.size() + arrayMapSize;
    }

    public synchronized boolean isEmpty() {
        return size() == 0;
    }

    /**
     * The methods below are not used in critical paths and are not optimized.
     * They they are O(n) complexity. We can revisit with change in usage.
     */

    synchronized public void clear() {
        backupMap.clear();
        Arrays.fill(arrayMap, null);
        arrayMapSize = 0;
    }

    /**
     * Returns a new map containing the current snapshot of transactions in
     * this map.
     */
    synchronized public Map getMap() {
        final Map map = new HashMap(backupMap);
        for (Object element : arrayMap) {
            @SuppressWarnings("unchecked")
            final T txn = (T)element;
            if (txn != null) {
                T old = map.put(txn.getId(), txn);
                assert old == null;
            }
        }

        return map;
    }

    /**
     * For test use only
     */
    public HashMap getBackupMap() {
        return backupMap;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy