Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2024 60East Technologies Inc., All Rights Reserved.
//
// This computer software is owned by 60East Technologies Inc. and is
// protected by U.S. copyright laws and other laws and by international
// treaties. This computer software is furnished by 60East Technologies
// Inc. pursuant to a written license agreement and may be used, copied,
// transmitted, and stored only in accordance with the terms of such
// license agreement and with the inclusion of the above copyright notice.
// This computer software or any other copies thereof may not be provided
// or otherwise made available to any other person.
//
// U.S. Government Restricted Rights. This computer software: (a) was
// developed at private expense and is in all respects the proprietary
// information of 60East Technologies Inc.; (b) was not developed with
// government funds; (c) is a trade secret of 60East Technologies Inc.
// for all purposes of the Freedom of Information Act; and (d) is a
// commercial item and thus, pursuant to Section 12.212 of the Federal
// Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
// Government's use, duplication or disclosure of the computer software
// is subject to the restrictions set forth by 60East Technologies Inc..
//
////////////////////////////////////////////////////////////////////////////
package com.crankuptheamps.client;
import com.crankuptheamps.client.BlockPublishStore.Block;
import com.crankuptheamps.client.exception.CommandException;
import com.crankuptheamps.client.exception.TimedOutException;
import com.crankuptheamps.client.fields.Field;
import com.crankuptheamps.client.fields.IntegerField;
import java.io.IOException;
import java.lang.Long;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
import java.util.zip.CRC32;
import com.crankuptheamps.client.exception.DisconnectedException;
import com.crankuptheamps.client.exception.StoreException;
import com.crankuptheamps.client.exception.PublishGapException;
/**
* BlockPublishStore is a base class for publish {@link Store} implementations.
* As messages are stored, space is allocated from a pre-created flat buffer. As
* messages are discarded, space in that buffer is marked "free" for future
* store operations. If messages are stored faster than they are published, the buffer
* is re-sized to create more capacity.
*/
public class BlockPublishStore implements Store
{
// This field represents the number of blocks per reallocation. It determines
// how many blocks are allocated when resizing the buffer to create more
// capacity
private int _blocksPerRealloc = 10000;
// Keeps track of the next sequence number to be used when storing messages
private volatile long _nextSequence = 1;
// A ReentrantLock instance used for synchronization purposes
private final Lock lock = new ReentrantLock();
// A Condition object associated with the lock used to signal when blocks are
// free for storage
private final Condition _blocksFree = lock.newCondition();
// A Condition object associated with the lock used to signal when messages are
// ready for processing
private final Condition _messageReady = lock.newCondition();
// boolean flag that indicates whether the buffer is currently being resized
private volatile boolean _resizing = false;
protected PublishStoreResizeHandler _resizeHandler = null;
/**
* A simple wrapper object around a byte array that allows a sub-range to be
* specified using its offset and length properties.
*/
public static class ByteSequence {
public byte[] array;
public long offset, len;
// This constructor initializes a 'ByteSequence' with null array and zero offset
// and length
public ByteSequence()
{
this.array = null;
this.offset = this.len = 0;
}
/**
* Initializes a new instance of the 'ByteSequence' class with specified array,
* offset, and length values.
*
* @param array The byte array.
* @param offset The offset within the byte array.
* @param len The length of the byte sequence.
*/
public ByteSequence(byte[] array, long offset, long len)
{
this.array = array;
this.offset = offset;
this.len = len;
}
}
/**
* Interface which is used to hold the BlockPublishStore
* buffer data.
*/
public interface Buffer extends AutoCloseable
{
// Returns the size of the buffer
public long getSize() throws IOException;
// Sets the size of the buffer
public void setSize(long newSize) throws IOException;
// Returns the current position in the buffer
public long getPosition() throws IOException;
// Sets the current position in the buffer
public void setPosition(long position) throws IOException;
// Writes a byte to the buffer
public void putByte(byte b) throws IOException;
// Reads a byte from the buffer
public byte getByte() throws IOException;
// Writes an integer to the buffer
public void putInt(int i) throws IOException;
// Writes an integer to a specified position in the buffer
public void putInt(long position, int i) throws IOException;
// Reads an integer from the buffer
public int getInt() throws IOException;
// Reads an integer from a specified position in the buffer
public int getInt(long position) throws IOException;
// Writes a long to the buffer
public void putLong(long l) throws IOException;
// Writes a long to a specified position in the buffer
public void putLong(long position, long l) throws IOException;
// Reads a long from the buffer
public long getLong() throws IOException;
// Writes a ByteSequence to the buffer
public void putBytes(ByteSequence bytes) throws IOException;
// Reads a ByteSequence from the buffer
public void getBytes(ByteSequence outBytes) throws IOException;
// Sets a range of bytes to zero in the buffer
public void zero(long offset, int length) throws IOException;
// Writes a byte array to the buffer
public void putBytes(byte[] buffer, long offset, long length) throws IOException;
// Reads data into a Field object from the buffer
public void getBytes(Field outField, int length) throws IOException;
}
// This nested class represents a block in the buffer. It is used for managing
// the storage of messages
static class Block {
// An integer representing the index of the block
public int index;
// A reference to the next block in the chain
public Block nextInChain;
// A reference to the next block in the list
public Block nextInList;
// A long representing the sequence number associated with the block
public long sequenceNumber;
// A constant integer indicating the size of a block
public static final int SIZE = 2048; // finely honed
// A constant integer representing the size of the block header
public static final int BLOCK_HEADER_SIZE = 32;
// A constant integer representing the size of block data
public static final int BLOCK_DATA_SIZE = Block.SIZE - BLOCK_HEADER_SIZE;
// A constant integer representing the chain header size
public static final int CHAIN_HEADER = 32;
// Position of the CRC value
public static final int BLOCK_CRC_POSITION = 16;
// Position of the ready flag, non-zero is ready
public static final int BLOCK_READY_POSITION = 24;
// Initializes a Block with the specified index
public Block(int index) { this.index = index; }
// Returns the position of the block within the buffer
public long getPosition() { return (long) SIZE * index; }
// Provides a string representation of the block, including its index and
// sequence number
public String toString()
{
return String.format("block %d sequenceNumber %d", index, sequenceNumber);
}
}
// A buffer acts as a random access file -- mmap, memory only, and file
// implementations exist.
protected Buffer _buffer;
// _freeList is the list of free blocks in the file. _usedList is the list of
// the used head blocks. Used non-head blocks are only kept alive by "chain"
// references from the head block. Used list is ordered where the start is the
// LEAST recently used.
volatile Block _freeList = null;
volatile Block _usedList = null;
volatile Block _endOfUsedList = null;
// The metadata format is currently:
// int size - unused
// int nextInChain - set to the int of the version number, such as 4000100 for
// 4.0.1.0
// long sequence - set to the index of the last discarded
Block _metadataBlock = null;
// Save this so we're not GCing stuff.
CRC32 _crc = null;
// We use this _argument thing since Java doesn't have output parameters, but we
// sort of need them.
ByteSequence _argument = new ByteSequence();
// We use this to hold everything we read from the buffer for replay
Message _message;
// Determines if discardUpTo will throw
protected boolean _errorOnPublishGap = false;
// When a message spans multiple blocks we need to reassemble it
// into one place before replaying. We use this buffer to do so,
// allocating on demand.
byte[] _readBuffer;
// Write assembly area
ArrayStoreBuffer _sb = null;
protected static final int AMPS_MIN_PUB_STORE_DISCARDED_VERSION = 4000100;
protected static final int METADATA_VERSION_LOCATION = 4;
protected static final int METADATA_LAST_DISCARDED_LOCATION = 8;
/**
* Protected constructor for BlockPublishStore.
* This constructor initializes the BlockPublishStore with a specified buffer
* and blocksPerRealloc
*
* @param buffer An instance of the Buffer interface representing the
* data buffer.
* @param blocksPerRealloc An integer representing the number of blocks per
* reallocation.
* @param isAFile A boolean indicating whether this store represents a
* file.
*/
protected BlockPublishStore(Buffer buffer, int blocksPerRealloc, boolean isAFile)
{
this._blocksPerRealloc = blocksPerRealloc;
this._buffer = buffer;
if (isAFile)
{
this._crc = new CRC32();
this._sb = new ArrayStoreBuffer();
try {
this._sb.setSize(Block.SIZE);
} catch (IOException e)
{
// If there's an IOException, set _sb to null
this._sb = null;
}
}
}
/**
* Protected constructor for BlockPublishStore.
* This constructor is a convenience method that calls the first constructor
* with isAFile set to false
*
* @param buffer An instance of the Buffer interface representing the
* data buffer.
* @param blocksPerRealloc An integer representing the number of blocks per
* reallocation.
*/
protected BlockPublishStore(Buffer buffer, int blocksPerRealloc)
{
this(buffer, blocksPerRealloc, false);
}
/**
* Protected constructor for BlockPublishStore.
* This constructor is a convenience method that calls the second constructor
* with default blocksPerRealloc (10000) and isAFile (false).
* @param buffer An instance of the Buffer interface representing the data
* buffer.
*/
protected BlockPublishStore(Buffer buffer)
{
this(buffer, 10000, false);
}
/**
* Stores a message in the BlockPublishStore.
* @param m The message to be stored.
* @throws StoreException If an error occurs during message storage.
*/
public void store(Message m) throws StoreException
{
store(m, true);
}
/**
* Closes the BlockPublishStore.
*
* @throws Exception If an error occurs during the closing operation.
*/
public void close() throws Exception
{
_buffer.close();
}
/**
* Flattens a chain of blocks and moves them to the free list for reuse.
* @param toRemove The Block object to be removed and flattened to the free
* list.
*/
private void flattenToFreeList(Block toRemove)
{
Block currentInChain = toRemove;
lock.lock();
try
{
if (toRemove == _usedList) {
_usedList = _usedList.nextInList;
} else {
Block prev = _usedList;
for (Block b = prev.nextInList; b != null; b = b.nextInList) {
if (b == toRemove) {
prev.nextInList = b.nextInList;
break;
}
prev = prev.nextInList;
}
}
while (currentInChain != null)
{
Block nextInChain = currentInChain.nextInChain;
try {
_buffer.zero(currentInChain.getPosition(),
Block.BLOCK_HEADER_SIZE);
} catch (IOException ex) {
// Going to ignore this, since we here because of an error
}
currentInChain.sequenceNumber = 0;
currentInChain.nextInChain = null;
// and, flatten this chain onto the zeroed out list.
currentInChain.nextInList = _freeList;
_freeList = currentInChain;
currentInChain = nextInChain;
}
} finally {
lock.unlock();
}
}
/**
* Protected utility method for storing messages in the BlockPublishStore.
* @param m The message to be stored.
* @param assignSequence A boolean flag indicating whether to assign a sequence
* number to the message.
* @throws StoreException If an error occurs during message storage.
*/
protected void store(Message m, boolean assignSequence)
throws StoreException
{
// Get the command type of the message (e.g., Publish, SOWDelete)
int operation = m.getCommand();
// Initialize the 'flag' to -1 (a default value)
int flag = -1;
// Initialize 'data' as null; it will hold the message data
Field data = null;
// Check the type of operation and set 'data' and 'flag' accordingly
if (operation == Message.Command.Publish ||
operation == Message.Command.DeltaPublish ||
(operation == Message.Command.SOWDelete && !m.isDataNull()))
{
data = m.getDataRaw();
if (operation == Message.Command.SOWDelete)
flag = Store.SOWDeleteByData;
} else if (!m.isFilterNull()) {
data = m.getFilterRaw();
flag = Store.SOWDeleteByFilter;
} else if (!m.isSowKeysNull()) {
data = m.getSowKeysRaw();
flag = Store.SOWDeleteByKeys;
} else if (!m.isBookmarkNull()) {
if (m.isOptionsNull() || !m.getOptions().contains("cancel")) {
data = m.getBookmarkRaw();
flag = Store.SOWDeleteByBookmark;
} else {
data = m.getBookmarkRaw();
flag = Store.SOWDeleteByBookmarkCancel;
}
} else {
// Throw an exception if the message has no valid data, filter, sow keys, or
// bookmark
throw new StoreException("Cannot store a Message with no data, filter, sow keys, or bookmark.");
}
// Declare Block objects for current, next, and the first block
Block current, next, first = null;
// Get fields related to message properties
Field cmdId = m.getCommandIdRaw();
long commandIdRemaining = cmdId == null ? 0 : cmdId.length;
Field correlationId = m.getCorrelationIdRaw();
long correlationIdRemaining = correlationId == null ? 0 : correlationId.length;
Field expiration = m.getExpirationRaw();
long expirationRemaining = expiration == null ? 0 : expiration.length;
Field sowKey = m.getSowKeyRaw();
long sowKeyRemaining = sowKey == null ? 0 : sowKey.length;
Field topic = m.getTopicRaw();
long topicRemaining = topic.length;
long dataRemaining = data.length;
// Nothing to write if this is a Unknown command, which is a Queue ack
// Calculate the total remaining bytes to write
long totalRemaining = ((operation == Message.Command.Unknown) ? 0
: (Block.CHAIN_HEADER + commandIdRemaining +
correlationIdRemaining + expirationRemaining +
sowKeyRemaining + topicRemaining + dataRemaining));
// Calculate the length of the last block
int lastBlockLength = ((operation == Message.Command.Unknown) ? 0
: ((int) (totalRemaining % Block.BLOCK_DATA_SIZE)));
// Calculate the number of blocks to write
int blocksToWrite = ((operation == Message.Command.Unknown) ? 0
: ((int) ((totalRemaining / Block.BLOCK_DATA_SIZE) +
(lastBlockLength > 0 ? 1 : 0))));
// Initialize CRC value
long crcVal = 0;
try {
// First block gets sequence assigned
// Assign a sequence number to the first block if required
long currentSequence = (assignSequence ? 0 : m.getSequence());
first = next = get(assignSequence, currentSequence);
// Check if there is space to allocate a block; throw an exception if not
if (first == null)
{
throw new StoreException("The store is full, and no blocks can be allocated.");
}
// Assign the sequence number to the message if required
if (assignSequence)
m.setSequence(first.sequenceNumber);
boolean loopComplete = false;
// Loop through the blocks to write
while (blocksToWrite > 0)
{
loopComplete = false;
current = next;
int bytesRemainingInBlock = Block.BLOCK_DATA_SIZE;
// Allocate a new block if more than one block needs to be written
if (blocksToWrite > 1)
{
next = get(false, 0);
if (next == null)
{
// If no additional block can be allocated, flatten the list and throw an
// exception
flattenToFreeList(first);
throw new StoreException("The store is full, and no additional blocks can be allocated.");
}
} else
{
next = null;
}
current.nextInChain = next;
// Write block information into the buffer
Buffer buffer = _sb == null ? _buffer : _sb;
lock.lock();
try {
long start = _sb == null ? current.getPosition() : 0;
buffer.setPosition(start);
if (current == first)
{
buffer.putInt((int) totalRemaining);
} else
{
buffer.putInt(next == null ? lastBlockLength : Block.BLOCK_DATA_SIZE);
}
buffer.putInt(next == null ? current.index : next.index);
buffer.putLong(current == first ? m.getSequence() : 0);
// Calculate CRC value if needed (CRC is disabled when not writing to disk)
if (_crc != null)
{
_crc.reset();
if (cmdId != null && commandIdRemaining > 0)
_crc.update(cmdId.buffer,
(int) cmdId.position,
(int) commandIdRemaining);
if (correlationId != null && correlationIdRemaining > 0)
_crc.update(correlationId.buffer,
(int) correlationId.position,
(int) correlationIdRemaining);
if (expiration != null && expirationRemaining > 0)
_crc.update(expiration.buffer,
(int) expiration.position,
(int) expirationRemaining);
if (sowKey != null && sowKeyRemaining > 0)
_crc.update(sowKey.buffer,
(int) sowKey.position,
(int) sowKeyRemaining);
if (topicRemaining > 0)
_crc.update(topic.buffer,
(int) topic.position,
(int) topicRemaining);
if (dataRemaining > 0)
_crc.update(data.buffer,
(int) data.position,
(int) dataRemaining);
crcVal = _crc.getValue();
}
buffer.putLong(crcVal);
// Write data, topic, and other content
buffer.setPosition(start + Block.BLOCK_HEADER_SIZE);
if (current == first)
{
buffer.putInt((int) operation);
buffer.putInt((int) commandIdRemaining);
buffer.putInt((int) correlationIdRemaining);
buffer.putInt((int) expirationRemaining);
buffer.putInt((int) sowKeyRemaining);
buffer.putInt((int) topicRemaining);
buffer.putInt(flag);
buffer.putInt((int) m.getAckTypeOutgoing());
bytesRemainingInBlock -= Block.CHAIN_HEADER;
}
// Write remaining fields into the block
long bytesToWrite = commandIdRemaining <= bytesRemainingInBlock ? commandIdRemaining
: bytesRemainingInBlock;
if (bytesToWrite > 0)
{
Field rawField = m.getCommandIdRaw();
buffer.putBytes(rawField.buffer,
rawField.position + rawField.length -
commandIdRemaining,
bytesToWrite);
bytesRemainingInBlock -= bytesToWrite;
commandIdRemaining -= bytesToWrite;
}
bytesToWrite = correlationIdRemaining <= bytesRemainingInBlock ? correlationIdRemaining
: bytesRemainingInBlock;
if (bytesToWrite > 0)
{
Field rawField = m.getCorrelationIdRaw();
buffer.putBytes(rawField.buffer,
rawField.position + rawField.length -
correlationIdRemaining,
bytesToWrite);
bytesRemainingInBlock -= bytesToWrite;
correlationIdRemaining -= bytesToWrite;
}
bytesToWrite = expirationRemaining <= bytesRemainingInBlock ? expirationRemaining
: bytesRemainingInBlock;
if (bytesToWrite > 0)
{
Field rawField = m.getExpirationRaw();
buffer.putBytes(rawField.buffer,
rawField.position + rawField.length -
expirationRemaining,
bytesToWrite);
bytesRemainingInBlock -= bytesToWrite;
expirationRemaining -= bytesToWrite;
}
bytesToWrite = sowKeyRemaining <= bytesRemainingInBlock ? sowKeyRemaining : bytesRemainingInBlock;
if (bytesToWrite > 0)
{
Field rawField = m.getSowKeyRaw();
buffer.putBytes(rawField.buffer,
rawField.position + rawField.length -
sowKeyRemaining,
bytesToWrite);
bytesRemainingInBlock -= bytesToWrite;
sowKeyRemaining -= bytesToWrite;
}
bytesToWrite = topicRemaining <= bytesRemainingInBlock ? topicRemaining : bytesRemainingInBlock;
if (bytesToWrite > 0)
{
Field rawField = m.getTopicRaw();
buffer.putBytes(rawField.buffer,
rawField.position + rawField.length -
topicRemaining,
bytesToWrite);
bytesRemainingInBlock -= bytesToWrite;
topicRemaining -= bytesToWrite;
}
bytesToWrite = dataRemaining <= bytesRemainingInBlock ? dataRemaining : bytesRemainingInBlock;
if (bytesToWrite > 0)
{
buffer.putBytes(data.buffer,
data.position + data.length -
dataRemaining,
bytesToWrite);
dataRemaining -= bytesToWrite;
}
// If we've used sb as an assembly area, here's where we actually write to the
// file.
if (_sb != null)
{
_buffer.setPosition(current.getPosition());
_argument.array = _sb.getBuffer();
_argument.offset = 0;
_argument.len = _sb.getPosition();
_buffer.putBytes(_argument);
}
loopComplete = true;
} finally {
lock.unlock();
if (!loopComplete) {
// If the loop is not completed, flatten the list and set 'first' to null
flattenToFreeList(first);
first = null;
}
}
--blocksToWrite;
}
// Now, there should be no data or topic left to write.
assert (dataRemaining == 0 && topicRemaining == 0);
// Lock and signal that the message is ready
lock.lock();
try // Done, signal the ready flag
{
_buffer.putInt(first.getPosition() + Block.BLOCK_READY_POSITION,
1);
_messageReady.signalAll();
} finally {
lock.unlock();
}
} catch (IOException e) {
// If an IOException occurs, flatten the list and throw a StoreException
if (first != null)flattenToFreeList(first);
throw new StoreException(e);
}
}
/**
* This method is responsible for managing the discarding of blocks in a store
* based on the specified index. It handles locking, updating metadata, and
* organizing the blocks for efficient reuse.
*/
public void discardUpTo(long index) throws StoreException
{
// If we're empty or already past this value, just return
if (index == 0 || _usedList == null ||
index <= _metadataBlock.sequenceNumber)
{
lock.lock();
try {
if (_errorOnPublishGap && index < _metadataBlock.sequenceNumber
&& index > 0) {
throw new PublishGapException("Connection to current server could cause a message gap as server is only up to " + index + " but Store is already to " + _metadataBlock.sequenceNumber);
}
// If the last persisted index is less than the specified index, update it
if (_getLastPersisted() < index) {
_metadataBlock.sequenceNumber = index;
// Position plus size of two ints
// Update the metadata with the new sequence number
_buffer.putLong(_metadataBlock.getPosition() +
METADATA_LAST_DISCARDED_LOCATION,
_metadataBlock.sequenceNumber);
}
if (_nextSequence <= index)
_nextSequence = index + 1;
} catch (IOException ex) {
throw new StoreException("Error saving last discarded: " + ex,
ex);
} finally {
lock.unlock();
}
return;
}
// Set the metadata block's sequence number to the specified index
_metadataBlock.sequenceNumber = index;
// First, find the beginning of where we should discard
Block almostFreeList = detach(index);
// We no longer need to keep a lock. The only funny thing about our state is
// that there are "missing" entries -- some on _usedList, some on _freeList, and
// some on our (local) almostFreeList, about to be freed. We could be fancy
// and enqueue them for freeing, not going to do that.
// For each one of these guys on our list, flatten onto the
// new "zeroedOutList" and remember the last entry. We'll use
// that to efficiently prepend, below.
Block zeroedOutList = null;
Block lastOnTheList = almostFreeList;
try
{
// Clear out the muck in the byte buffer
Block current = almostFreeList;
while (current != null) {
Block next = current.nextInList;
Block currentInChain = current;
while (currentInChain != null) {
Block nextInChain = currentInChain.nextInChain;
_buffer.zero(currentInChain.getPosition(),
Block.BLOCK_HEADER_SIZE);
currentInChain.sequenceNumber = 0;
currentInChain.nextInChain = null;
// And, flatten this chain onto the zeroed out list.
currentInChain.nextInList = zeroedOutList;
zeroedOutList = currentInChain;
currentInChain = nextInChain;
}
current = next;
}
} catch (IOException ioex)
{
throw new StoreException(ioex);
}
// now zeroedOutList is flat and free. We can just prepend it to the free list.
lock.lock();
try
{
_metadataBlock.sequenceNumber = index;
try {
// Position plus size of two ints
// Update the metadata with the new sequence number
_buffer.putLong(_metadataBlock.getPosition() +
METADATA_LAST_DISCARDED_LOCATION,
_metadataBlock.sequenceNumber);
} catch (IOException ex) {
throw new StoreException("Error saving last discarded: " + ex, ex);
}
if (lastOnTheList != null) {
lastOnTheList.nextInList = _freeList;
_freeList = zeroedOutList;
}
// Signal that blocks are free
_blocksFree.signalAll();
}
finally {
lock.unlock();
}
}
/**
* Gets the last persisted sequence number.
*
* @return The last persisted sequence number.
* @throws StoreException If an error occurs while retrieving the last persisted
* sequence number.
*/
public long getLastPersisted() throws StoreException {
lock.lock();
try {
return _getLastPersisted();
} finally {
lock.unlock();
}
}
// Lock should already be held. Gets the last persisted sequence number
private long _getLastPersisted() throws StoreException {
if (_metadataBlock.sequenceNumber != 0) {
return _metadataBlock.sequenceNumber;
}
if (_nextSequence != 1) {
long minSeq = _getLowestUnpersisted();
long maxSeq = _getHighestUnpersisted();
if (minSeq != -1)
_metadataBlock.sequenceNumber = minSeq - 1;
if (maxSeq != -1 && _nextSequence <= maxSeq)
_nextSequence = maxSeq + 1;
if (_nextSequence < _metadataBlock.sequenceNumber)
_metadataBlock.sequenceNumber = _nextSequence - 1;
} else {
_metadataBlock.sequenceNumber = System.currentTimeMillis() * 1000000;
_nextSequence = _metadataBlock.sequenceNumber + 1;
}
try {
// Position plus size of two ints
_buffer.putLong(_metadataBlock.getPosition() +
METADATA_LAST_DISCARDED_LOCATION,
_metadataBlock.sequenceNumber);
} catch (IOException ex) {
throw new StoreException("Error saving last discarded: " + ex, ex);
}
return _metadataBlock.sequenceNumber;
}
/**
* Gets the lowest unpersisted sequence number.
*
* @return The lowest unpersisted sequence number or -1 if no sequence numbers
* are unpersisted.
*/
public long getLowestUnpersisted() {
lock.lock();
try {
return _getLowestUnpersisted();
} finally {
lock.unlock();
}
}
// Lock should already be held. Gets the lowest unpersisted sequence number
private long _getLowestUnpersisted() {
if (_usedList != null) {
return _usedList.sequenceNumber;
}
return -1;
}
/**
* Gets the highest unpersisted sequence number.
*
* @return The highest unpersisted sequence number or -1 if no sequence numbers
* are unpersisted.
*/
public long getHighestUnpersisted() {
lock.lock();
try {
return _getHighestUnpersisted();
} finally {
lock.unlock();
}
}
// Lock should already be held. Gets the highest unpersisted sequence number
private long _getHighestUnpersisted() {
if (_endOfUsedList != null) {
return _endOfUsedList.sequenceNumber;
}
return -1;
}
/**
* Sets the message to be stored.
*
* @param m The message to be stored.
*/
public void setMessage(Message m) {
_message = m;
}
/**
* Get whether the Store will throw a PublishGapException from discardUpTo if the
* sequence number being discarded is less then the current last persisted.
* @return If true, an exception will be thrown during logon if a gap could be
* created. If false, logon is allowed to proceed.
*/
public boolean getErrorOnPublishGap()
{
return _errorOnPublishGap;
}
/**
* Set whether the Store should throw a PublishGapException from discardUpTo if the
* sequence number being discarded is less then the current last persisted. This can
* occur if the client fails over to a server that has not been synchronously replicated
* to by its previous server, which lead to the new server having a gap in messages from
* this client.
* @param errorOnGap If true, an exception will be thrown during logon if a gap could be
* created. If false, allow the logon to proceed.
*/
public void setErrorOnPublishGap(boolean errorOnGap)
{
_errorOnPublishGap = errorOnGap;
}
// Detaches blocks from the list up to a specified index
// The detach method is used to remove blocks from a list (_usedList) up to a
// specified index. It iterates through the list, identifying blocks that should
// be discarded based on their sequenceNumber compared to the given index. The
// method then detaches these blocks from the list, updates the list of used
// blocks, and returns the detached blocks that should be discarded. This
// process ensures efficient management of blocks in the list for storage
// purposes.
private Block detach(long index) {
// Initialize variables to keep track of different lists of blocks
Block keepList = null, endOfDiscardList = null, discardList = null;
// Acquire a lock to ensure thread safety while detaching blocks
lock.lock();
try {
// Advance through the list, find the first one to keep.
// We keep everything after that point, and discard everything up
// to that point.
// Start with the current used list
keepList = _usedList;
// Iterate through the list of blocks (usedList)
while (keepList != null && keepList.sequenceNumber <= index) {
// If a block should be discarded, add it to the discard list
if (discardList == null) {
discardList = keepList;
}
// Update the last block to discard
endOfDiscardList = keepList;
// Move to the next block in the list
keepList = keepList.nextInList;
}
// Detach the remainder of the list by breaking the link to the discarded
// blocks, and make it the new usedList.
if (endOfDiscardList != null) {
endOfDiscardList.nextInList = null;
}
// Update the used list to contain only the blocks that should be kept
_usedList = keepList;
// If no blocks remain in the used list, update the end of the used list
if (keepList == null) {
_endOfUsedList = null;
}
} finally {
// Release the lock to allow other threads to access the list
lock.unlock();
}
// Return the detached blocks that should be discarded
return discardList;
}
/**
* Replays a single message onto a store replayer.
*
* @param b The first block of the message.
* @param replayer The replayer to play this message on.
* @return Success returns true, false if not ready or failure.
* @throws IOException If an I/O error occurs.
* @throws DisconnectedException If a disconnection occurs.
*/
private void replayOnto(Block b, StoreReplayer replayer)
throws IOException, DisconnectedException {
// Lock is already held
// Clear fields we use
// If message isn't set by a Client, then it must not matter what type
if (_message == null)
_message = new JSONMessage(StandardCharsets.UTF_8.newEncoder(),
StandardCharsets.UTF_8.newDecoder());
_message.reset();
// Get the position of the block in the buffer
long bPosition = b.getPosition();
_buffer.setPosition(bPosition);
// Get the total length of the message (excluding headers)
int totalLength = _buffer.getInt() - Block.CHAIN_HEADER;
// Can occur if replaying during disconnect or for queue ack messages
if (totalLength <= 0) {
return; // this can occur when replaying during a disconnect scenario
}
// Don't care about the next in chain -- we already read that
_buffer.getInt();
_message.setSequence(_buffer.getLong());
_buffer.getLong(); // Skip CRC until ready
// Wait until the message is ready for replay (block is marked as ready)
while (_buffer.getInt(bPosition + Block.BLOCK_READY_POSITION) == 0) {
// An interrupted exception is ignored to recheck
try {
_messageReady.await(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
}
// Set the buffer position to read the CRC value
_buffer.setPosition(bPosition + Block.BLOCK_CRC_POSITION);
long crcVal = _buffer.getLong();
// Grab the operation, lengths, flag, and ack type from the block header
_buffer.setPosition(bPosition + Block.BLOCK_HEADER_SIZE);
int operation = _buffer.getInt();
if (operation == Message.Command.Unknown)
return;
_message.setCommand(operation);
int commandIdLength = _buffer.getInt();
int correlationIdLength = _buffer.getInt();
int expirationLength = _buffer.getInt();
int sowKeyLength = _buffer.getInt();
int topicLength = _buffer.getInt();
int flag = _buffer.getInt();
_message.setAckType(_buffer.getInt());
// Set the buffer position to read message data
_buffer.setPosition(bPosition + Block.BLOCK_HEADER_SIZE +
Block.CHAIN_HEADER);
byte[] array = null;
int offset = 0;
if (b.nextInChain == null) {
// Everything fits into one block. replay directly from the block memory.
_argument.len = totalLength;
_buffer.getBytes(_argument);
array = _argument.array;
offset = (int) _argument.offset;
} else {
// Discontinuous data; we'll do this the hard way.
// Allocate enough space to hold it contiguously. then, copy the pieces
// in place, then call the replayer.
if (_readBuffer == null || _readBuffer.length < totalLength) {
_readBuffer = new byte[totalLength];
}
int position = 0;
for (Block part = b; part != null; part = part.nextInChain) {
int maxReadable = Block.BLOCK_DATA_SIZE;
if (b != part) {
_buffer.setPosition(part.getPosition() + Block.BLOCK_HEADER_SIZE);
} else // read chain and extended metadata
{
maxReadable -= Block.CHAIN_HEADER;
}
int readLen = totalLength - position > maxReadable ? maxReadable : totalLength - position;
_argument.len = readLen;
try {
_buffer.getBytes(_argument);
} catch (Exception e) {
throw new IOException("Corrupted message found.", e);
}
// Copy the data to the read buffer
System.arraycopy(_argument.array, (int) _argument.offset, _readBuffer, position, readLen);
position += readLen;
}
// Ensure that the total length matches the position (data integrity check)
if (position != totalLength) {
throw new IOException(String.format("Corrupted message found. Expected %d bytes, read %d bytes.",
totalLength, position));
}
array = _readBuffer;
offset = 0;
}
if (_crc != null) {
_crc.reset();
try {
_crc.update(array, offset, totalLength);
} catch (Exception e) {
throw new IOException("Corrupted message found.", e);
}
if (_crc.getValue() != crcVal) {
StringBuilder sb = new StringBuilder();
for (Block part = b; part != null; part = part.nextInChain) {
sb.append(part);
sb.append('\n');
}
String message = String
.format("Corrupted message found. Block index %d, sequence %d, topic length %d, expected CRC %d, actual CRC %d, Block list: %s",
b.index, b.sequenceNumber, topicLength,
crcVal, _crc.getValue(),
sb.toString());
throw new IOException(message);
}
}
if (commandIdLength > 0) {
_message.setCommandId(array, offset, commandIdLength);
totalLength -= commandIdLength;
offset += commandIdLength;
}
if (correlationIdLength > 0) {
_message.getCorrelationIdRaw().set(array, offset, correlationIdLength);
totalLength -= correlationIdLength;
offset += correlationIdLength;
}
if (expirationLength > 0) {
_message.getExpirationRaw().set(array, offset, expirationLength);
totalLength -= expirationLength;
offset += expirationLength;
}
if (sowKeyLength > 0) {
_message.setSowKey(array, offset, sowKeyLength);
totalLength -= sowKeyLength;
offset += sowKeyLength;
}
if (topicLength > 0) {
_message.setTopic(array, offset, topicLength);
totalLength -= topicLength;
offset += topicLength;
}
// Set various message fields based on the operation and flag
if (operation == Message.Command.Publish ||
operation == Message.Command.DeltaPublish) {
_message.setData(array, offset, totalLength);
replayer.execute(_message);
} else if (operation == Message.Command.SOWDelete) {
switch (flag) {
case Store.SOWDeleteByData: {
_message.setData(array, offset, totalLength);
replayer.execute(_message);
}
break;
case Store.SOWDeleteByFilter: {
_message.setFilter(array, offset, totalLength);
replayer.execute(_message);
}
break;
case Store.SOWDeleteByKeys: {
_message.setSowKeys(array, offset, totalLength);
replayer.execute(_message);
}
break;
case Store.SOWDeleteByBookmark: {
_message.setBookmark(array, offset, totalLength);
replayer.execute(_message);
}
break;
case Store.SOWDeleteByBookmarkCancel: {
_message.setBookmark(array, offset, totalLength).setOptions("cancel");
replayer.execute(_message);
}
break;
default: {
String message = String.format(
"SOWDelete message with invalid flag found. Block index %d, sequence %d, topic length %d, expected data length %d, operation %d, flag %d",
b.index, b.sequenceNumber, topicLength,
totalLength, operation, flag);
throw new IOException(message);
}
// break;
}
} else {
String message = String.format(
"Message with invalid operation found. Block index %d, sequence %d, topic length %d, expected data length %d, operation %d, flag %d",
b.index, b.sequenceNumber, topicLength,
totalLength, operation, flag);
throw new IOException(message);
}
}
/**
* Replays stored messages onto a store replayer. It processes each block in the
* list of used blocks, replaying them onto the provided replayer.
*
* @param replayer The replayer to play messages on.
* @throws StoreException If an exception occurs during replay, the
* method will throw a StoreException, and all
* following blocks will be ignored.
* @throws DisconnectedException If a disconnection occurs during replay.
*/
public void replay(StoreReplayer replayer) throws StoreException, DisconnectedException {
lock.lock();
try {
// If there are no used blocks, there's nothing to replay
if (_usedList == null)
return;
boolean corrupted = false;
StoreException ex = null;
Block lastGood = null;
Block nextInList = null;
// for each used block
for (Block b = _usedList; b != null; b = nextInList) {
nextInList = b.nextInList;
if (!corrupted) {
try {
if (_buffer.getInt(b.getPosition() +
Block.BLOCK_READY_POSITION) == 0) {
// Replay needs to stop here
break;
}
replayOnto(b, replayer);
lastGood = b;
} catch (IOException ioex) {
corrupted = true;
_endOfUsedList = lastGood == null ? _usedList : lastGood;
ex = new StoreException("Exception during replay, ignoring all following blocks", ioex);
}
}
// Can't just do an else here, need to deal with first corrupted block
if (corrupted) // We're corrupt, let's clean up bad blocks
{
Block currentInChain = b;
while (currentInChain != null) {
Block nextInChain = currentInChain.nextInChain;
try {
_buffer.zero(currentInChain.getPosition(),
Block.BLOCK_HEADER_SIZE);
} catch (IOException e) {
} // ignore
currentInChain.sequenceNumber = 0;
currentInChain.nextInChain = null;
// and, flatten this chain onto the _freeList
currentInChain.nextInList = _freeList;
_freeList = currentInChain;
currentInChain = nextInChain;
}
}
}
if (corrupted)
{
throw ex;
}
}
finally {
// If there are no used blocks, there's nothing to replay
lock.unlock();
}
}
/**
* Replays a single message from the store, given a specific sequence index,
* onto a store replayer.
* If the specified index is less than or equal to the current sequence number,
* the method will create a message with the correct sequence number and return
* true. Otherwise, it searches for the block corresponding to the provided index
* in the list of used blocks, replays the message, and returns true if successful.
*
* @param replayer The replayer to play the message on.
* @param index The sequence index of the message to replay.
* @return True if the message was successfully replayed or created; false
* otherwise.
* @throws StoreException If an exception occurs during replay, the
* method will throw a StoreException.
* @throws DisconnectedException If a disconnection occurs during replay.
*/
public boolean replaySingle(StoreReplayer replayer, long index)
throws StoreException, DisconnectedException {
lock.lock();
try {
// If the specified index is less than or equal to the current sequence number,
// create a message with the correct sequence number and return true.
if (index <= _metadataBlock.sequenceNumber) {
return true;
}
// Find the first one that equals this index.
for(Block b = this._usedList; b != null; b = b.nextInList)
{
// Continue to check sequenceNumber which can get reset due to
// an ack or if store() failed for some reason.
while (b.sequenceNumber == index &&
(_buffer.getInt(b.getPosition() + Block.BLOCK_READY_POSITION) == 0))
{
// An interrupted exception is ignored to recheck
try {
_messageReady.await(1000, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e) { }
}
if (b.sequenceNumber == index)
{
_buffer.setPosition(b.getPosition());
if (_buffer.getInt() <= 0)
return false;
// Replay the message onto the replayer
replayOnto(b, replayer);
return true;
}
else if (b.sequenceNumber > index) // too far
{
break;
}
}
return false;
} catch (IOException ioex) {
throw new StoreException(ioex);
} finally {
lock.unlock();
}
}
/**
* Returns the count of unpersisted messages in the store. This count represents
* the number of messages that have been received but not yet persisted.
*
* @return The count of unpersisted messages.
*/
public long unpersistedCount() {
lock.lock();
try {
if (_usedList == null)
return 0;
// Calculate the count of unpersisted messages based on the difference between
// the sequence numbers of the last and first used blocks, inclusive.
return _endOfUsedList.sequenceNumber - _usedList.sequenceNumber + 1;
} finally {
lock.unlock();
}
}
/**
* Finds or creates a Block object with the specified index from a provided map
* of Blocks. If a Block with the given index already exists in the map, it is
* returned. Otherwise, a new Block is created, added to the map, and returned.
*
* @param index The index of the Block to find or create.
* @param allBlocks A map containing Block objects where the index is used as
* the key.
* @return The Block object with the specified index.
*/
Block findOrCreate(int index, Map allBlocks) {
if (allBlocks.containsKey(index)) {
return allBlocks.get(index);
} else {
Block b = new Block((int) index);
allBlocks.put(index, b);
return b;
}
}
/**
* Recovers the store's state during initialization. This method parses the
* store's buffer, reconstructs the used and free lists of blocks, and updates
* the metadata block accordingly.
*
* @throws StoreException If an error occurs during store recovery.
*/
protected void recover() throws StoreException {
lock.lock();
try {
_usedList = _freeList = null;
// Start here and work the buffer.
// Create data structures to hold block information during recovery.
HashMap allBlocks = new HashMap(); // // A map to store block objects by
// file index
TreeMap heads = new TreeMap(); // A map to store block objects by sequence number
try {
long end = _buffer.getSize();
if (end == 0)
// If the buffer is empty, return as there's nothing to recover
return;
long minSeq = 0;
long maxSeq = 0;
// Iterate through the store's buffer to recover block informa
for (long offset = 0; offset < end; offset += Block.SIZE) {
// Calculate the block index based on the offset
int index = (int) (offset / Block.SIZE);
// Set the buffer's read position to the current offset
_buffer.setPosition(offset);
// Read the block size
int size = _buffer.getInt();
int nextIndex = _buffer.getInt();
long sequence = _buffer.getLong();
_buffer.getLong(); // ignore crcVal
int ready = _buffer.getInt();
// All empty blocks are free.
// The first block will be the metadata block
if (index == 0) {
// Create a new metadata block
_metadataBlock = new Block(index);
_metadataBlock.nextInChain = null;
_metadataBlock.nextInList = null;
_metadataBlock.sequenceNumber = sequence;
// Update the next sequence number
_nextSequence = sequence + 1;
// Update the minimum sequence number
minSeq = _nextSequence;
continue;
}
// Find or create a block based on the index
Block b = findOrCreate(index, allBlocks);
b.nextInChain = ((size == 0 || nextIndex == index) ? null : findOrCreate(nextIndex, allBlocks));
// All empty blocks are free. That's the rule.
if (size == 0) {
// Add the block to the free list
b.nextInList = _freeList;
_freeList = b;
} else if (sequence != 0) { // Only head blocks have their sequence number set.
if (ready == 0) // Block wasn't completed, add to free list
{
Block currentInChain = b;
while (currentInChain != null) {
Block nextInChain = currentInChain.nextInChain;
_buffer.zero(currentInChain.getPosition(),
Block.BLOCK_HEADER_SIZE);
currentInChain.sequenceNumber = 0;
currentInChain.nextInChain = null;
// and, flatten this chain onto the free list
currentInChain.nextInList = _freeList;
_freeList = currentInChain;
currentInChain = nextInChain;
}
} else {
b.sequenceNumber = sequence;
if (sequence < minSeq)
minSeq = sequence;
if (sequence > maxSeq)
maxSeq = sequence;
heads.put(sequence, b);
}
}
// else, Belongs to part of a "chain"
}
// Update metadata block with the correct sequence number.
if (_metadataBlock.sequenceNumber < (minSeq - 1))
_metadataBlock.sequenceNumber = minSeq - 1;
if (_nextSequence <= maxSeq)
_nextSequence = maxSeq + 1;
// Add head blocks to the used list in ascending order of sequence numbers.
for (Entry e : heads.entrySet()) {
Block b = e.getValue();
b.nextInList = null;
if (_endOfUsedList != null) {
_endOfUsedList.nextInList = b;
_endOfUsedList = b;
} else {
_usedList = _endOfUsedList = b;
}
}
} catch (IOException ioex) {
throw new StoreException(ioex);
}
} finally {
// Release the lock
lock.unlock();
}
}
/**
* Ensures that the free list is not empty by potentially growing it. This
* method acquires the lock, checks if the free list is empty, and if so,
* attempts to grow the free list by adding more blocks to it.
*
* @throws StoreException If an error occurs during the process of growing the
* free list.
*/
protected void growFreeListIfEmpty() throws StoreException {
lock.lock();
try {
_growFreeListIfEmpty();
} finally {
lock.unlock();
}
}
/**
* The internal method that actually grows the free list if it's empty. This
* method is called while the lock is already acquired. It checks if the free
* list is empty, and if so, it attempts to add more blocks to it, ensuring that
* the free list is not empty.
*
* @throws StoreException If an error occurs during the process of growing the
* free list.
*/
private void _growFreeListIfEmpty() throws StoreException {
// Lock must already be acquired.
// Wait while `_resizing` is true.
while (_resizing) {
// An interrupted exception is ignored to recheck the time
try {
_blocksFree.await(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
}
}
// If `_freeList` is not empty, return early
if (_freeList != null)
return;
try {
// Get the current size of the buffer
long oldSize = _buffer.getSize();
// Calculate the new size of the buffer by adding more blocks
long newSize = oldSize + ((long) Block.SIZE * (long) _blocksPerRealloc);
// Set `_resizing` to true to indicate resizing is in progress
_resizing = true;
try {
// Wait until the buffer's size is updated to the new size
while (_buffer.getSize() == oldSize) {
boolean changeSize = true;
// Check if there's a `_resizeHandler`, and if so, call it to potentially change
// the size
if (_resizeHandler != null) {
// Unlock the lock temporarily to invoke the resize handler
lock.unlock();
try {
changeSize = _resizeHandler.invoke(this, newSize);
} finally {
// Reacquire the lock after calling the handler
lock.lock();
}
}
// During unlock time, something may have freed some space.
if (_freeList != null)
return;
// Unless resize handler denied us, increase size
if (changeSize) {
if (newSize > Integer.MAX_VALUE) {
// If this message is changed, be sure to update testPubStoreMaxException
throw new StoreException("Maximum size for publish Store exceeded. Cannot grow beyond Integer.MAX_VALUE");
}
_buffer.setSize(newSize);
}
}
} finally {
// Set `_resizing` back to false once resizing is done
_resizing = false;
}
// If the buffer's size is still less than the new size, throw an exception
if (_buffer.getSize() < newSize)
throw new StoreException("Publish store could not resize, possibly due to resize handler.");
// Initialize the index (`idx`) to the last block's index in the old buffer size
int idx = (int) (oldSize / Block.SIZE);
// If `idx` is 0, create and initialize the metadata block
if (idx == 0) {
_metadataBlock = new Block(idx);
_metadataBlock.nextInChain = null;
_metadataBlock.nextInList = null;
_metadataBlock.sequenceNumber = 0;
long metadataPosition = _metadataBlock.getPosition();
_buffer.zero(metadataPosition, Block.SIZE);
_buffer.setPosition(metadataPosition +
METADATA_VERSION_LOCATION);
// Default to lowest version where first block is last discarded
int version;
try {
version = Client.getVersionAsInt(Client.getVersion());
} catch (CommandException ex) {
// An exception will occur for develop branch
version = AMPS_MIN_PUB_STORE_DISCARDED_VERSION;
}
_buffer.putInt(version);
_buffer.setPosition(metadataPosition +
METADATA_LAST_DISCARDED_LOCATION);
_buffer.putLong(0);
++idx;
}
// Create and initialize new blocks and add them to the free list
while (idx < (int) (newSize / Block.SIZE)) {
Block b = new Block(idx);
_buffer.zero(b.getPosition(), Block.SIZE);
b.nextInChain = null;
b.nextInList = _freeList;
_freeList = b;
++idx;
}
// Signal all waiting threads that new blocks are available
_blocksFree.signalAll();
} catch (IOException e) {
// Catch any IOException and wrap it in a StoreException before rethrowing
throw new StoreException(e);
}
}
/**
* Flushes the publish store.
* This method blocks until all messages have been successfully flushed or until
* a timeout occurs.
*
* @throws TimedOutException If the flush operation times out before all
* messages are flushed.
*/
public void flush() throws TimedOutException {
// Acquire a lock to enter a critical section
lock.lock();
try {
if (_usedList == null)
// If the usedList is empty (no messages), return immediately
return;
// Get the sequence number of the last used message
long current = _endOfUsedList.sequenceNumber;
// Continue to wait while there are messages in the usedList and the current
// message's sequence number is greater than or equal to the last used message's
// sequence number
while (_usedList != null && current >= _usedList.sequenceNumber)
{
try {
// Wait for a signal (notification) that indicates the store has been flushed
_blocksFree.await();
// Handle an InterruptedException if it occurs (ignored in this code)
} catch (InterruptedException e) { }
}
}
finally {
// Release the lock when done with the critical section
lock.unlock(); }
}
/**
* Flushes the publish store, waiting until all messages in the store are
* flushed or until a timeout occurs. This method allows specifying a timeout
* for the flush operation.
*
* @param timeout The maximum time to wait for the flush operation, in
* milliseconds.
* @throws TimedOutException If the flush operation times out before all
* messages are flushed.
*/
public void flush(long timeout) throws TimedOutException {
lock.lock();
try {
// Check if the _usedList is null (no items to flush), and if so, return
// immediately
if (_usedList == null)
return;
// Get the sequence number of the last item in the _usedList
long current = _endOfUsedList.sequenceNumber;
// Record the current time in milliseconds
long start = System.currentTimeMillis();
long end;
// Continue looping until either the _usedList is empty or the current time
// exceeds the timeout
while (_usedList != null && current >= _usedList.sequenceNumber) {
// Get the current time again
end = System.currentTimeMillis();
// If a positive timeout is specified and the elapsed time exceeds the timeout,
// throw a TimedOutException
if (timeout > 0 && (end - start) >= timeout)
throw new TimedOutException("Timed out waiting to flush publish store.");
try {
// Calculate the remaining time until the timeout and await the notification of
// a change in _blocksFree
long remaining = timeout - (end - start);
// An interrupted exception is ignored to recheck
_blocksFree.await(remaining, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
}
} finally {
// Release the lock to allow other threads to access the critical section
lock.unlock();
}
}
/**
* Sets a resize handler for the publish store. The resize handler is called
* when the store needs to be resized.
*
* @param handler The resize handler to set.
*/
public void setResizeHandler(PublishStoreResizeHandler handler) {
_resizeHandler = handler;
}
/********* PRIVATE METHODS *************/
/**
* Gets a block from the free list, optionally assigning a sequence number to
* it. If a sequence number is not assigned, the provided sequence value is
* used.
*
* @param assignSequence Indicates whether to assign a new sequence number to
* the block.
* @param sequence The sequence number to assign to the block if not
* assigning a new one.
* @return The retrieved block from the free list.
* @throws StoreException If an error occurs while getting the block.
*/
private Block get(boolean assignSequence, long sequence)
throws StoreException {
// Declare a Block variable and initialize it to null
Block b = null;
lock.lock();
try {
// Ensure the free list is populated with blocks
_growFreeListIfEmpty();
// Assign the first block from the free list to variable 'b'
b = _freeList;
// Check if a block was successfully obtained from the free list
if (b != null) {
// Update the free list to exclude the retrieved block
_freeList = b.nextInList;
// Remove any reference to the next block in the retrieved block
b.nextInList = null;
// Check if a new sequence number should be assigned.
// Make sure we start at a good value
if (assignSequence) {
// Make sure we start at a good value
if (_nextSequence <= 1)
// If the sequence is not initialized, retrieve it.
_getLastPersisted();
// Assign a new sequence number to the block
b.sequenceNumber = _nextSequence++;
// Check if a specific sequence number was provided
} else if (sequence != 0) {
// Assign the provided sequence number to the block
b.sequenceNumber = sequence;
}
if (assignSequence || sequence != 0) {
if (_endOfUsedList != null) {
// Attach the block to the end of the used list
_endOfUsedList.nextInList = b;
// Update the end of the used list to the new block
_endOfUsedList = b;
} else {
// _endOfUsedList is null if _usedList is null, as well.
// If the used list is empty, initialize it with the retrieved block
_endOfUsedList = _usedList = b;
}
}
}
} finally {
// Release the lock to allow other threads to access the method
lock.unlock();
}
// Return the retrieved block (or null if no block was available)
return b;
}
}