net.openhft.chronicle.queue.impl.single.SCQIndexing Maven / Gradle / Ivy
Show all versions of chronicle-queue Show documentation
/*
* Copyright 2016-2020 https://chronicle.software
*
* Licensed 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 net.openhft.chronicle.queue.impl.single;
import net.openhft.chronicle.bytes.Byteable;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.bytes.BytesUtil;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.Maths;
import net.openhft.chronicle.core.StackTrace;
import net.openhft.chronicle.core.annotation.UsedViaReflection;
import net.openhft.chronicle.core.io.AbstractCloseable;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.scoped.ScopedResource;
import net.openhft.chronicle.core.threads.CleaningThreadLocal;
import net.openhft.chronicle.core.threads.ThreadLocalHelper;
import net.openhft.chronicle.core.values.LongArrayValues;
import net.openhft.chronicle.core.values.LongValue;
import net.openhft.chronicle.queue.impl.ExcerptContext;
import net.openhft.chronicle.wire.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.EOFException;
import java.io.StreamCorruptedException;
import java.io.UncheckedIOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
import static net.openhft.chronicle.core.io.Closeable.closeQuietly;
import static net.openhft.chronicle.queue.RollCycle.MAX_INDEX_COUNT;
import static net.openhft.chronicle.wire.Wires.NOT_INITIALIZED;
@SuppressWarnings("deprecation")
class SCQIndexing extends AbstractCloseable implements Indexing, Demarshallable, WriteMarshallable, Closeable {
private static final boolean IGNORE_INDEXING_FAILURE = Jvm.getBoolean("queue.ignoreIndexingFailure");
private static final boolean REPORT_LINEAR_SCAN = Jvm.getBoolean("chronicle.queue.report.linear.scan.latency");
private static final long LINEAR_SCAN_WARN_THRESHOLD_NS = Long.getLong("linear.scan.warn.ns", 100_000);
final LongValue nextEntryToBeIndexed;
private final int indexCount;
private final int indexCountBits;
private final int indexSpacing;
private final int indexSpacingBits;
private final LongValue index2Index;
private final Supplier longArraySupplier;
@NotNull
private final ThreadLocal> index2indexArray;
@NotNull
private final ThreadLocal> indexArray;
@NotNull
private final WriteMarshallable index2IndexTemplate;
@NotNull
private final WriteMarshallable indexTemplate;
/**
* Extracted as field to prevent lambda creation on every method reference pass.
*/
private final Function, LongArrayValuesHolder> arrayValuesSupplierCall = this::newLogArrayValuesHolder;
LongValue writePosition;
Sequence sequence;
// visible for testing
int linearScanCount;
int linearScanByPositionCount;
Collection closeables = new ArrayList<>();
private long lastScannedIndex = -1;
/**
* used by {@link Demarshallable}
*
* @param wire a wire
*/
@UsedViaReflection
private SCQIndexing(@NotNull WireIn wire) {
this(wire.read(IndexingFields.indexCount).int32(),
wire.read(IndexingFields.indexSpacing).int32(),
wire.read(IndexingFields.index2Index).int64ForBinding(null),
wire.read(IndexingFields.lastIndex).int64ForBinding(null),
wire::newLongArrayReference);
}
SCQIndexing(@NotNull WireType wireType, int indexCount, int indexSpacing) {
this(indexCount,
indexSpacing,
wireType.newLongReference().get(),
wireType.newLongReference().get(),
wireType.newLongArrayReference());
}
private SCQIndexing(int indexCount, int indexSpacing, LongValue index2Index, LongValue nextEntryToBeIndexed, Supplier longArraySupplier) {
this.indexCount = indexCount;
this.indexCountBits = Maths.intLog2(indexCount);
this.indexSpacing = indexSpacing;
this.indexSpacingBits = Maths.intLog2(indexSpacing);
this.index2Index = index2Index;
this.nextEntryToBeIndexed = nextEntryToBeIndexed;
this.longArraySupplier = longArraySupplier;
this.index2indexArray = CleaningThreadLocal.withCleanup(wr -> Closeable.closeQuietly(wr.get()));
this.indexArray = CleaningThreadLocal.withCleanup(wr -> Closeable.closeQuietly(wr.get()));
this.index2IndexTemplate = w -> w.writeEventName("index2index").int64array(indexCount);
this.indexTemplate = w -> w.writeEventName("index").int64array(indexCount);
singleThreadedCheckDisabled(true);
}
private LongArrayValuesHolder newLogArrayValuesHolder(Supplier las) {
LongArrayValues values = las.get();
LongArrayValuesHolder longArrayValuesHolder = new LongArrayValuesHolder(values);
closeables.add(values);
return longArrayValuesHolder;
}
@NotNull
private LongArrayValuesHolder getIndex2IndexArray() {
return ThreadLocalHelper.getTL(index2indexArray, longArraySupplier, arrayValuesSupplierCall);
}
@NotNull
private LongArrayValuesHolder getIndexArray() {
return ThreadLocalHelper.getTL(indexArray, longArraySupplier, arrayValuesSupplierCall);
}
public long toAddress0(long index) {
throwExceptionIfClosed();
long siftedIndex = index >> (indexSpacingBits + indexCountBits);
long mask = indexCount - 1L;
// convert to an offset
return mask & siftedIndex;
}
long toAddress1(long index) {
long siftedIndex = index >> indexSpacingBits;
long mask = indexCount - 1L;
// convert to an offset
return mask & siftedIndex;
}
/* @Override
protected boolean performCloseInBackground() {
return true;
}*/
@Override
protected void performClose() {
closeQuietly(index2Index, nextEntryToBeIndexed);
closeQuietly(closeables);
closeables.clear();
// Eagerly clean up the contents of thread locals but only for this thread.
// The contents of the thread local for other threads will be cleaned up in
// MappedFile.performRelease
closeTL(indexArray);
closeTL(index2indexArray);
}
private void closeTL(ThreadLocal> tl) {
WeakReference weakReference = tl.get();
if (weakReference == null)
return;
LongArrayValuesHolder holder = weakReference.get();
if (holder != null)
closeQuietly(holder.values());
}
@Override
public void writeMarshallable(@NotNull WireOut wire) {
wire.write(IndexingFields.indexCount).int64(indexCount)
.write(IndexingFields.indexSpacing).int64(indexSpacing)
.write(IndexingFields.index2Index).int64forBinding(0L, index2Index)
.write(IndexingFields.lastIndex).int64forBinding(0L, nextEntryToBeIndexed);
}
@NotNull
private LongArrayValues arrayForAddress(@NotNull Wire wire, long secondaryAddress) {
LongArrayValuesHolder holder = getIndexArray();
if (holder.address() == secondaryAddress)
return holder.values();
holder.address(secondaryAddress);
wire.bytes().readPositionRemaining(secondaryAddress, 4); // to read the header.
wire.readMetaDataHeader();
return array(wire, holder.values(), false);
}
@NotNull
private LongArrayValues array(@NotNull WireIn w, @NotNull LongArrayValues using, boolean index2index) {
@NotNull final ValueIn valueIn = readIndexValue(w, index2index ? "index2index" : "index");
valueIn.int64array(using, this, (o1, o2) -> {
});
return using;
}
private ValueIn readIndexValue(@NotNull WireIn w, @NotNull String expectedName) {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
final StringBuilder sb = stlSb.get();
long readPos = w.bytes().readPosition();
@NotNull final ValueIn valueIn = w.readEventName(sb);
if (!expectedName.contentEquals(sb))
throw new IllegalStateException("expecting " + expectedName + ", was " + sb + ", bytes: " + w.bytes().readPosition(readPos).toHexString());
return valueIn;
}
}
/**
* Creates a new Excerpt containing and index which will be 1L << 17L bytes long, This method is used for creating both the primary and secondary
* indexes. Chronicle Queue uses a root primary index ( each entry in the primary index points to a unique a secondary index. The secondary index
* only records the addressForRead of every 64th except, the except are linearly scanned from there on. )
*
* @param wire the current wire
* @return the addressForRead of the Excerpt containing the usable index, just after the header
*/
long newIndex(@NotNull WireOut wire, boolean index2index) throws StreamCorruptedException {
long writePosition = this.writePosition.getVolatileValue();
Bytes> bytes = wire.bytes();
bytes.writePosition(writePosition);
long position = wire.enterHeader(indexCount * 8L + 128);
WriteMarshallable writer = index2index ? index2IndexTemplate : indexTemplate;
writer.writeMarshallable(wire);
wire.updateHeader(position, true, 0);
return position;
}
long newIndex(@NotNull Wire wire, @NotNull LongArrayValues index2Index, long index2) throws StreamCorruptedException {
try {
long pos = newIndex(wire, false);
if (!index2Index.compareAndSet(index2, NOT_INITIALIZED, pos)) {
throw new IllegalStateException("Index " + index2 + " in index2index was altered while we hold the write lock!");
}
index2Index.setMaxUsed(index2 + 1);
return pos;
} catch (Exception e) {
throw e;
}
}
/**
* Moves the position to the {@code index} The indexes are stored in many excerpts, so the index2index tells chronicle where ( in other words
* the addressForRead of where ) the root first level targetIndex is stored. The indexing works like a tree, but only 2 levels deep, the root of
* the tree is at index2index ( this first level targetIndex is 1MB in size and there is only one of them, it only holds the addresses of the
* second level indexes, there will be many second level indexes ( created on demand ), each is about 1MB in size (this second level targetIndex
* only stores the position of every 64th excerpt (depending on RollCycle)), so from every 64th excerpt a linear scan occurs.
*
* @param ec the data structure we are navigating
* @param index the index we wish to move to
* @return the position of the {@code targetIndex} or -1 if the index can not be found
*/
@NotNull
ScanResult moveToIndex(@NotNull final ExcerptContext ec, final long index) {
ScanResult value = moveToIndex0(ec, index);
if (value == null)
return moveToIndexFromTheStart(ec, index);
return value;
}
@NotNull
private ScanResult moveToIndexFromTheStart(@NotNull ExcerptContext ec, long index) {
try {
Wire wire = ec.wire();
if (wire == null)
return ScanResult.END_OF_FILE;
wire.bytes().readPositionUnlimited(0);
if (wire.readDataHeader())
return linearScan(wire, index, 0, wire.bytes().readPosition());
} catch (EOFException fallback) {
return ScanResult.END_OF_FILE;
}
return ScanResult.NOT_FOUND;
}
// visible for testing
@Nullable
ScanResult moveToIndex0(@NotNull final ExcerptContext ec, final long index) {
if (index2Index.getVolatileValue() == NOT_INITIALIZED)
return null;
Wire wireForIndex = ec.wireForIndex();
LongArrayValues index2index = getIndex2index(wireForIndex);
long primaryOffset = toAddress0(index);
long secondaryAddress = 0;
long startIndex = index & -indexSpacing;
while (primaryOffset >= 0) {
secondaryAddress = index2index.getValueAt(primaryOffset);
if (secondaryAddress != 0) {
@NotNull final LongArrayValues array1 = arrayForAddress(wireForIndex, secondaryAddress);
ScanResult result = scanSecondaryIndexBackwards(ec, array1, startIndex, index);
if (result != null)
return result;
}
startIndex -= (long) indexCount * indexSpacing;
primaryOffset--;
}
return null;
}
private ScanResult scanSecondaryIndexBackwards(@NotNull final ExcerptContext ec, LongArrayValues array1, long startIndex, long index) {
long secondaryOffset = toAddress1(index);
do {
long fromAddress = array1.getValueAt(secondaryOffset);
if (fromAddress == 0) {
secondaryOffset--;
startIndex -= indexSpacing;
continue;
}
Wire wire = ec.wire();
if (wire == null)
break;
if (index == startIndex) {
wire.bytes().readPositionUnlimited(fromAddress);
return ScanResult.FOUND;
} else {
return linearScan(wire, index, startIndex, fromAddress);
}
} while (secondaryOffset >= 0);
return null; // no index,
}
/**
* moves the context to the index of {@code toIndex} by doing a linear scans form a {@code fromKnownIndex} at {@code knownAddress}
note meta
* data is skipped and does not count to the indexes
*
* @param wire if successful, moves the context to an addressForRead relating to the index {@code toIndex }
* @param toIndex the index that we wish to move the context to
* @param fromKnownIndex a know index ( used as a starting point )
* @param knownAddress a know addressForRead ( used as a starting point )
* @see SCQIndexing#moveToIndex
*/
@NotNull
private ScanResult linearScan(@NotNull final Wire wire,
final long toIndex,
final long fromKnownIndex,
final long knownAddress) {
if (toIndex == fromKnownIndex)
return ScanResult.FOUND;
long start = REPORT_LINEAR_SCAN ? System.nanoTime() : 0;
ScanResult scanResult = linearScan0(wire, toIndex, fromKnownIndex, knownAddress);
if (REPORT_LINEAR_SCAN) {
printLinearScanTime(lastScannedIndex, fromKnownIndex, start, "linearScan by index");
}
return scanResult;
}
private void printLinearScanTime(long toIndex, long fromKnownIndex, long start, String desc) {
// still warming up?
if (toIndex <= 1)
return;
// took too long to scan?
long end = System.nanoTime();
if (end < start + LINEAR_SCAN_WARN_THRESHOLD_NS)
return;
doPrintLinearScanTime(toIndex, fromKnownIndex, start, desc, end);
}
private void doPrintLinearScanTime(long toIndex, long fromKnownIndex, long start, String desc, long end) {
StackTrace st = null;
if (Jvm.isDebugEnabled(getClass())) {
int time = Jvm.isArm() ? 20_000_000 : 250_000;
// ignore the time for the first message
if (toIndex > 0 && end > start + time)
st = new StackTrace("This is a profile stack trace, not an ERROR");
}
long tookUS = (end - start) / 1000;
String message = "Took " + tookUS + " us to " + desc + " " +
fromKnownIndex + " to index " + toIndex;
Jvm.perf().on(getClass(), message, st);
}
@NotNull
private ScanResult linearScan0(@NotNull final Wire wire,
final long toIndex,
long fromKnownIndex,
long knownAddress) {
this.linearScanCount++;
@NotNull final Bytes> bytes = wire.bytes();
// optimized if the `toIndex` is the last sequence
long lastAddress = writePosition.getVolatileValue();
long lastIndex = this.sequence.getSequence(lastAddress);
if (toIndex == lastIndex) {
assert (lastAddress >= knownAddress && lastIndex >= fromKnownIndex);
knownAddress = lastAddress;
fromKnownIndex = lastIndex;
}
bytes.readPositionUnlimited(knownAddress);
for (long i = fromKnownIndex; ; i++) {
try {
if (wire.readDataHeader()) {
if (i == toIndex) {
lastScannedIndex = i;
return ScanResult.FOUND;
}
int header = bytes.readVolatileInt();
if (Wires.isNotComplete(header)) { // or isEndOfFile
lastScannedIndex = i;
return ScanResult.NOT_REACHED;
}
bytes.readSkip(Wires.lengthOf(header));
continue;
}
} catch (EOFException fallback) {
// reached the end of the file.
if (i == toIndex) {
return ScanResult.END_OF_FILE;
}
}
lastScannedIndex = i;
return i == toIndex ? ScanResult.NOT_FOUND : ScanResult.NOT_REACHED;
}
}
ScanResult linearScanTo(final long toIndex, final long knownIndex, final ExcerptContext ec, final long knownAddress) {
return linearScan(ec.wire(), toIndex, knownIndex, knownAddress);
}
long linearScanByPosition(@NotNull final Wire wire,
final long toPosition,
final long indexOfNext,
final long startAddress,
boolean inclusive) throws EOFException {
long start = REPORT_LINEAR_SCAN ? System.nanoTime() : 0;
long index = linearScanByPosition0(wire, toPosition, indexOfNext, startAddress, inclusive);
if (REPORT_LINEAR_SCAN) {
printLinearScanTime(index, startAddress, start, "linearScan by position");
}
return index;
}
long linearScanByPosition0(@NotNull final Wire wire,
final long toPosition,
long indexOfNext,
long startAddress,
boolean inclusive) throws EOFException {
linearScanByPositionCount++;
assert toPosition >= 0;
Bytes> bytes = wire.bytes();
long i;
// optimized if the `toPosition` is the writePosition
long lastAddress = writePosition.getVolatileValue();
long lastIndex = this.sequence.getSequence(lastAddress);
i = calculateInitialValue(toPosition, indexOfNext, startAddress, bytes, lastAddress, lastIndex);
while (bytes.readPosition() <= toPosition) {
WireIn.HeaderType headerType = wire.readDataHeader(true);
if (headerType == WireIn.HeaderType.EOF) {
if (toPosition == Long.MAX_VALUE)
return i;
throw new EOFException();
}
if (!inclusive && toPosition == bytes.readPosition())
return i;
switch (headerType) {
case NONE:
if (toPosition == Long.MAX_VALUE) {
return i;
}
int header = bytes.readVolatileInt(bytes.readPosition());
throwIndexNotWritten(toPosition, startAddress, bytes, header);
break;
case META_DATA:
break;
case DATA:
++i;
break;
case EOF:
throw new AssertionError("EOF should have been handled");
}
if (bytes.readPosition() == toPosition)
return i;
int header = bytes.readVolatileInt();
int len = Wires.lengthOf(header);
assert Wires.isReady(header);
bytes.readSkip(len);
}
return throwPositionNotAtStartOfMessage(toPosition, bytes);
}
private long calculateInitialValue(long toPosition, long indexOfNext, long startAddress, Bytes> bytes, long lastAddress, long lastIndex) {
if (lastAddress > 0 && toPosition == lastAddress
&& lastIndex != Sequence.NOT_FOUND && lastIndex != Sequence.NOT_FOUND_RETRY) {
bytes.readPositionUnlimited(toPosition);
return lastIndex - 1;
} else {
bytes.readPositionUnlimited(startAddress);
return indexOfNext - 1;
}
}
private void throwIndexNotWritten(long toPosition, long startAddress, Bytes> bytes, int header) {
throw new IllegalArgumentException(
"You can't know the index for an entry which hasn't been written. " +
"start: " + startAddress +
", at: " + bytes.readPosition() +
", header: " + Integer.toHexString(header) +
", toPos: " + toPosition);
}
private long throwPositionNotAtStartOfMessage(long toPosition, Bytes> bytes) {
throw new IllegalArgumentException("position not the start of a message, bytes" +
".readPosition()=" + bytes.readPosition() + ",toPosition=" + toPosition);
}
@Override
public long nextEntryToBeIndexed() {
return nextEntryToBeIndexed.getVolatileValue();
}
long sequenceForPosition(@NotNull ExcerptContext ec,
final long position,
boolean inclusive) throws StreamCorruptedException {
long indexOfNext = 0;
long lastKnownAddress = 0;
@NotNull Wire wire = ec.wireForIndex();
try {
final LongArrayValues index2indexArr = getIndex2index(wire);
int used2 = getUsedAsInt(index2indexArr);
Outer:
for (int index2 = used2 - 1; index2 >= 0; index2--) {
long secondaryAddress = getSecondaryAddress(wire, index2indexArr, index2);
if (secondaryAddress == 0)
continue;
LongArrayValues indexValues = arrayForAddress(wire, secondaryAddress);
// TODO use a binary rather than linear search
// check the first one to see if any in the index is appropriate.
int used = getUsedAsInt(indexValues);
if (used == 0)
continue;
long posN = indexValues.getVolatileValueAt(0);
assert posN >= 0;
if (posN > position)
continue;
for (int index1 = used - 1; index1 >= 0; index1--) {
long pos = indexValues.getVolatileValueAt(index1);
// TODO pos shouldn't be 0, but holes in the index appear..
if (pos == 0 || pos > position) {
continue;
}
lastKnownAddress = pos;
indexOfNext = ((long) index2 << (indexCountBits + indexSpacingBits)) + ((long) index1 << indexSpacingBits);
if (lastKnownAddress == position)
return indexOfNext;
break Outer;
}
}
} catch (IllegalStateException e) {
if (Jvm.isDebugEnabled(getClass()))
Jvm.debug().on(getClass(), "Attempt to find " + Long.toHexString(position), e);
}
try {
return linearScanByPosition(wire, position, indexOfNext, lastKnownAddress, inclusive);
} catch (EOFException e) {
throw new UncheckedIOException(e);
}
}
static int getUsedAsInt(LongArrayValues index2indexArr) {
if (((Byteable) index2indexArr).bytesStore() == null)
return 0;
final long used = index2indexArr.getUsed();
if (used < 0 || used > MAX_INDEX_COUNT)
throw new IllegalStateException("Used: " + used);
return (int) used;
}
void initIndex(@NotNull Wire wire) throws StreamCorruptedException {
long index2Index = this.index2Index.getVolatileValue();
if (index2Index != NOT_INITIALIZED)
throw new IllegalStateException("Who wrote the index2index?");
// Ensure new header position is found despite first header not being finalized
long oldPos = wire.bytes().writePosition();
if (!writePosition.compareAndSwapValue(0, oldPos))
throw new IllegalStateException("Who updated the position?");
long index = newIndex(wire, true);
this.index2Index.compareAndSwapValue(NOT_INITIALIZED, index);
LongArrayValues index2index = getIndex2index(wire);
newIndex(wire, index2index, 0);
// Reset position as it were
if (!writePosition.compareAndSwapValue(oldPos, 0))
throw new IllegalStateException("Who reset the position?");
}
@SuppressWarnings("try")
private LongArrayValues getIndex2index(@NotNull Wire wire) {
LongArrayValuesHolder holder = getIndex2IndexArray();
LongArrayValues values = holder.values();
if (((Byteable) values).bytesStore() != null)
return values;
final long indexToIndex = index2Index.getVolatileValue();
try (DocumentContext ignored = wire.readingDocument(indexToIndex)) {
return array(wire, values, true);
}
}
// May throw UnrecoverableTimeoutException
private long getSecondaryAddress(@NotNull Wire wire, @NotNull LongArrayValues index2indexArr, int index2) throws StreamCorruptedException {
long secondaryAddress = index2indexArr.getVolatileValueAt(index2);
if (secondaryAddress == 0) {
secondaryAddress = newIndex(wire, index2indexArr, index2);
long sa = index2indexArr.getValueAt(index2);
if (sa != secondaryAddress)
throw new AssertionError();
}
return secondaryAddress;
}
/**
* add an entry to the sequenceNumber, so stores the position of an sequenceNumber
*
* @param ec the wire that used to store the data
* @param sequenceNumber the sequenceNumber that the data will be stored to
* @param position the position the data is at
* @throws UnrecoverableTimeoutException todo
* @throws StreamCorruptedException todo
*/
void setPositionForSequenceNumber(@NotNull ExcerptContext ec,
long sequenceNumber,
long position) throws StreamCorruptedException {
// only say for example index every 0,15,31st entry
if (!indexable(sequenceNumber)) {
return;
}
Wire wire = ec.wireForIndex();
Bytes> bytes = wire.bytes();
if (position > bytes.capacity())
throw new IllegalArgumentException("pos: " + position);
// find the index2index
final LongArrayValues index2indexArr = getIndex2index(wire);
if (((Byteable) index2indexArr).bytesStore() == null) {
assert false;
return;
}
int index2 = (int) ((sequenceNumber) >>> (indexCountBits + indexSpacingBits));
if (index2 >= indexCount) {
if (IGNORE_INDEXING_FAILURE) {
return;
}
throwNumEntriesExceededForRollCycle(sequenceNumber);
}
long secondaryAddress = getSecondaryAddress(wire, index2indexArr, index2);
if (secondaryAddress > bytes.capacity())
throwSecondaryAddressError(secondaryAddress);
bytes.readLimitToCapacity();
LongArrayValues indexValues = arrayForAddress(wire, secondaryAddress);
int index3 = (int) ((sequenceNumber >>> indexSpacingBits) & (indexCount - 1));
// check the last one first.
long posN = indexValues.getValueAt(index3);
if (posN == 0) {
indexValues.setValueAt(index3, position);
indexValues.setMaxUsed(index3 + 1L);
} else {
indexValues.setValueAt(index3, position);
return;
}
nextEntryToBeIndexed.setMaxValue(sequenceNumber + indexSpacing);
}
private void throwSecondaryAddressError(long secondaryAddress) {
throw new IllegalStateException("sa2: " + secondaryAddress);
}
private void throwNumEntriesExceededForRollCycle(long sequenceNumber) {
throw new IllegalStateException("Unable to index " + sequenceNumber + ", the number of entries exceeds max number for the current rollcycle");
}
@Override
public boolean indexable(long index) {
throwExceptionIfClosed();
return (index & (indexSpacing - 1)) == 0;
}
@Override
public long lastSequenceNumber(@NotNull ExcerptContext ec)
throws StreamCorruptedException {
throwExceptionIfClosed();
Sequence sequence1 = this.sequence;
if (sequence1 != null) {
for (int i = 0; i < 128; i++) {
long address = writePosition.getVolatileValue(0);
if (address == 0)
return -1;
long sequence = sequence1.getSequence(address);
if (sequence == Sequence.NOT_FOUND_RETRY)
continue;
if (sequence == Sequence.NOT_FOUND)
break;
try {
Wire wireForIndex = ec.wireForIndex();
return wireForIndex == null ? sequence : linearScanByPosition(wireForIndex, Long.MAX_VALUE, sequence, address, true);
} catch (EOFException e) {
throw new UncheckedIOException(e);
}
}
}
return sequenceForPosition(ec, Long.MAX_VALUE, false);
}
@Override
public int indexCount() {
return indexCount;
}
@Override
public int indexSpacing() {
return indexSpacing;
}
public long moveToEnd(final Wire wire) {
Sequence sequence1 = this.sequence;
if (sequence1 != null) {
for (int i = 0; i < 128; i++) {
long endAddress = writePosition.getVolatileValue(0);
if (endAddress == 0)
return -1;
long sequence = sequence1.getSequence(endAddress);
if (sequence == Sequence.NOT_FOUND_RETRY)
continue;
if (sequence == Sequence.NOT_FOUND)
return -1;
Bytes> bytes = wire.bytes();
if (wire.usePadding())
endAddress += BytesUtil.padOffset(endAddress);
bytes.readPosition(endAddress);
for (; ; ) {
int header = bytes.readVolatileInt(endAddress);
if (header == 0 || Wires.isNotComplete(header))
return sequence;
int len = Wires.lengthOf(header) + 4;
len += (int) BytesUtil.padOffset(len);
bytes.readSkip(len);
endAddress += len;
if (Wires.isData(header))
sequence += 1;
}
}
}
return -1;
}
@Override
public int linearScanCount() {
return linearScanCount;
}
@Override
public int linearScanByPositionCount() {
return linearScanByPositionCount;
}
enum IndexingFields implements WireKey {
indexCount, indexSpacing, index2Index,
lastIndex // NOTE: the nextEntryToBeIndexed
}
static class LongArrayValuesHolder {
private final LongArrayValues values;
private long address;
LongArrayValuesHolder(LongArrayValues values) {
this.values = values;
address = Long.MIN_VALUE;
}
public long address() {
return address;
}
public void address(long address) {
this.address = address;
}
public LongArrayValues values() {
return values;
}
}
}