blob: 9cbd9c8be40f628226e051aedb673185cbce33ea [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.geode.internal;
/**
* UniqueIdGenerator is factory that will produce unique ids that fall in a range between 0 and
* numIds-1 inclusive. Obtained ids will not be reissued until they are released and until every
* other non-obtained id has been obtained first.
* <p>
* Memory Use: this implementation allocates numIds/64 8-byte longs at construction time and keeps
* them allocated.
* <p>
* Instances of this class are thread safe.
*
* @since GemFire 5.0.2
*
*/
public class UniqueIdGenerator {
/*
* BitSets are packed into arrays of "units." Currently a unit is a long, which consists of 64
* bits, requiring 6 address bits. The choice of unit is determined purely by performance
* concerns.
*/
private static final int ADDRESS_BITS_PER_UNIT = 6;
private static final int BITS_PER_UNIT = 1 << ADDRESS_BITS_PER_UNIT;
private static final int BIT_INDEX_MASK = BITS_PER_UNIT - 1;
/**
* The units in this BitSet. The ith bit is stored in units[i/64] at bit position i % 64 (where
* bit position 0 refers to the least significant bit and 63 refers to the most significant bit).
*/
private final long units[];
/**
* The maximum id supported by this generator
*/
private final int MAX_ID;
/**
* The next id to return from obtain if it is available.
*/
private int ctr = 0;
/**
* Given a bit index return unit index containing it.
*/
private static int unitIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_UNIT;
}
/**
* Given a bit index, return a unit that masks that bit in its unit.
*/
private static long bit(int bitIndex) {
return 1L << (bitIndex & BIT_INDEX_MASK);
}
/**
* Sets the bit at the specified index to <code>true</code> if it is not already in the set.
* Otherwise does nothing.
*
* @param bitIndex a bit index.
* @exception IndexOutOfBoundsException if the specified index is negative.
*/
private void setBit(int bitIndex) {
int unitIndex = unitIndex(bitIndex);
long bit = bit(bitIndex);
this.units[unitIndex] |= bit;
}
/**
* Sets the bit specified by the index to <code>false</code>.
*
* @param bitIndex the index of the bit to be cleared.
*/
private void clearBit(int bitIndex) {
int unitIndex = unitIndex(bitIndex);
long bit = bit(bitIndex);
this.units[unitIndex] &= ~bit;
}
/*
* trailingZeroTable[i] is the number of trailing zero bits in the binary representation of i.
*/
private static final byte trailingZeroTable[] = {-25, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1,
0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0,
1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,
0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0,
2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1,
0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0,
1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,
0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
private static int trailingZeroCnt(long val) {
// Loop unrolled for performance
int byteVal = (int) val & 0xff;
if (byteVal != 0) {
return trailingZeroTable[byteVal];
}
byteVal = (int) (val >>> 8) & 0xff;
if (byteVal != 0) {
return trailingZeroTable[byteVal] + 8;
}
byteVal = (int) (val >>> 16) & 0xff;
if (byteVal != 0) {
return trailingZeroTable[byteVal] + 16;
}
byteVal = (int) (val >>> 24) & 0xff;
if (byteVal != 0) {
return trailingZeroTable[byteVal] + 24;
}
byteVal = (int) (val >>> 32) & 0xff;
if (byteVal != 0) {
return trailingZeroTable[byteVal] + 32;
}
byteVal = (int) (val >>> 40) & 0xff;
if (byteVal != 0) {
return trailingZeroTable[byteVal] + 40;
}
byteVal = (int) (val >>> 48) & 0xff;
if (byteVal != 0) {
return trailingZeroTable[byteVal] + 48;
}
byteVal = (int) (val >>> 56) & 0xff;
return trailingZeroTable[byteVal] + 56;
}
private static final long WORD_MASK = 0xffffffffffffffffL;
/**
* Returns the index of the first bit that is set to <code>false</code> that occurs on or after
* the specified starting index.
*
* @param fromIndex the index to start checking from (inclusive).
* @return the index of the next clear bit.
*/
private int nextClearBit(int fromIndex) {
int u = unitIndex(fromIndex);
int testIndex = (fromIndex & BIT_INDEX_MASK);
long unit = this.units[u] >> testIndex;
if (unit == (WORD_MASK >> testIndex)) {
testIndex = 0;
}
while ((unit == WORD_MASK) && (u < this.units.length - 1)) {
unit = this.units[++u];
}
if (unit == WORD_MASK) {
return -1; // could not find a clear bit
}
int result;
if (unit == 0) {
result = u * BITS_PER_UNIT + testIndex;
} else {
testIndex += trailingZeroCnt(~unit);
result = ((u * BITS_PER_UNIT) + testIndex);
}
if (result > MAX_ID || result < 0) {
return -1;
} else {
return result;
}
}
/**
* Creates unique id pool that has numIds in it.
*
* @param numIds the maximum number of ids
*/
public UniqueIdGenerator(int numIds) {
if (numIds <= 0) {
throw new IllegalArgumentException(
"numIds < 0");
}
this.units = new long[(unitIndex(numIds - 1) + 1)];
this.MAX_ID = numIds - 1;
this.ctr = 0;
}
/**
* Obtain an id form the pool of available ids and return it.
*
* @throws IllegalStateException if all ids have been obtained
*/
public int obtain() {
synchronized (this) {
int candidate = this.ctr;
int result = nextClearBit(candidate);
if (result == -1 && candidate != 0) {
// The following check will do some additional work by scanning
// the range [candidate..MAX_ID] again if it does not find a clear bit
// in the range [0..candidate-1] but it will only do this when
// we are going to throw an exception.
result = nextClearBit(0);
}
if (result == -1) {
throw new IllegalStateException(
"Ran out of message ids");
} else {
setBit(result);
if (result == MAX_ID) {
this.ctr = 0;
} else {
this.ctr = result + 1;
}
return result;
}
}
}
/**
* Release a previously obtained id allowing it to be obtained in the future.
*/
public void release(int id) {
if (id < 0) {
throw new IllegalArgumentException(
String.format("negative id: %s", Integer.valueOf(id)));
} else if (id > this.MAX_ID) {
throw new IllegalArgumentException(
String.format("id > MAX_ID: %s", Integer.valueOf(id)));
}
synchronized (this) {
clearBit(id);
}
}
}