, Exception> {
+ /**
+ * This method is not supported in this interface and will always throw an {@link UnsupportedOperationException}.
+ * Use the {@link #create(long, int, File)} method instead.
+ *
+ * @param size The size of the buffer to be created.
+ * @param maxReaders The maximum number of readers supported by the buffer.
+ * @return Throws an {@link UnsupportedOperationException}.
+ * @throws UnsupportedOperationException Always thrown to indicate the method should not be used.
+ */
@Override
default @NotNull BytesStore, ?> apply(Long size, Integer maxReaders) throws Exception {
throw new UnsupportedOperationException("Call the create function instead");
}
+ /**
+ * Creates a {@link BytesStore} with the given size, maximum readers, and associated file for asynchronous operations.
+ *
+ * @param size The size of the buffer to create.
+ * @param maxReaders The maximum number of readers that can access the buffer.
+ * @param file The file associated with the buffer for storage.
+ * @return A {@link BytesStore} instance configured for asynchronous operations.
+ * @throws Exception If any error occurs during buffer creation.
+ */
@NotNull BytesStore, ?> create(long size, int maxReaders, File file) throws Exception;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/BinarySearch.java b/src/main/java/net/openhft/chronicle/queue/impl/single/BinarySearch.java
index e2265c9451..06dbd6f8f1 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/BinarySearch.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/BinarySearch.java
@@ -28,18 +28,32 @@
import java.util.Iterator;
import java.util.NavigableSet;
+/**
+ * The {@code BinarySearch} class provides functionality to perform a binary search
+ * across excerpts within a Chronicle Queue using {@link Wire} as the key and a {@link Comparator}
+ * for matching.
+ *
+ * This implementation relies on Chronicle Queue's encoded 64-bit indexes. While it works under current
+ * assumptions where the high bit of the index is not set, it may become unreliable if that changes in future.
+ */
public enum BinarySearch {
INSTANCE;
/**
- * returns the index or -1 if not found or the index if an exact match is found, an approximation in the form of -approximateIndex
- * or -1 if there was no searching to be done.
+ * Performs a binary search using the provided key and comparator.
+ * It searches for an exact match and returns the index if found, or an approximate index in the form of {@code -approximateIndex}.
+ * Returns {@code -1} if no search is possible or no match is found.
*
* Warning : This implementation is unreliable as index are an encoded 64bits, where we could use all the bits including the
* high bit which is used for the sign. At the moment it will work as its unlikely to reach a point where we store
* enough messages in the chronicle queue to use the high bit, having said this its possible in the future the
* high bit in the index ( used for the sign ) may be used, this implementation is unsafe as it relies on this
* bit not being set ( in other words set to zero ).
+ *
+ * @param tailer The {@link ExcerptTailer} used to read from the queue.
+ * @param key The {@link Wire} key used for comparison.
+ * @param c The {@link Comparator} to compare entries.
+ * @return The index if an exact match is found, otherwise {@code -approximateIndex} or {@code -1} if no match is found.
*/
public static long search(@NotNull ExcerptTailer tailer,
@NotNull Wire key,
@@ -67,6 +81,15 @@ public static long search(@NotNull ExcerptTailer tailer,
}
}
+ /**
+ * Performs a linear search through the available cycles to find the one that may contain the key.
+ *
+ * @param cycles The set of available cycles.
+ * @param key The key to search for.
+ * @param c The comparator for comparing keys.
+ * @param tailer The tailer used for reading the queue.
+ * @return The found cycle or the previous cycle if no exact match was found.
+ */
private static long findCycleLinearSearch(@NotNull NavigableSet cycles, Wire key,
@NotNull Comparator c,
@NotNull ExcerptTailer tailer) {
@@ -115,14 +138,14 @@ else if (compare > 0)
}
/**
- * @return The index if an exact match is found, an approximation in the form of -approximateIndex
- * or a negative number (- the approx index) if there was no searching to be done.
- *
- * Warning : This implementation is unreliable as index are an encoded 64bits, where we could use all the bits including the
- * high bit which is used for the sign. At the moment it will work as its unlikely to reach a point where we store
- * enough messages in the chronicle queue to use the high bit, having said this its possible in the future the
- * high bit in the index ( used for the sign ) may be used, this implementation is unsafe as it relies on this
- * bit not being set ( in other words set to zero ).
+ * Finds an entry within the cycle that matches the key using binary search.
+ *
+ * @param key The key to search for.
+ * @param c The comparator for comparing keys.
+ * @param cycle The cycle to search within.
+ * @param tailer The tailer used to navigate the queue.
+ * @param rollCycle The roll cycle information for the queue.
+ * @return The index if an exact match is found, or {@code -approximateIndex} if not found.
*/
public static long findWithinCycle(@NotNull Wire key,
@NotNull Comparator c,
@@ -177,7 +200,7 @@ else if (cmp > 0) {
}
}
- return midIndex == 0 ? -1 : -midIndex; // -approximateIndex
+ return midIndex == 0 ? -1 : -midIndex; // Return -approximateIndex if not exact match
} finally {
key.bytes().readPosition(readPosition);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/DirectoryListing.java b/src/main/java/net/openhft/chronicle/queue/impl/single/DirectoryListing.java
index c489d2622e..f16738d9de 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/DirectoryListing.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/DirectoryListing.java
@@ -22,21 +22,75 @@
import java.io.File;
+/**
+ * The {@code DirectoryListing} interface defines the contract for managing and tracking files
+ * within a Chronicle Queue directory. Implementations of this interface are responsible for
+ * monitoring files, managing cycles, and handling file creation events.
+ *
+ * It provides methods to refresh the directory, retrieve cycle information, and handle file events
+ * associated with the queue's rolling mechanism.
+ */
public interface DirectoryListing extends Closeable {
+
+ /**
+ * Initializes the directory listing. This method is intended for any setup required after
+ * the implementation is instantiated. The default implementation does nothing.
+ */
default void init() {
}
+ /**
+ * Refreshes the state of the directory listing, optionally forcing the refresh.
+ * This method updates the internal state of the directory listing, checking for any changes
+ * to the files and cycles managed by the queue.
+ *
+ * @param force If true, forces a refresh even if the conditions for an automatic refresh are not met.
+ */
void refresh(boolean force);
+ /**
+ * Returns the timestamp of the last refresh operation.
+ *
+ * @return The time of the last refresh in milliseconds since the epoch.
+ */
long lastRefreshTimeMS();
+ /**
+ * Called when a new file is created in the directory.
+ * This method is invoked when a new file corresponding to a specific cycle is detected.
+ *
+ * @param file The newly created file.
+ * @param cycle The cycle number associated with the file.
+ */
void onFileCreated(File file, int cycle);
+ /**
+ * Returns the minimum cycle number created so far.
+ *
+ * @return The minimum cycle number.
+ */
int getMinCreatedCycle();
+ /**
+ * Returns the maximum cycle number created so far.
+ *
+ * @return The maximum cycle number.
+ */
int getMaxCreatedCycle();
+ /**
+ * Returns the modification count, which represents the number of changes made to the directory
+ * listing since it was last refreshed.
+ *
+ * @return The modification count.
+ */
long modCount();
+ /**
+ * Called when a roll occurs, indicating that the cycle has changed.
+ * This method is invoked to signal that a roll to a new cycle has been completed.
+ *
+ * @param cycle The new cycle number.
+ */
void onRoll(int cycle);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/FileSystemDirectoryListing.java b/src/main/java/net/openhft/chronicle/queue/impl/single/FileSystemDirectoryListing.java
index fe995e18ba..bf7c3f9df3 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/FileSystemDirectoryListing.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/FileSystemDirectoryListing.java
@@ -26,6 +26,15 @@
import static net.openhft.chronicle.queue.impl.single.TableDirectoryListing.*;
+/**
+ * The {@code FileSystemDirectoryListing} class is responsible for managing the listing of files
+ * in a queue directory on the file system. It tracks the minimum and maximum cycle numbers
+ * of created files and provides methods for refreshing the directory state and handling file
+ * creation events.
+ *
+ * This class extends {@link SimpleCloseable} and implements {@link DirectoryListing}, allowing
+ * it to manage resources and handle cleanup when closed.
+ */
final class FileSystemDirectoryListing extends SimpleCloseable implements DirectoryListing {
private final File queueDir;
private final ToIntFunction fileNameToCycleFunction;
@@ -34,6 +43,13 @@ final class FileSystemDirectoryListing extends SimpleCloseable implements Direct
private int maxCreatedCycle = Integer.MIN_VALUE;
private long lastRefreshTimeMS;
+ /**
+ * Constructs a {@code FileSystemDirectoryListing} with the specified directory and function
+ * for determining cycle numbers from filenames.
+ *
+ * @param queueDir The directory containing the queue files.
+ * @param fileNameToCycleFunction A function that converts a filename to a cycle number.
+ */
FileSystemDirectoryListing(final File queueDir,
final ToIntFunction fileNameToCycleFunction,
final TimeProvider time) {
@@ -42,11 +58,24 @@ final class FileSystemDirectoryListing extends SimpleCloseable implements Direct
this.time = time;
}
+ /**
+ * Called when a new file is created in the directory, updating the internal cycle tracking.
+ *
+ * @param file The newly created file.
+ * @param cycle The cycle number associated with the file.
+ */
@Override
public void onFileCreated(final File file, final int cycle) {
onRoll(cycle);
}
+ /**
+ * Refreshes the directory listing, scanning for queue files and updating the minimum and
+ * maximum cycle numbers. If {@code force} is true, forces a directory scan even if conditions
+ * for automatic refresh are not met.
+ *
+ * @param force If true, forces a refresh.
+ */
@Override
public void refresh(boolean force) {
lastRefreshTimeMS = time.currentTimeMillis();
@@ -66,6 +95,7 @@ public void refresh(boolean force) {
}
}
+ // Update the minimum and maximum cycles based on the filenames
int min = UNSET_MIN_CYCLE;
if (!INITIAL_MIN_FILENAME.equals(minFilename))
min = fileNameToCycleFunction.applyAsInt(minFilename);
@@ -78,26 +108,54 @@ public void refresh(boolean force) {
maxCreatedCycle = max;
}
+ /**
+ * Returns the timestamp of the last refresh in milliseconds since the epoch.
+ *
+ * @return The timestamp of the last directory refresh.
+ */
@Override
public long lastRefreshTimeMS() {
return lastRefreshTimeMS;
}
+ /**
+ * Returns the minimum cycle number of the created files in the directory.
+ *
+ * @return The minimum cycle number.
+ */
@Override
public int getMinCreatedCycle() {
return minCreatedCycle;
}
+ /**
+ * Returns the maximum cycle number of the created files in the directory.
+ *
+ * @return The maximum cycle number.
+ */
@Override
public int getMaxCreatedCycle() {
return maxCreatedCycle;
}
+ /**
+ * Returns the modification count of the directory listing, indicating the number of changes
+ * since the last refresh. This implementation returns {@code -1}, as no modification tracking
+ * is implemented.
+ *
+ * @return The modification count, or {@code -1} if unsupported.
+ */
@Override
public long modCount() {
return -1;
}
+ /**
+ * Updates the cycle tracking when a new cycle is rolled.
+ * Adjusts the minimum and maximum cycle numbers based on the provided cycle.
+ *
+ * @param cycle The new cycle number.
+ */
@Override
public void onRoll(int cycle) {
minCreatedCycle = Math.min(minCreatedCycle, cycle);
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/IllegalIndexException.java b/src/main/java/net/openhft/chronicle/queue/impl/single/IllegalIndexException.java
index fc4bfb4baf..8150e27b62 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/IllegalIndexException.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/IllegalIndexException.java
@@ -1,11 +1,44 @@
+/*
+ * Copyright 2016-2022 chronicle.software
+ *
+ * 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 static java.lang.String.format;
+/**
+ * The {@code IllegalIndexException} is thrown when an index provided to a method or operation
+ * is after the next index in the queue, violating the queue's index boundaries.
+ *
+ * This exception extends {@link IllegalArgumentException} and provides a detailed message
+ * including the provided index and the last valid index in the queue.
+ */
public class IllegalIndexException extends IllegalArgumentException {
private static final long serialVersionUID = 0L;
+ /**
+ * Constructs an {@code IllegalIndexException} with a formatted message indicating
+ * the provided index and the last valid index in the queue.
+ *
+ * @param providedIndex The index that was provided, which is after the next valid index.
+ * @param lastIndex The last valid index in the queue.
+ */
public IllegalIndexException(long providedIndex, long lastIndex) {
+ // Create an exception message with hex formatting for both indices
super(format("Index provided is after the next index in the queue, provided index = %x, last index in queue = %x", providedIndex, lastIndex));
}
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/IndexNotAvailableException.java b/src/main/java/net/openhft/chronicle/queue/impl/single/IndexNotAvailableException.java
index d09444af2f..bcb81653b5 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/IndexNotAvailableException.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/IndexNotAvailableException.java
@@ -18,9 +18,23 @@
package net.openhft.chronicle.queue.impl.single;
+/**
+ * The {@code IndexNotAvailableException} is thrown when an attempt is made to access an index
+ * that is not available. This can occur when an index is expected but is either not present
+ * or has not yet been generated.
+ *
+ * This exception extends {@link IllegalStateException} to indicate that the current state
+ * does not allow the requested index operation to be completed.
+ */
public class IndexNotAvailableException extends IllegalStateException {
private static final long serialVersionUID = 0L;
- public IndexNotAvailableException(String s) {
- super(s);
+
+ /**
+ * Constructs an {@code IndexNotAvailableException} with the specified detail message.
+ *
+ * @param message the detail message explaining why the index is not available.
+ */
+ public IndexNotAvailableException(String message) {
+ super(message);
}
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/Indexing.java b/src/main/java/net/openhft/chronicle/queue/impl/single/Indexing.java
index bff2a3ae65..9e64ea4187 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/Indexing.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/Indexing.java
@@ -1,3 +1,21 @@
+/*
+ * Copyright 2016-2022 chronicle.software
+ *
+ * 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.queue.impl.ExcerptContext;
@@ -5,17 +23,41 @@
import java.io.StreamCorruptedException;
/**
- * Provides internal-only indexing functionality for {@link SingleChronicleQueue}. This interface will be expanded over
- * time to clarify the API contract of {@link SCQIndexing}.
+ * The {@code Indexing} interface provides internal-only indexing functionality for
+ * {@link SingleChronicleQueue}. This interface supports indexing operations like tracking
+ * the next entry to be indexed, verifying indexability, and retrieving sequence numbers.
+ * It is designed to be expanded over time to further clarify the API contract of {@link SCQIndexing}.
*/
public interface Indexing {
+ /**
+ * Retrieves the number of entries between each index.
+ *
+ * @return the number of entries between each index.
+ */
int indexCount();
+ /**
+ * Retrieves the spacing between indexed entries.
+ * If the spacing is set to 1, every entry will be indexed.
+ *
+ * @return the spacing between indexed entries.
+ */
int indexSpacing();
+ /**
+ * Returns the next entry to be indexed in the queue.
+ *
+ * @return the index of the next entry to be indexed.
+ */
long nextEntryToBeIndexed();
+ /**
+ * Checks if a given index is indexable based on the current indexing strategy.
+ *
+ * @param index The index to check.
+ * @return {@code true} if the index is eligible for indexing, otherwise {@code false}.
+ */
boolean indexable(long index);
/**
@@ -25,13 +67,23 @@ public interface Indexing {
* the value may be stale by the time it's returned. If you're holding the write lock it is guaranteed
* to be accurate.
*
- * @param ex An {@link ExcerptContext} used to scan the roll cycle if necssary
- * @return the sequence of the last excerpt in the cycle
- * @throws StreamCorruptedException if the index is corrupt
+ * @param ex An {@link ExcerptContext} used to scan the roll cycle if necessary.
+ * @return the sequence number of the last excerpt in the cycle.
+ * @throws StreamCorruptedException if the index is corrupted.
*/
long lastSequenceNumber(ExcerptContext ex) throws StreamCorruptedException;
+ /**
+ * Retrieves the count of linear scans that have occurred while indexing.
+ *
+ * @return the number of linear scans performed.
+ */
int linearScanCount();
+ /**
+ * Retrieves the count of linear scans performed based on positions.
+ *
+ * @return the number of position-based linear scans.
+ */
int linearScanByPositionCount();
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/InternalAppender.java b/src/main/java/net/openhft/chronicle/queue/impl/single/InternalAppender.java
index cd45e75370..ef82b219f4 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/InternalAppender.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/InternalAppender.java
@@ -22,7 +22,11 @@
import net.openhft.chronicle.queue.ExcerptAppender;
/**
- * please don't use this interface as it's an internal implementation.
+ * This interface, {@code InternalAppender}, extends the {@link ExcerptAppender} and provides
+ * additional functionality for appending entries at specific indices within a queue.
+ *
+ * Note: This is an internal interface and should not be used externally
+ * as it is subject to changes without notice.
*/
public interface InternalAppender extends ExcerptAppender {
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataField.java b/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataField.java
index 156ac8a66d..b2913a4845 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataField.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataField.java
@@ -22,6 +22,14 @@
import net.openhft.chronicle.wire.WireKey;
import org.jetbrains.annotations.Nullable;
+/**
+ * The {@code MetaDataField} enum represents various metadata fields used within a Chronicle Queue.
+ * These fields are associated with metadata stored in queue files, such as wire types, write positions,
+ * and replication-related information.
+ *
+ * Each field may have a specific role in managing or accessing the queue metadata, and certain fields
+ * may require a default value for proper queue functioning.
+ */
public enum MetaDataField implements WireKey {
wireType,
writePosition,
@@ -36,6 +44,13 @@ public enum MetaDataField implements WireKey {
dataFormat,
metadata;
+ /**
+ * This method throws an {@link IORuntimeException} indicating that the field requires a value to be provided,
+ * as no default value is available.
+ *
+ * @return Throws an exception indicating that the field requires a value.
+ * @throws IORuntimeException when a default value is requested for a field that doesn't support one.
+ */
@Nullable
@Override
public Object defaultValue() {
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataKeys.java b/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataKeys.java
index 6345937746..6763853995 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataKeys.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/MetaDataKeys.java
@@ -19,9 +19,34 @@
import net.openhft.chronicle.wire.WireKey;
+/**
+ * The {@code MetaDataKeys} enum defines keys that are used to represent different sections
+ * or types of metadata within a Chronicle Queue. These keys help in identifying and accessing
+ * specific metadata components such as headers, indexes, and roll information.
+ */
public enum MetaDataKeys implements WireKey {
+
+ /**
+ * Represents the key for the queue's header metadata. The header typically contains
+ * information about the queue's structure and configuration.
+ */
header,
+
+ /**
+ * Represents the key for the index-to-index structure within the queue, which manages
+ * the link between different index entries and assists in efficient navigation.
+ */
index2index,
+
+ /**
+ * Represents the key for the index, which keeps track of the positions of various
+ * excerpts in the queue for quick lookups and reads.
+ */
index,
+
+ /**
+ * Represents the key for the roll metadata, which manages the queue's rolling behavior,
+ * determining when the queue rolls over to the next cycle.
+ */
roll
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/MicroToucher.java b/src/main/java/net/openhft/chronicle/queue/impl/single/MicroToucher.java
index 2c28b962e7..465eef2899 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/MicroToucher.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/MicroToucher.java
@@ -24,16 +24,35 @@
import net.openhft.posix.MSyncFlag;
import net.openhft.posix.PosixAPI;
+/**
+ * The {@code MicroToucher} class is responsible for managing memory page touches
+ * and syncing to ensure that memory writes are persisted efficiently.
+ * It is used in conjunction with the {@link StoreAppender} to track and handle
+ * the memory pages that need touching or syncing during append operations.
+ */
public class MicroToucher {
private final StoreAppender appender;
private long lastPageTouched = 0;
private volatile long lastPageToSync = 0;
private long lastPageSynced = 0;
+ /**
+ * Constructs a {@code MicroToucher} with a specified {@link StoreAppender}.
+ *
+ * @param appender The appender associated with this micro-toucher, used to
+ * retrieve the wire and manage positions for memory touching.
+ */
public MicroToucher(StoreAppender appender) {
this.appender = appender;
}
+ /**
+ * Executes the page-touching logic. It calculates the memory page where the appender's last
+ * position is and attempts to "touch" the next page. This ensures that the memory pages
+ * are kept in an active state as data is written.
+ *
+ * @return {@code true} if the page was successfully touched, {@code false} otherwise.
+ */
public boolean execute() {
final Wire bufferWire = appender.wire();
if (bufferWire == null)
@@ -59,6 +78,11 @@ public boolean execute() {
return false;
}
+ /**
+ * Executes the background sync operation, syncing memory pages to disk.
+ * It ensures that pages touched earlier are asynchronously written to disk using
+ * the {@link PosixAPI#msync} function.
+ */
public void bgExecute() {
final long lastPage = this.lastPageToSync;
final long start = this.lastPageSynced;
@@ -76,6 +100,13 @@ public void bgExecute() {
this.lastPageSynced += length;
}
+ /**
+ * Synchronizes the specified range of memory pages to disk.
+ *
+ * @param bytes The bytes store from which to sync.
+ * @param start The start address of the memory to sync.
+ * @param length The length of the memory region to sync.
+ */
private void sync(BytesStore, ?> bytes, long start, long length) {
if (!bytes.inside(start, length))
return;
@@ -84,6 +115,14 @@ private void sync(BytesStore, ?> bytes, long start, long length) {
// System.out.println("sync took " + (System.nanoTime() - a) / 1000);
}
+ /**
+ * Attempts to touch a memory page by performing a compare-and-swap operation on it.
+ * This method ensures the page is actively touched and handled by the system.
+ *
+ * @param nextPage The memory address of the next page to touch.
+ * @param bs The bytes store associated with the memory.
+ * @return {@code true} if the page was touched successfully, {@code false} otherwise.
+ */
protected boolean touchPage(long nextPage, BytesStore, ?> bs) {
return bs.compareAndSwapLong(nextPage, 0, 0);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/MissingStoreFileException.java b/src/main/java/net/openhft/chronicle/queue/impl/single/MissingStoreFileException.java
index 48ea1a48d2..e4475d24e4 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/MissingStoreFileException.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/MissingStoreFileException.java
@@ -19,10 +19,21 @@
package net.openhft.chronicle.queue.impl.single;
/**
- * Thrown when a store file we expect to be present is missing (probably because it was deleted)
+ * This exception is thrown when a store file, which is expected to be present,
+ * is missing. The missing file could be due to accidental deletion or
+ * other external causes, potentially leading to issues in data consistency.
+ *
+ *
This class extends {@link IllegalStateException} and provides an
+ * informative message to identify the missing store file.
*/
public class MissingStoreFileException extends IllegalStateException {
private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructs a {@code MissingStoreFileException} with the specified detail message.
+ *
+ * @param s The detail message, indicating which file is missing or relevant information.
+ */
public MissingStoreFileException(String s) {
super(s);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/NamedTailerNotAvailableException.java b/src/main/java/net/openhft/chronicle/queue/impl/single/NamedTailerNotAvailableException.java
index 7b3bc0aa60..1524f99ce5 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/NamedTailerNotAvailableException.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/NamedTailerNotAvailableException.java
@@ -1,31 +1,83 @@
+/*
+ * Copyright 2016-2022 chronicle.software
+ *
+ * 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;
+/**
+ * This exception is thrown when a named tailer cannot be created due to specific reasons.
+ * Named tailers are used in Chronicle Queue for named access patterns, and this exception
+ * provides details on why the creation of such a tailer has failed.
+ *
+ *
The exception includes a {@link Reason} that describes why the tailer creation failed,
+ * and provides the name of the tailer in question.
+ */
public class NamedTailerNotAvailableException extends IllegalStateException {
private static final long serialVersionUID = 0L;
private final String tailerName;
-
private final Reason reason;
+ /**
+ * Constructs a {@code NamedTailerNotAvailableException} with the given tailer name and reason.
+ *
+ * @param tailerName The name of the tailer that could not be created.
+ * @param reason The reason for the failure, as defined in {@link Reason}.
+ */
public NamedTailerNotAvailableException(String tailerName, Reason reason) {
super("Named tailer cannot be created because: " + reason.description);
this.tailerName = tailerName;
this.reason = reason;
}
+ /**
+ * Returns the name of the tailer that could not be created.
+ *
+ * @return The name of the unavailable tailer.
+ */
public String tailerName() {
return tailerName;
}
+ /**
+ * Returns the reason why the tailer could not be created.
+ *
+ * @return The {@link Reason} for the failure.
+ */
public Reason reason() {
return reason;
}
+ /**
+ * Enum representing the possible reasons why a named tailer cannot be created.
+ */
public enum Reason {
+ /**
+ * Indicates that named tailers cannot be created on a replication sink.
+ */
NOT_AVAILABLE_ON_SINK("Replicated named tailers cannot be instantiated on a replication sink");
private final String description;
+ /**
+ * Constructs a {@code Reason} with the given description.
+ *
+ * @param description A brief description of the reason.
+ */
Reason(String description) {
this.description = description;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/NoOpCondition.java b/src/main/java/net/openhft/chronicle/queue/impl/single/NoOpCondition.java
index 619d52d05b..dd99ce6e28 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/NoOpCondition.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/NoOpCondition.java
@@ -25,41 +25,83 @@
import java.util.concurrent.locks.Condition;
/**
- * A condition that is always true
+ * {@code NoOpCondition} is a no-operation implementation of the {@link Condition} interface.
+ * This condition is always true and does not block, signal, or modify any thread state.
+ *
+ *
All operations on this condition return immediately without performing any blocking
+ * or waiting behavior, effectively serving as a placeholder or dummy condition.
*/
public final class NoOpCondition implements Condition {
+ /**
+ * Singleton instance of {@code NoOpCondition}, as it has no mutable state and can be reused.
+ */
public static final NoOpCondition INSTANCE = new NoOpCondition();
+ /**
+ * Private constructor to enforce singleton usage through {@link #INSTANCE}.
+ */
private NoOpCondition() {}
+ /**
+ * Does nothing, returns immediately.
+ */
@Override
- public void await() throws InterruptedException {
+ public void await() {
}
+ /**
+ * Does nothing, returns immediately.
+ */
@Override
public void awaitUninterruptibly() {
}
+ /**
+ * Returns the input nanosecond duration without any delay or action.
+ *
+ * @param nanosTimeout The timeout in nanoseconds.
+ * @return The same input nanosecond value.
+ */
@Override
- public long awaitNanos(long l) {
- return l;
+ public long awaitNanos(long nanosTimeout) {
+ return nanosTimeout;
}
+ /**
+ * Returns {@code true} without blocking.
+ *
+ * @param l The maximum time to wait.
+ * @param timeUnit The time unit of the {@code l} argument.
+ * @return Always returns {@code true}.
+ * @throws InterruptedException This method does not throw an exception.
+ */
@Override
public boolean await(long l, TimeUnit timeUnit) throws InterruptedException {
return true;
}
+ /**
+ * Returns {@code true} without waiting for the given date.
+ *
+ * @param date The deadline by which waiting should end.
+ * @return Always returns {@code true}.
+ */
@Override
public boolean awaitUntil(@NotNull Date date) {
return true;
}
+ /**
+ * Does nothing, returns immediately.
+ */
@Override
public void signal() {
}
+ /**
+ * Does nothing, returns immediately.
+ */
@Override
public void signalAll() {
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/NotComparableException.java b/src/main/java/net/openhft/chronicle/queue/impl/single/NotComparableException.java
index 0965335b38..0536cb40d5 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/NotComparableException.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/NotComparableException.java
@@ -19,17 +19,26 @@
package net.openhft.chronicle.queue.impl.single;
/**
- * Thrown by a binary search comparator when the value we're searching on is not present in the current
- * entry.
- *
- * The assumption is that this occurs rarely. If it does, it will significantly reduce the performance
- * of the binary search.
+ * {@code NotComparableException} is thrown by a binary search comparator when the value being searched for
+ * is not present in the current entry.
+ *
+ *
This exception is expected to be rare during a binary search operation. However, when it occurs,
+ * it can significantly reduce the performance of the binary search due to the need for additional comparisons
+ * or fallback logic.
+ *
+ *
This exception is a singleton, with a single instance available via the {@link #INSTANCE} field.
*/
public final class NotComparableException extends RuntimeException {
private static final long serialVersionUID = 0L;
+ /**
+ * Singleton instance of {@code NotComparableException}.
+ */
public static final NotComparableException INSTANCE = new NotComparableException();
+ /**
+ * Private constructor to enforce singleton usage via {@link #INSTANCE}.
+ */
private NotComparableException() {
}
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/NotReachedException.java b/src/main/java/net/openhft/chronicle/queue/impl/single/NotReachedException.java
index 56d07676d8..9fad467651 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/NotReachedException.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/NotReachedException.java
@@ -18,8 +18,20 @@
package net.openhft.chronicle.queue.impl.single;
+/**
+ * {@code NotReachedException} is thrown when an expected condition or state is not reached during the operation of a system or process.
+ *
+ *
This exception typically indicates that some required milestone or checkpoint in a process was not achieved, potentially due to
+ * a failure or an unexpected state.
+ */
public class NotReachedException extends IllegalStateException {
private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructs a new {@code NotReachedException} with the specified detail message.
+ *
+ * @param s the detail message explaining the reason the exception was thrown
+ */
public NotReachedException(final String s) {
super(s);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/PrecreatedFiles.java b/src/main/java/net/openhft/chronicle/queue/impl/single/PrecreatedFiles.java
index bc2ee88526..513d0aac3d 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/PrecreatedFiles.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/PrecreatedFiles.java
@@ -22,11 +22,23 @@
import java.io.File;
+/**
+ * Utility class for handling pre-created Chronicle Queue files. Pre-created files have a specific
+ * file suffix ({@code ".precreated"}) and can be renamed to the required queue or store file name
+ * when necessary.
+ */
public enum PrecreatedFiles {
; // none
private static final String PRE_CREATED_FILE_SUFFIX = ".precreated";
+ /**
+ * Renames a pre-created queue file to the required queue file name.
+ *
+ * If the pre-created file exists and the rename operation fails, a warning is logged.
+ *
+ * @param requiredQueueFile The queue file that the pre-created file should be renamed to.
+ */
public static void renamePreCreatedFileToRequiredFile(final File requiredQueueFile) {
final File preCreatedFile = preCreatedFile(requiredQueueFile);
if (preCreatedFile.exists()) {
@@ -36,11 +48,25 @@ public static void renamePreCreatedFileToRequiredFile(final File requiredQueueFi
}
}
+ /**
+ * Creates and returns a file object representing a pre-created store file for the given
+ * required store file.
+ *
+ * @param requiredStoreFile The file for which a pre-created store file is required.
+ * @return The pre-created store file object.
+ */
public static File preCreatedFileForStoreFile(final File requiredStoreFile) {
return new File(requiredStoreFile.getParentFile(), requiredStoreFile.getName() +
PRE_CREATED_FILE_SUFFIX);
}
+ /**
+ * Creates and returns a file object representing a pre-created queue file for the given
+ * required queue file.
+ *
+ * @param requiredQueueFile The file for which a pre-created queue file is required.
+ * @return The pre-created queue file object.
+ */
public static File preCreatedFile(final File requiredQueueFile) {
final String fileName = requiredQueueFile.getName();
final String name = fileName.substring(0, fileName.length() - 4);
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/Pretoucher.java b/src/main/java/net/openhft/chronicle/queue/impl/single/Pretoucher.java
index 219da677d8..210471b791 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/Pretoucher.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/Pretoucher.java
@@ -37,8 +37,22 @@
*/
public interface Pretoucher extends Closeable {
+ /**
+ * Executes the pre-touching process, advancing over pages in the Chronicle Queue's store file.
+ * This method is intended to be run continuously in a background thread, ensuring pages are prepared
+ * before they are accessed by appenders.
+ *
+ * If the underlying queue has been closed, this method will throw an {@link InvalidEventHandlerException}.
+ *
+ *
+ * @throws InvalidEventHandlerException if the queue has been closed or if there is an issue during the pre-touch operation.
+ */
void execute() throws InvalidEventHandlerException;
+ /**
+ * Closes the pretoucher and releases any resources associated with it.
+ * After calling this method, further calls to {@link #execute()} will throw an {@link InvalidEventHandlerException}.
+ */
@Override
void close();
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/ReadOnlyWriteLock.java b/src/main/java/net/openhft/chronicle/queue/impl/single/ReadOnlyWriteLock.java
index 942cad241a..05d2a81155 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/ReadOnlyWriteLock.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/ReadOnlyWriteLock.java
@@ -19,26 +19,62 @@
import java.util.function.LongConsumer;
+/**
+ * A {@code ReadOnlyWriteLock} is a {@link WriteLock} implementation for read-only Chronicle Queues.
+ * This lock throws {@link IllegalStateException} when any write-related operation is attempted,
+ * indicating that the queue is in a read-only state.
+ */
public class ReadOnlyWriteLock implements WriteLock {
+
+ /**
+ * Attempts to acquire the write lock.
+ * This method always throws an {@link IllegalStateException} since the queue is read-only.
+ *
+ * @throws IllegalStateException if called, because the queue is read-only.
+ */
@Override
public void lock() {
throw new IllegalStateException("Queue is read-only");
}
+ /**
+ * Attempts to release the write lock.
+ * This method always throws an {@link IllegalStateException} since the queue is read-only.
+ *
+ * @throws IllegalStateException if called, because the queue is read-only.
+ */
@Override
public void unlock() {
throw new IllegalStateException("Queue is read-only");
}
+ /**
+ * Closes the write lock.
+ * This method does nothing for read-only locks.
+ */
@Override
public void close() {
}
+ /**
+ * Attempts to forcefully unlock if the locking process is dead.
+ * This method always throws an {@link IllegalStateException} since the queue is read-only.
+ *
+ * @throws IllegalStateException if called, because the queue is read-only.
+ */
@Override
public boolean forceUnlockIfProcessIsDead() {
throw new IllegalStateException("Queue is read-only");
}
+ /**
+ * Checks if the lock is held by the current process.
+ * Since the queue is read-only, this method always returns false and triggers the provided consumer
+ * with {@link Long#MAX_VALUE}, which represents an invalid process.
+ *
+ * @param notCurrentProcessConsumer the consumer that will be called with {@link Long#MAX_VALUE}.
+ * @return false, as the lock is not held by the current process (read-only queue).
+ */
@Override
public boolean isLockedByCurrentProcess(LongConsumer notCurrentProcessConsumer) {
notCurrentProcessConsumer.accept(Long.MAX_VALUE);
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/ReferenceCountedCache.java b/src/main/java/net/openhft/chronicle/queue/impl/single/ReferenceCountedCache.java
index 2b18f1583a..d1077951f1 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/ReferenceCountedCache.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/ReferenceCountedCache.java
@@ -29,16 +29,36 @@
import java.util.function.Function;
/**
- * Thread-safe, self-cleaning cache for ReferenceCounted (and Closeable) objects
+ * A thread-safe, self-cleaning cache for managing {@link ReferenceCounted} and {@link Closeable} objects.
+ *
+ * This cache ensures that cached objects are reference-counted, and once the reference count
+ * drops to one (held by this cache), the object will be automatically released.
+ * The cache performs background cleanup to release resources when the last external reference is removed.
+ *
+ *
+ * @param The type of the cache key
+ * @param The type of the cached objects, which must implement both {@link ReferenceCounted} and {@link Closeable}
+ * @param The type returned by the transformer function
+ * @param The type of any exception that might be thrown during object creation
*/
public class ReferenceCountedCache
extends AbstractCloseable {
+ // Cache to store objects against keys
private final Map cache = new LinkedHashMap<>();
+ // Function to transform a cached object into the desired return type
private final Function transformer;
+ // Function to create a new object when it's not present in the cache
private final ThrowingFunction creator;
+ // Listener to handle reference count changes for objects
private final ReferenceChangeListener referenceChangeListener;
+ /**
+ * Constructs a {@code ReferenceCountedCache} instance.
+ *
+ * @param transformer A function to transform a cached object into the return type.
+ * @param creator A function that creates a new object if it is not present in the cache.
+ */
@SuppressWarnings("this-escape")
public ReferenceCountedCache(final Function transformer,
final ThrowingFunction creator) {
@@ -49,6 +69,14 @@ public ReferenceCountedCache(final Function transformer,
singleThreadedCheckDisabled(true);
}
+ /**
+ * Retrieves a cached object or creates a new one if not present.
+ * If the object is created, it will be automatically added to the cache.
+ *
+ * @param key The key to identify the object in the cache.
+ * @return The transformed value from the cached object.
+ * @throws E If the object creation process encounters an error.
+ */
@NotNull
V get(@NotNull final K key) throws E {
throwExceptionIfClosed();
@@ -72,6 +100,10 @@ V get(@NotNull final K key) throws E {
return rv;
}
+ /**
+ * Closes the cache and releases all cached objects.
+ * This method ensures all resources held by the cached objects are released.
+ */
@Override
protected void performClose() {
synchronized (cache) {
@@ -82,6 +114,11 @@ protected void performClose() {
}
}
+ /**
+ * Releases the resources of a cached object.
+ *
+ * @param value The cached object to release.
+ */
private void releaseResource(T value) {
try {
if (value != null)
@@ -91,6 +128,11 @@ private void releaseResource(T value) {
}
}
+ /**
+ * Removes the object associated with the given key from the cache and releases its resources.
+ *
+ * @param key The key of the object to remove.
+ */
public void remove(K key) {
// harmless to call if cache is already closing/closed
@@ -99,17 +141,24 @@ public void remove(K key) {
}
}
+ /**
+ * A listener to trigger cache cleanup when the last reference (besides this cache) is removed.
+ */
private class TriggerFlushOnLastReferenceRemoval implements ReferenceChangeListener {
private final Runnable bgCleanup = this::bgCleanup;
@Override
public void onReferenceRemoved(ReferenceCounted referenceCounted, ReferenceOwner referenceOwner) {
+ // If only this cache holds the reference, trigger background cleanup
if (referenceOwner != ReferenceCountedCache.this && referenceCounted.refCount() == 1) {
BackgroundResourceReleaser.run(bgCleanup);
}
}
+ /**
+ * Performs background cleanup to remove objects with no remaining references from the cache.
+ */
private void bgCleanup() {
// remove all which have been de-referenced by other than me. Garbagy but rare
synchronized (cache) {
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/RollCycleEncodeSequence.java b/src/main/java/net/openhft/chronicle/queue/impl/single/RollCycleEncodeSequence.java
index 0b477f5bdb..35b0ca7281 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/RollCycleEncodeSequence.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/RollCycleEncodeSequence.java
@@ -23,11 +23,22 @@
import net.openhft.chronicle.core.values.TwoLongValue;
import net.openhft.chronicle.wire.Sequence;
+/**
+ * This class encodes and manages the sequence in a Chronicle Queue based on roll cycles.
+ * It is responsible for encoding the sequence and position, and handling sequence retrieval.
+ */
class RollCycleEncodeSequence implements Sequence {
private final TwoLongValue writePositionAndSequence;
private final int cycleShift;
private final long sequenceMask;
+ /**
+ * Constructs an instance of RollCycleEncodeSequence.
+ *
+ * @param writePositionAndSequence The value containing both write position and sequence.
+ * @param indexCount The number of indices.
+ * @param indexSpacing The spacing between indices.
+ */
RollCycleEncodeSequence(LongValue writePositionAndSequence, int indexCount, int indexSpacing) {
this.cycleShift = Math.max(32, Maths.intLog2(indexCount) * 2 + Maths.intLog2(indexSpacing));
this.sequenceMask = (1L << cycleShift) - 1;
@@ -35,6 +46,12 @@ class RollCycleEncodeSequence implements Sequence {
(TwoLongValue) writePositionAndSequence : null;
}
+ /**
+ * Sets the sequence value and position in the underlying TwoLongValue.
+ *
+ * @param sequence The sequence number to set.
+ * @param position The position to set.
+ */
@Override
public void setSequence(long sequence, long position) {
if (writePositionAndSequence == null)
@@ -43,6 +60,13 @@ public void setSequence(long sequence, long position) {
writePositionAndSequence.setOrderedValue2(value);
}
+ /**
+ * Converts the given header number and sequence into an index.
+ *
+ * @param headerNumber The header number.
+ * @param sequence The sequence number.
+ * @return The index value.
+ */
@Override
public long toIndex(long headerNumber, long sequence) {
long cycle = toLowerBitsWritePosition(headerNumber);
@@ -57,8 +81,8 @@ public long toIndex(long headerNumber, long sequence) {
* NOT_FOUND_RETRY will be return if a sequence number can not be found ( so can retry )
* or NOT_FOUND when you should not retry
*
- * @param forWritePosition the last write position, expected to be the end of queue
- * @return NOT_FOUND_RETRY if the sequence for this write position can not be found, or NOT_FOUND if sequenceValue==null or the sequence for this {@code writePosition}
+ * @param forWritePosition The write position, expected to be the end of the queue.
+ * @return The sequence number or {@link Sequence#NOT_FOUND_RETRY} if the sequence is not found.
*/
@SuppressWarnings("deprecation")
public long getSequence(long forWritePosition) {
@@ -87,14 +111,33 @@ public long getSequence(long forWritePosition) {
return Sequence.NOT_FOUND_RETRY;
}
+ /**
+ * Combines the cycle and sequence number into a single long value.
+ *
+ * @param cycle The cycle number.
+ * @param sequenceNumber The sequence number.
+ * @return The combined long value.
+ */
private long toLongValue(long cycle, long sequenceNumber) {
return (cycle << cycleShift) + (sequenceNumber & sequenceMask);
}
+ /**
+ * Extracts the sequence number from an index.
+ *
+ * @param index The encoded index.
+ * @return The sequence number.
+ */
public long toSequenceNumber(long index) {
return index & sequenceMask;
}
+ /**
+ * Extracts the cycle portion of the index by shifting right.
+ *
+ * @param index The encoded index.
+ * @return The lower bits of the write position.
+ */
private long toLowerBitsWritePosition(long index) {
return index >>> cycleShift;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQIndexing.java b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQIndexing.java
index 421cbac7fd..3b71300308 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQIndexing.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQIndexing.java
@@ -1,8 +1,10 @@
/*
- * Copyright 2016-2020 https://chronicle.software
+ * Copyright 2016-2020 chronicle.software
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
+ * 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
@@ -12,7 +14,6 @@
* 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;
@@ -49,6 +50,12 @@
import static net.openhft.chronicle.queue.RollCycle.MAX_INDEX_COUNT;
import static net.openhft.chronicle.wire.Wires.NOT_INITIALIZED;
+/**
+ * SCQIndexing is responsible for managing index structures within a {@link SingleChronicleQueue}.
+ * It stores and tracks positions of entries in a chronicle queue, optimizing access and scans for entries.
+ * This class also maintains thread-local storage for index arrays and is capable of managing
+ * write positions for entries.
+ */
@SuppressWarnings("deprecation")
class SCQIndexing extends AbstractCloseable implements Indexing, Demarshallable, WriteMarshallable, Closeable {
private static final boolean IGNORE_INDEXING_FAILURE = Jvm.getBoolean("queue.ignoreIndexingFailure");
@@ -84,9 +91,9 @@ class SCQIndexing extends AbstractCloseable implements Indexing, Demarshallable,
private long lastScannedIndex = -1;
/**
- * used by {@link Demarshallable}
+ * Constructor used for demarshalling via {@link Demarshallable}.
*
- * @param wire a wire
+ * @param wire The input wire to read from.
*/
@UsedViaReflection
private SCQIndexing(@NotNull WireIn wire) {
@@ -97,6 +104,13 @@ private SCQIndexing(@NotNull WireIn wire) {
wire::newLongArrayReference);
}
+ /**
+ * Constructor to create an {@code SCQIndexing} instance using a specific wire type.
+ *
+ * @param wireType The wire type used for creating the index structure.
+ * @param indexCount The count of indexes.
+ * @param indexSpacing The spacing between indexes.
+ */
SCQIndexing(@NotNull WireType wireType, int indexCount, int indexSpacing) {
this(indexCount,
indexSpacing,
@@ -105,7 +119,17 @@ private SCQIndexing(@NotNull WireIn wire) {
wireType.newLongArrayReference());
}
- private SCQIndexing(int indexCount, int indexSpacing, LongValue index2Index, LongValue nextEntryToBeIndexed, Supplier longArraySupplier) {
+ /**
+ * Main constructor to initialize indexing with required parameters.
+ *
+ * @param indexCount The count of indexes to maintain.
+ * @param indexSpacing The spacing between indexes.
+ * @param index2Index Reference for storing index-to-index values.
+ * @param nextEntryToBeIndexed Reference for tracking the next entry to be indexed.
+ * @param longArraySupplier Supplier for creating long array values.
+ */
+ private SCQIndexing(int indexCount, int indexSpacing, LongValue index2Index,
+ LongValue nextEntryToBeIndexed, Supplier longArraySupplier) {
this.indexCount = indexCount;
this.indexCountBits = Maths.intLog2(indexCount);
this.indexSpacing = indexSpacing;
@@ -120,6 +144,7 @@ private SCQIndexing(int indexCount, int indexSpacing, LongValue index2Index, Lon
singleThreadedCheckDisabled(true);
}
+ // Helper method to create a new LongArrayValuesHolder
private LongArrayValuesHolder newLogArrayValuesHolder(Supplier las) {
LongArrayValues values = las.get();
LongArrayValuesHolder longArrayValuesHolder = new LongArrayValuesHolder(values);
@@ -127,11 +152,13 @@ private LongArrayValuesHolder newLogArrayValuesHolder(Supplier
return longArrayValuesHolder;
}
+ // Fetches the index-to-index array from thread-local storage.
@NotNull
private LongArrayValuesHolder getIndex2IndexArray() {
return ThreadLocalHelper.getTL(index2indexArray, longArraySupplier, arrayValuesSupplierCall);
}
+ // Fetches the index array from thread-local storage.
@NotNull
private LongArrayValuesHolder getIndexArray() {
return ThreadLocalHelper.getTL(indexArray, longArraySupplier, arrayValuesSupplierCall);
@@ -153,11 +180,9 @@ long toAddress1(long index) {
return mask & siftedIndex;
}
-/* @Override
- protected boolean performCloseInBackground() {
- return true;
- }*/
-
+ /**
+ * Closes this indexing instance, releasing resources.
+ */
@Override
protected void performClose() {
closeQuietly(index2Index, nextEntryToBeIndexed);
@@ -170,6 +195,7 @@ protected void performClose() {
closeTL(index2indexArray);
}
+ // Helper method to close a thread-local LongArrayValuesHolder.
private void closeTL(ThreadLocal> tl) {
WeakReference weakReference = tl.get();
if (weakReference == null)
@@ -179,6 +205,13 @@ private void closeTL(ThreadLocal> tl) {
closeQuietly(holder.values());
}
+ /**
+ * Serializes the indexing fields of this class to the provided {@link WireOut} object.
+ * The fields include the index count, index spacing, and binding the {@code index2Index}
+ * and {@code nextEntryToBeIndexed} fields.
+ *
+ * @param wire The {@link WireOut} object to which the data is written.
+ */
@Override
public void writeMarshallable(@NotNull WireOut wire) {
wire.write(IndexingFields.indexCount).int64(indexCount)
@@ -187,6 +220,15 @@ public void writeMarshallable(@NotNull WireOut wire) {
.write(IndexingFields.lastIndex).int64forBinding(0L, nextEntryToBeIndexed);
}
+ /**
+ * Retrieves the {@link LongArrayValues} stored at the specified secondary address within the wire.
+ * If the secondary address matches the previously used address, the cached array is returned.
+ * Otherwise, the new array is read from the wire.
+ *
+ * @param wire The wire containing the array data.
+ * @param secondaryAddress The address to fetch the array from.
+ * @return The {@link LongArrayValues} at the specified address.
+ */
@NotNull
private LongArrayValues arrayForAddress(@NotNull Wire wire, long secondaryAddress) {
LongArrayValuesHolder holder = getIndexArray();
@@ -198,6 +240,14 @@ private LongArrayValues arrayForAddress(@NotNull Wire wire, long secondaryAddres
return array(wire, holder.values(), false);
}
+ /**
+ * Reads an array of {@link LongArrayValues} from the wire and fills the specified {@code using} array.
+ *
+ * @param w The wire to read the array from.
+ * @param using The {@link LongArrayValues} instance to populate.
+ * @param index2index Whether the array being read is the index2index array.
+ * @return The populated {@link LongArrayValues} instance.
+ */
@NotNull
private LongArrayValues array(@NotNull WireIn w, @NotNull LongArrayValues using, boolean index2index) {
@NotNull final ValueIn valueIn = readIndexValue(w, index2index ? "index2index" : "index");
@@ -206,6 +256,14 @@ private LongArrayValues array(@NotNull WireIn w, @NotNull LongArrayValues using,
return using;
}
+ /**
+ * Reads a value from the wire and checks if the event name matches the expected name.
+ * Throws an {@link IllegalStateException} if the names do not match.
+ *
+ * @param w The wire to read the value from.
+ * @param expectedName The expected event name.
+ * @return The {@link ValueIn} corresponding to the expected event.
+ */
private ValueIn readIndexValue(@NotNull WireIn w, @NotNull String expectedName) {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
final StringBuilder sb = stlSb.get();
@@ -218,12 +276,13 @@ private ValueIn readIndexValue(@NotNull WireIn w, @NotNull String expectedName)
}
/**
- * 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. )
+ * Creates a new excerpt containing an index, which will be {@code 1L << 17L} bytes long.
+ * This method is used for creating both primary and secondary indexes.
*
- * @param wire the current wire
- * @return the addressForRead of the Excerpt containing the usable index, just after the header
+ * @param wire The wire for writing the index.
+ * @param index2index Whether this is for an index2index structure.
+ * @return The address of the excerpt containing the usable index, just after the header.
+ * @throws StreamCorruptedException If the stream is corrupted during the process.
*/
long newIndex(@NotNull WireOut wire, boolean index2index) throws StreamCorruptedException {
long writePosition = this.writePosition.getVolatileValue();
@@ -259,9 +318,9 @@ long newIndex(@NotNull Wire wire, @NotNull LongArrayValues index2Index, long ind
* 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
+ * @param ec The excerpt context used for reading the index.
+ * @param index The index to move to.
+ * @return A {@link ScanResult} indicating the result of the operation.
*/
@NotNull
ScanResult moveToIndex(@NotNull final ExcerptContext ec, final long index) {
@@ -271,6 +330,13 @@ ScanResult moveToIndex(@NotNull final ExcerptContext ec, final long index) {
return value;
}
+ /**
+ * Performs a linear scan from the start of the wire to find the specified {@code index}.
+ *
+ * @param ec The excerpt context used for reading the index.
+ * @param index The index to find.
+ * @return A {@link ScanResult} indicating the result of the operation.
+ */
@NotNull
private ScanResult moveToIndexFromTheStart(@NotNull ExcerptContext ec, long index) {
try {
@@ -287,7 +353,15 @@ private ScanResult moveToIndexFromTheStart(@NotNull ExcerptContext ec, long inde
return ScanResult.NOT_FOUND;
}
- // visible for testing
+ /**
+ * Attempts to move the context to the specified {@code index}. This method navigates through the index structure
+ * stored in the wire and retrieves the corresponding address. It begins by using the primary index (index2index)
+ * to locate the secondary index, and then performs a backwards scan in the secondary index.
+ *
+ * @param ec The {@link ExcerptContext} used for reading the index.
+ * @param index The index to move to.
+ * @return A {@link ScanResult} indicating the result of the operation, or {@code null} if the index cannot be found.
+ */
@Nullable
ScanResult moveToIndex0(@NotNull final ExcerptContext ec, final long index) {
if (index2Index.getVolatileValue() == NOT_INITIALIZED)
@@ -314,7 +388,17 @@ ScanResult moveToIndex0(@NotNull final ExcerptContext ec, final long index) {
return null;
}
-
+ /**
+ * Performs a backwards scan of the secondary index to locate the specified {@code index}. If the index is found,
+ * the position is moved to the address corresponding to the index. Otherwise, it performs a linear scan from
+ * the starting point.
+ *
+ * @param ec The {@link ExcerptContext} used for reading the index.
+ * @param array1 The secondary index to scan.
+ * @param startIndex The starting index of the scan.
+ * @param index The target index to find.
+ * @return A {@link ScanResult} indicating whether the index was found, or if a linear scan is required.
+ */
private ScanResult scanSecondaryIndexBackwards(@NotNull final ExcerptContext ec, LongArrayValues array1, long startIndex, long index) {
long secondaryOffset = toAddress1(index);
@@ -344,11 +428,11 @@ private ScanResult scanSecondaryIndexBackwards(@NotNull final ExcerptContext ec,
* 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
+ * @param wire The wire used to read the data.
+ * @param toIndex The target index to reach.
+ * @param fromKnownIndex A known starting index.
+ * @param knownAddress The address corresponding to the known starting index.
+ * @return A {@link ScanResult} indicating the outcome of the scan.
*/
@NotNull
@@ -366,6 +450,15 @@ private ScanResult linearScan(@NotNull final Wire wire,
return scanResult;
}
+ /**
+ * Prints the time taken for a linear scan operation, if it exceeds a threshold.
+ * The method also records a stack trace if debugging is enabled.
+ *
+ * @param toIndex The target index.
+ * @param fromKnownIndex The known starting index.
+ * @param start The start time of the scan.
+ * @param desc A description of the scan operation.
+ */
private void printLinearScanTime(long toIndex, long fromKnownIndex, long start, String desc) {
// still warming up?
if (toIndex <= 1)
@@ -379,6 +472,15 @@ private void printLinearScanTime(long toIndex, long fromKnownIndex, long start,
doPrintLinearScanTime(toIndex, fromKnownIndex, start, desc, end);
}
+ /**
+ * Logs the details of a linear scan operation that took longer than expected, along with a stack trace if debugging is enabled.
+ *
+ * @param toIndex The target index.
+ * @param fromKnownIndex The known starting index.
+ * @param start The start time of the scan.
+ * @param desc A description of the scan operation.
+ * @param end The end time of the scan.
+ */
private void doPrintLinearScanTime(long toIndex, long fromKnownIndex, long start, String desc, long end) {
StackTrace st = null;
if (Jvm.isDebugEnabled(getClass())) {
@@ -394,6 +496,15 @@ private void doPrintLinearScanTime(long toIndex, long fromKnownIndex, long start
Jvm.perf().on(getClass(), message, st);
}
+ /**
+ * Performs the actual linear scan operation from a known index and address to the target {@code toIndex}.
+ *
+ * @param wire The wire used to read the data.
+ * @param toIndex The target index to reach.
+ * @param fromKnownIndex The known starting index.
+ * @param knownAddress The address corresponding to the known starting index.
+ * @return A {@link ScanResult} indicating the outcome of the scan.
+ */
@NotNull
private ScanResult linearScan0(@NotNull final Wire wire,
final long toIndex,
@@ -439,10 +550,33 @@ private ScanResult linearScan0(@NotNull final Wire wire,
}
}
+ /**
+ * Performs a linear scan from a known index and address to a target {@code toIndex}.
+ * This method leverages the wire's data and metadata headers to navigate the records
+ * within the Chronicle Queue.
+ *
+ * @param toIndex The target index to scan to.
+ * @param knownIndex A known index to start the scan from.
+ * @param ec The {@link ExcerptContext} used for reading the index.
+ * @param knownAddress The address corresponding to the known index.
+ * @return A {@link ScanResult} indicating the outcome of the scan.
+ */
ScanResult linearScanTo(final long toIndex, final long knownIndex, final ExcerptContext ec, final long knownAddress) {
return linearScan(ec.wire(), toIndex, knownIndex, knownAddress);
}
+ /**
+ * Performs a linear scan by position to locate the entry at the specified {@code toPosition}.
+ * This method returns the index of the entry or an exception if the position is not valid.
+ *
+ * @param wire The wire object used to read the data.
+ * @param toPosition The target position in the wire.
+ * @param indexOfNext The index of the next known entry.
+ * @param startAddress The starting address to begin the scan from.
+ * @param inclusive Whether the target position should be inclusive.
+ * @return The index of the found entry.
+ * @throws EOFException If the scan reaches the end of the file before finding the position.
+ */
long linearScanByPosition(@NotNull final Wire wire,
final long toPosition,
final long indexOfNext,
@@ -456,6 +590,19 @@ long linearScanByPosition(@NotNull final Wire wire,
return index;
}
+ /**
+ * Helper method to perform the actual linear scan by position. This method reads through
+ * the wire's entries, navigating based on position and header type, until it finds the
+ * required position or reaches the end of the wire.
+ *
+ * @param wire The wire object used to read the data.
+ * @param toPosition The target position in the wire.
+ * @param indexOfNext The index of the next known entry.
+ * @param startAddress The starting address to begin the scan from.
+ * @param inclusive Whether the target position should be inclusive.
+ * @return The index of the found entry.
+ * @throws EOFException If the scan reaches the end of the file before finding the position.
+ */
long linearScanByPosition0(@NotNull final Wire wire,
final long toPosition,
long indexOfNext,
@@ -465,12 +612,14 @@ long linearScanByPosition0(@NotNull final Wire wire,
assert toPosition >= 0;
Bytes> bytes = wire.bytes();
long i;
- // optimized if the `toPosition` is the writePosition
+
+ // Optimized path if the `toPosition` is the last written position.
long lastAddress = writePosition.getVolatileValue();
long lastIndex = this.sequence.getSequence(lastAddress);
i = calculateInitialValue(toPosition, indexOfNext, startAddress, bytes, lastAddress, lastIndex);
+ // Scan through the entries until the target position is found or exceeded.
while (bytes.readPosition() <= toPosition) {
WireIn.HeaderType headerType = wire.readDataHeader(true);
if (headerType == WireIn.HeaderType.EOF) {
@@ -484,6 +633,7 @@ long linearScanByPosition0(@NotNull final Wire wire,
switch (headerType) {
case NONE:
+ // Case where no data header is found
if (toPosition == Long.MAX_VALUE) {
return i;
}
@@ -492,17 +642,21 @@ long linearScanByPosition0(@NotNull final Wire wire,
throwIndexNotWritten(toPosition, startAddress, bytes, header);
break;
case META_DATA:
+ // Skip metadata
break;
case DATA:
+ // Increment the index for each valid data entry
++i;
break;
case EOF:
throw new AssertionError("EOF should have been handled");
}
+ // If the current position matches the target, return the index
if (bytes.readPosition() == toPosition)
return i;
+ // Skip over the current entry
int header = bytes.readVolatileInt();
int len = Wires.lengthOf(header);
assert Wires.isReady(header);
@@ -512,6 +666,18 @@ long linearScanByPosition0(@NotNull final Wire wire,
return throwPositionNotAtStartOfMessage(toPosition, bytes);
}
+ /**
+ * Calculates the initial index value to start scanning from, based on whether
+ * the target position is the last written position or a known earlier position.
+ *
+ * @param toPosition The target position in the wire.
+ * @param indexOfNext The index of the next known entry.
+ * @param startAddress The starting address for the scan.
+ * @param bytes The bytes object associated with the wire.
+ * @param lastAddress The address of the last written entry.
+ * @param lastIndex The index of the last written entry.
+ * @return The starting index for the scan.
+ */
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) {
@@ -523,6 +689,14 @@ private long calculateInitialValue(long toPosition, long indexOfNext, long start
}
}
+ /**
+ * Throws an exception if an index is requested for an entry that hasn't been written yet.
+ *
+ * @param toPosition The target position in the wire.
+ * @param startAddress The starting address for the scan.
+ * @param bytes The bytes object associated with the wire.
+ * @param header The header of the current entry.
+ */
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. " +
@@ -532,6 +706,14 @@ private void throwIndexNotWritten(long toPosition, long startAddress, Bytes> b
", toPos: " + toPosition);
}
+ /**
+ * Throws an exception if the position is not at the start of a message, meaning the scan failed
+ * to locate a valid message at the specified position.
+ *
+ * @param toPosition The target position in the wire.
+ * @param bytes The bytes object associated with the wire.
+ * @return A long indicating the failure.
+ */
private long throwPositionNotAtStartOfMessage(long toPosition, Bytes> bytes) {
throw new IllegalArgumentException("position not the start of a message, bytes" +
".readPosition()=" + bytes.readPosition() + ",toPosition=" + toPosition);
@@ -542,6 +724,17 @@ public long nextEntryToBeIndexed() {
return nextEntryToBeIndexed.getVolatileValue();
}
+ /**
+ * Returns the sequence number for a given position in the wire.
+ * If an exact match is found for the position, the corresponding index is returned;
+ * otherwise, a linear scan is performed to approximate the closest sequence.
+ *
+ * @param ec The {@link ExcerptContext} used to navigate the queue.
+ * @param position The position for which the sequence is requested.
+ * @param inclusive Whether the position should be treated inclusively.
+ * @return The sequence number for the given position, or an approximation based on the linear scan.
+ * @throws StreamCorruptedException If the index is corrupted or not initialized properly.
+ */
long sequenceForPosition(@NotNull ExcerptContext ec,
final long position,
boolean inclusive) throws StreamCorruptedException {
@@ -551,6 +744,8 @@ long sequenceForPosition(@NotNull ExcerptContext ec,
try {
final LongArrayValues index2indexArr = getIndex2index(wire);
int used2 = getUsedAsInt(index2indexArr);
+
+ // Outer loop: Iterate through index2index array to find the relevant secondary index.
Outer:
for (int index2 = used2 - 1; index2 >= 0; index2--) {
long secondaryAddress = getSecondaryAddress(wire, index2indexArr, index2);
@@ -558,18 +753,19 @@ long sequenceForPosition(@NotNull ExcerptContext ec,
continue;
LongArrayValues indexValues = arrayForAddress(wire, secondaryAddress);
- // TODO use a binary rather than linear search
+ // TODO: Use a binary search instead of a linear search for optimization.
- // check the first one to see if any in the index is appropriate.
int used = getUsedAsInt(indexValues);
if (used == 0)
continue;
+ // Check if the first value in the index is appropriate.
long posN = indexValues.getVolatileValueAt(0);
assert posN >= 0;
if (posN > position)
continue;
+ // Inner loop: Search within the secondary index.
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..
@@ -590,12 +786,20 @@ long sequenceForPosition(@NotNull ExcerptContext ec,
Jvm.debug().on(getClass(), "Attempt to find " + Long.toHexString(position), e);
}
try {
+ // Perform a linear scan if no exact match is found.
return linearScanByPosition(wire, position, indexOfNext, lastKnownAddress, inclusive);
} catch (EOFException e) {
throw new UncheckedIOException(e);
}
}
+ /**
+ * Retrieves the number of entries used in the given {@link LongArrayValues}.
+ * Validates that the number of entries is within the expected range.
+ *
+ * @param index2indexArr The {@link LongArrayValues} representing the index.
+ * @return The number of entries used, as an integer.
+ */
static int getUsedAsInt(LongArrayValues index2indexArr) {
if (((Byteable) index2indexArr).bytesStore() == null)
return 0;
@@ -606,13 +810,20 @@ static int getUsedAsInt(LongArrayValues index2indexArr) {
return (int) used;
}
+ /**
+ * Initializes the index and sets up the index2index and other structures required for indexing.
+ * This method sets the position and creates new entries in the index.
+ *
+ * @param wire The {@link Wire} object used to write to the queue.
+ * @throws StreamCorruptedException If the index is corrupted or not initialized properly.
+ */
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
+ // Ensure new header position is found despite the first header not being finalized.
long oldPos = wire.bytes().writePosition();
if (!writePosition.compareAndSwapValue(0, oldPos))
throw new IllegalStateException("Who updated the position?");
@@ -623,11 +834,18 @@ void initIndex(@NotNull Wire wire) throws StreamCorruptedException {
LongArrayValues index2index = getIndex2index(wire);
newIndex(wire, index2index, 0);
- // Reset position as it were
+ // Reset the position to its original value.
if (!writePosition.compareAndSwapValue(oldPos, 0))
throw new IllegalStateException("Who reset the position?");
}
+ /**
+ * Retrieves the {@link LongArrayValues} for the index2index array. If the index2index array
+ * has not been initialized, it reads it from the provided {@link Wire}.
+ *
+ * @param wire The wire object used to read from the queue.
+ * @return The {@link LongArrayValues} representing the index2index array.
+ */
@SuppressWarnings("try")
private LongArrayValues getIndex2index(@NotNull Wire wire) {
@@ -711,14 +929,32 @@ void setPositionForSequenceNumber(@NotNull ExcerptContext ec,
nextEntryToBeIndexed.setMaxValue(sequenceNumber + indexSpacing);
}
+ /**
+ * Throws an {@link IllegalStateException} if an invalid secondary address is encountered.
+ *
+ * @param secondaryAddress The secondary address that caused the error.
+ */
private void throwSecondaryAddressError(long secondaryAddress) {
throw new IllegalStateException("sa2: " + secondaryAddress);
}
+ /**
+ * Throws an {@link IllegalStateException} when the sequence number exceeds the allowed maximum
+ * number of entries for the current roll cycle.
+ *
+ * @param sequenceNumber The sequence number that exceeds the roll cycle's entry limit.
+ */
private void throwNumEntriesExceededForRollCycle(long sequenceNumber) {
throw new IllegalStateException("Unable to index " + sequenceNumber + ", the number of entries exceeds max number for the current rollcycle");
}
+ /**
+ * Determines if the given index is indexable based on the current index spacing.
+ * An index is indexable if it aligns with the defined index spacing.
+ *
+ * @param index The index to check for indexability.
+ * @return {@code true} if the index is indexable, otherwise {@code false}.
+ */
@Override
public boolean indexable(long index) {
throwExceptionIfClosed();
@@ -726,6 +962,15 @@ public boolean indexable(long index) {
return (index & (indexSpacing - 1)) == 0;
}
+ /**
+ * Retrieves the last sequence number in the queue.
+ * This method attempts to retrieve the sequence number based on the current write position and may use
+ * a linear scan if the exact sequence is not readily available.
+ *
+ * @param ec The {@link ExcerptContext} used to navigate the queue.
+ * @return The last sequence number, or {@code -1} if it cannot be found.
+ * @throws StreamCorruptedException If the sequence cannot be determined due to corruption.
+ */
@Override
public long lastSequenceNumber(@NotNull ExcerptContext ec)
throws StreamCorruptedException {
@@ -755,16 +1000,34 @@ public long lastSequenceNumber(@NotNull ExcerptContext ec)
return sequenceForPosition(ec, Long.MAX_VALUE, false);
}
+ /**
+ * Returns the number of indices available in the current roll cycle.
+ *
+ * @return The number of indices.
+ */
@Override
public int indexCount() {
return indexCount;
}
+ /**
+ * Returns the spacing between indexed entries.
+ * Index spacing defines how frequently entries are indexed.
+ *
+ * @return The index spacing.
+ */
@Override
public int indexSpacing() {
return indexSpacing;
}
+ /**
+ * Moves to the end of the wire, scanning for the final sequence.
+ * This method attempts to locate the last written entry in the wire and updates the sequence accordingly.
+ *
+ * @param wire The {@link Wire} object used to navigate the queue.
+ * @return The last sequence number, or {@code -1} if it cannot be determined.
+ */
public long moveToEnd(final Wire wire) {
Sequence sequence1 = this.sequence;
if (sequence1 != null) {
@@ -785,6 +1048,7 @@ public long moveToEnd(final Wire wire) {
bytes.readPosition(endAddress);
+ // Iterate through the wire to find the last complete entry.
for (; ; ) {
int header = bytes.readVolatileInt(endAddress);
if (header == 0 || Wires.isNotComplete(header))
@@ -805,38 +1069,76 @@ public long moveToEnd(final Wire wire) {
return -1;
}
+ /**
+ * Returns the count of linear scans performed during indexing.
+ *
+ * @return The count of linear scans.
+ */
@Override
public int linearScanCount() {
return linearScanCount;
}
+ /**
+ * Returns the count of linear scans by position performed during indexing.
+ *
+ * @return The count of linear scans by position.
+ */
@Override
public int linearScanByPositionCount() {
return linearScanByPositionCount;
}
+ /**
+ * Enumeration of fields used in the indexing structure.
+ * This defines the keys used for reading and writing index-related data in the wire.
+ */
enum IndexingFields implements WireKey {
indexCount, indexSpacing, index2Index,
lastIndex // NOTE: the nextEntryToBeIndexed
}
+ /**
+ * Holder class for {@link LongArrayValues} that caches the address and provides efficient access.
+ * This class is used to cache the address of an array and its values for quick lookups in the indexing structure.
+ */
static class LongArrayValuesHolder {
private final LongArrayValues values;
private long address;
+ /**
+ * Constructs a holder for the provided {@link LongArrayValues}.
+ *
+ * @param values The {@link LongArrayValues} to hold.
+ */
LongArrayValuesHolder(LongArrayValues values) {
this.values = values;
address = Long.MIN_VALUE;
}
+ /**
+ * Gets the current address of the held values.
+ *
+ * @return The address.
+ */
public long address() {
return address;
}
+ /**
+ * Sets a new address for the held values.
+ *
+ * @param address The new address.
+ */
public void address(long address) {
this.address = address;
}
+ /**
+ * Returns the {@link LongArrayValues} held by this holder.
+ *
+ * @return The {@link LongArrayValues}.
+ */
public LongArrayValues values() {
return values;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQMeta.java b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQMeta.java
index ec51489438..4d0a20ac05 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQMeta.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQMeta.java
@@ -26,12 +26,31 @@
import java.util.Objects;
+/**
+ * SCQMeta class implements the Metadata interface and handles
+ * metadata for SingleChronicleQueue, including roll settings,
+ * delta checkpoint intervals, and source IDs. This class is
+ * typically used to serialize and deserialize metadata.
+ */
public class SCQMeta implements Metadata {
+
+ // Represents the roll configuration for this metadata.
@NotNull
private final SCQRoll roll;
+
+ // Delta checkpoint interval, -1 indicates disabled.
private final int deltaCheckpointInterval;
+
+ // Source ID associated with the metadata.
private int sourceId;
+ /**
+ * Constructor used via reflection. It reads the metadata fields
+ * from the WireIn object and initializes the SCQMeta instance.
+ *
+ * @param wire WireIn object used to deserialize the metadata
+ * @throws NullPointerException if roll is not provided in the wire input
+ */
@SuppressWarnings("unused")
@UsedViaReflection
SCQMeta(@NotNull WireIn wire) {
@@ -40,25 +59,54 @@ public class SCQMeta implements Metadata {
this.sourceId = wire.bytes().readRemaining() > 0 ? wire.read(MetaDataField.sourceId).int32() : 0;
}
+ /**
+ * Constructs an SCQMeta object with the specified roll, delta checkpoint interval, and source ID.
+ *
+ * @param roll the SCQRoll object for roll configuration
+ * @param deltaCheckpointInterval the interval for delta checkpointing
+ * @param sourceId the source ID associated with this metadata
+ */
SCQMeta(@NotNull SCQRoll roll, int deltaCheckpointInterval, int sourceId) {
this.roll = roll;
this.deltaCheckpointInterval = deltaCheckpointInterval;
this.sourceId = sourceId;
}
+ /**
+ * Returns the roll configuration associated with this metadata.
+ *
+ * @return the SCQRoll object
+ */
@NotNull
public SCQRoll roll() {
return roll;
}
+ /**
+ * Returns the delta checkpoint interval. A value of -1 indicates
+ * that the checkpointing is disabled.
+ *
+ * @return the delta checkpoint interval
+ */
public int deltaCheckpointInterval() {
return deltaCheckpointInterval;
}
+ /**
+ * Returns the source ID associated with this metadata.
+ *
+ * @return the source ID
+ */
public int sourceId() {
return sourceId;
}
+ /**
+ * Writes the current state of the metadata to a WireOut object,
+ * including roll, delta checkpoint interval, and source ID.
+ *
+ * @param wire the WireOut object to serialize the metadata to
+ */
@Override
public void writeMarshallable(@NotNull WireOut wire) {
wire
@@ -67,6 +115,14 @@ public void writeMarshallable(@NotNull WireOut wire) {
.write(MetaDataField.sourceId).int32(this.sourceId);
}
+ /**
+ * Overrides the metadata from the given SCQMeta object. Various
+ * roll attributes and source ID are updated if different from
+ * the current values. Warnings are logged if any values are overridden.
+ *
+ * @param metadata the SCQMeta object to override values from
+ * @throws IllegalStateException if the provided metadata is not of type SCQMeta
+ */
@Override
public void overrideFrom(T metadata) {
if (!(metadata instanceof SCQMeta))
@@ -74,28 +130,33 @@ public void overrideFrom(T metadata) {
SCQMeta other = (SCQMeta) metadata;
+ // Override roll epoch if different
SCQRoll roll = other.roll;
if (roll.epoch() != this.roll.epoch()) {
Jvm.warn().on(getClass(), "Overriding roll epoch from existing metadata, was " + this.roll.epoch() + ", overriding to " + roll.epoch());
this.roll.epoch(roll.epoch());
}
+ // Override roll length if different
if (roll.length() != this.roll.length()) {
Jvm.warn().on(getClass(), "Overriding roll length from existing metadata, was " + this.roll.length() + ", overriding to " + roll.length());
this.roll.length(roll.length());
this.roll.format(roll.format());
}
+ // Override roll time if different
if (roll.rollTime() != null && !Objects.equals(roll.rollTime(), this.roll.rollTime())) {
Jvm.warn().on(getClass(), "Overriding roll time from existing metadata, was " + this.roll.rollTime() + ", overriding to " + roll.rollTime());
this.roll.rollTime(roll.rollTime());
}
+ // Override roll time zone if different
if (roll.rollTimeZone() != null && !Objects.equals(roll.rollTimeZone(), this.roll.rollTimeZone())) {
Jvm.warn().on(getClass(), "Overriding roll time zone from existing metadata, was " + this.roll.rollTimeZone() + ", overriding to " + roll.rollTimeZone());
this.roll.rollTimeZone(roll.rollTimeZone());
}
+ // Override sourceId if different
if (other.sourceId != sourceId) {
Jvm.warn().on(getClass(), "Overriding sourceId from existing metadata, was " + sourceId + ", overriding to " + other.sourceId);
this.sourceId = other.sourceId;
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQRoll.java b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQRoll.java
index 16a7eb7e93..18f9a0c923 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQRoll.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQRoll.java
@@ -1,8 +1,10 @@
/*
* Copyright 2016-2020 chronicle.software
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
+ * 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
@@ -12,7 +14,6 @@
* 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;
@@ -26,29 +27,50 @@
import java.time.LocalTime;
import java.time.ZoneId;
+/**
+ * SCQRoll class handles roll settings for the SingleChronicleQueue,
+ * such as the roll length, format, epoch time, roll time, and time zone.
+ * It implements {@link Demarshallable} and {@link WriteMarshallable}
+ * to allow reading and writing these fields to a wire format.
+ */
class SCQRoll implements Demarshallable, WriteMarshallable {
+
+ // The length of the roll in milliseconds.
private int length;
+
+ // The format used for roll naming, nullable.
@Nullable
private String format;
+
+ // The time at which the roll occurs, nullable.
@Nullable
private LocalTime rollTime;
+
+ // The time zone for the roll time, nullable.
@Nullable
private ZoneId rollTimeZone;
+
+ // The epoch offset in milliseconds since January 1, 1970.
private long epoch;
/**
- * used by {@link Demarshallable}
+ * Constructor used via reflection, typically for deserialization.
+ * Reads the roll settings from a {@link WireIn} object.
*
- * @param wire a wire
+ * @param wire the WireIn object used to read roll fields
*/
@UsedViaReflection
private SCQRoll(@NotNull WireIn wire) {
length = wire.read(RollFields.length).int32();
format = wire.read(RollFields.format).text();
epoch = wire.read(RollFields.epoch).int64();
+
+ // Read rollTime if it exists in the wire
ValueIn rollTimeVIN = wire.read(RollFields.rollTime);
if (rollTimeVIN.hasNext())
rollTime = rollTimeVIN.time();
+
+ // Read rollTimeZone if available
String zoneId = wire.read(RollFields.rollTimeZone).text();
if (zoneId != null)
rollTimeZone = ZoneId.of(zoneId);
@@ -56,6 +78,14 @@ private SCQRoll(@NotNull WireIn wire) {
rollTimeZone = null;
}
+ /**
+ * Constructs an SCQRoll with the provided roll cycle, epoch, roll time, and roll time zone.
+ *
+ * @param rollCycle the RollCycle object to derive the roll length and format
+ * @param epoch the epoch offset in milliseconds
+ * @param rollTime the time of the roll, nullable
+ * @param rollTimeZone the time zone for the roll, nullable
+ */
SCQRoll(@NotNull RollCycle rollCycle,
long epoch,
@Nullable LocalTime rollTime,
@@ -67,64 +97,124 @@ private SCQRoll(@NotNull WireIn wire) {
this.rollTimeZone = rollTimeZone;
}
+ /**
+ * Writes the current state of the SCQRoll to a {@link WireOut} object, including
+ * length, format, epoch, roll time, and roll time zone.
+ *
+ * @param wire the WireOut object to write the roll fields to
+ */
@Override
public void writeMarshallable(@NotNull WireOut wire) {
wire.write(RollFields.length).int32(length)
.write(RollFields.format).text(format)
.write(RollFields.epoch).int64(epoch);
+
+ // Write rollTime if not null
if (rollTime != null)
wire.write(RollFields.rollTime).time(rollTime);
+
+ // Write rollTimeZone if not null
if (rollTimeZone != null)
wire.write(RollFields.rollTimeZone).text(rollTimeZone.getId());
-
}
/**
- * @return an epoch offset as the number of number of milliseconds since January 1, 1970,
- * 00:00:00 GMT
+ * Returns the epoch offset as the number of milliseconds since January 1, 1970.
+ *
+ * @return the epoch time in milliseconds
*/
public long epoch() {
return this.epoch;
}
+ /**
+ * Returns the format used for roll naming.
+ *
+ * @return the format as a string, or null if not set
+ */
public String format() {
return this.format;
}
+ /**
+ * Returns the roll length in milliseconds.
+ *
+ * @return the length of the roll
+ */
int length() {
return length;
}
+ /**
+ * Returns the time at which the roll occurs, or null if not set.
+ *
+ * @return the roll time, nullable
+ */
@Nullable
public LocalTime rollTime() {
return rollTime;
}
+ /**
+ * Returns the time zone for the roll time, or null if not set.
+ *
+ * @return the roll time zone, nullable
+ */
@Nullable
public ZoneId rollTimeZone() {
return rollTimeZone;
}
+ /**
+ * Sets the length of the roll in milliseconds.
+ *
+ * @param length the new length to set
+ */
public void length(int length) {
this.length = length;
}
+ /**
+ * Sets the format for the roll naming.
+ *
+ * @param format the new format to set, nullable
+ */
public void format(@Nullable String format) {
this.format = format;
}
+ /**
+ * Sets the roll time.
+ *
+ * @param rollTime the new roll time to set, nullable
+ */
public void rollTime(@Nullable LocalTime rollTime) {
this.rollTime = rollTime;
}
+ /**
+ * Sets the roll time zone.
+ *
+ * @param rollTimeZone the new time zone to set, nullable
+ */
public void rollTimeZone(@Nullable ZoneId rollTimeZone) {
this.rollTimeZone = rollTimeZone;
}
+ /**
+ * Sets the epoch time.
+ *
+ * @param epoch the new epoch to set in milliseconds
+ */
public void epoch(long epoch) {
this.epoch = epoch;
}
+ /**
+ * Returns a string representation of the SCQRoll object, displaying all roll fields.
+ *
+ * @return a string representation of the object
+ */
@Override
public String toString() {
return "SCQRoll{" +
@@ -136,6 +226,9 @@ public String toString() {
'}';
}
+ /**
+ * Enum representing the fields used for marshalling and demarshalling roll data.
+ */
enum RollFields implements WireKey {
length, format, epoch, rollTime, rollTimeZone
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQTools.java b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQTools.java
index bc507a0927..cba4229c27 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/SCQTools.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/SCQTools.java
@@ -28,14 +28,29 @@
import static net.openhft.chronicle.bytes.MethodReader.MESSAGE_HISTORY_METHOD_ID;
import static net.openhft.chronicle.wire.BinaryWireCode.FIELD_NUMBER;
+/**
+ * SCQTools class provides utility methods for reading message history
+ * from a DocumentContext. It uses different methods to handle cases
+ * where the history can be read from wire or bytes.
+ */
@SuppressWarnings("deprecation")
public enum SCQTools {
- ; // none
+ ; // No instances, utility methods only
+ /**
+ * Reads the message history from the provided {@link DocumentContext} and updates the given
+ * {@link MessageHistory} object. Depending on the data format, it delegates to either reading
+ * from bytes or wire.
+ *
+ * @param dc the DocumentContext from which to read the message history
+ * @param history the MessageHistory object to update
+ * @return the updated MessageHistory, or null if not available
+ */
@Nullable
public static MessageHistory readHistory(@NotNull final DocumentContext dc, final MessageHistory history) {
final Wire wire = dc.wire();
+ // If wire is null, no history can be read
if (wire == null)
return null;
@@ -44,6 +59,7 @@ public static MessageHistory readHistory(@NotNull final DocumentContext dc, fina
try {
final Bytes> bytes = wire.bytes();
+ // Check the field number to determine the format
final byte code = bytes.readByte(bytes.readPosition());
history.reset();
@@ -51,28 +67,56 @@ public static MessageHistory readHistory(@NotNull final DocumentContext dc, fina
readHistoryFromBytes(wire, history) :
readHistoryFromWire(wire, history);
} finally {
+ // Restore the parent object after reading
wire.parent(parent);
}
}
+ /**
+ * Reads message history from wire bytes if the field number format is used.
+ * Verifies that the event number matches the MESSAGE_HISTORY_METHOD_ID before reading.
+ *
+ * @param wire the Wire object to read from
+ * @param history the MessageHistory object to update
+ * @return the updated MessageHistory, or null if the method ID doesn't match
+ */
@Nullable
private static MessageHistory readHistoryFromBytes(@NotNull final Wire wire, final MessageHistory history) {
+ // Check if the event number matches the expected MESSAGE_HISTORY_METHOD_ID
if (MESSAGE_HISTORY_METHOD_ID != wire.readEventNumber())
return null;
+
+ // Deserialize the message history into the provided object
wire.getValueIn().marshallable(history);
return history;
}
+ /**
+ * Reads message history from wire if it's not using the field number format.
+ * It relies on reading a marshallable value directly from the wire.
+ *
+ * @param wire the Wire object to read from
+ * @param history the MessageHistory object to update
+ * @return the updated MessageHistory, or null if history is not present
+ */
@Nullable
private static MessageHistory readHistoryFromWire(@NotNull final Wire wire, final MessageHistory history) {
final ValueIn historyValue = readHistoryValue(wire);
- if (historyValue == null) {
+ if (historyValue == null)
return null;
- }
+
+ // Deserialize the message history from the value
historyValue.marshallable(history);
return history;
}
+ /**
+ * Reads the history value from the wire. The method checks if the content
+ * matches the expected history key before proceeding with the read operation.
+ *
+ * @param wire the Wire object to read from
+ * @return the ValueIn object containing the history, or null if the key doesn't match
+ */
@Nullable
private static ValueIn readHistoryValue(@NotNull final Wire wire) {
try (final ScopedResource stlSb = StoreTailer.SBP.get()) {
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/ScanResult.java b/src/main/java/net/openhft/chronicle/queue/impl/single/ScanResult.java
index 8bb6858676..92ea36568b 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/ScanResult.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/ScanResult.java
@@ -17,6 +17,30 @@
*/
package net.openhft.chronicle.queue.impl.single;
+/**
+ * Represents the result of a scan operation within a Chronicle Queue.
+ * This enum provides the possible outcomes when scanning for a specific entry
+ * or state within a queue.
+ */
public enum ScanResult {
- FOUND, NOT_REACHED, NOT_FOUND, END_OF_FILE
+
+ /**
+ * The requested entry was found during the scan.
+ */
+ FOUND,
+
+ /**
+ * The scan has not yet reached the desired position or entry.
+ */
+ NOT_REACHED,
+
+ /**
+ * The requested entry was not found in the queue.
+ */
+ NOT_FOUND,
+
+ /**
+ * The end of the file has been reached during the scan.
+ */
+ END_OF_FILE
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueue.java b/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueue.java
index 91a8ab373d..bae3b9638b 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueue.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueue.java
@@ -15,6 +15,7 @@
* 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.*;
@@ -66,6 +67,14 @@
import static net.openhft.chronicle.wire.Wires.SPB_HEADER_SIZE;
import static net.openhft.chronicle.wire.Wires.acquireBytesScoped;
+/**
+ * SingleChronicleQueue is an implementation of RollingChronicleQueue that supports appending
+ * and reading of data from a file-based queue with roll cycles. This class is responsible
+ * for managing the lifecycle, rolling logic, and the underlying storage.
+ *
+ * It also supports various configurations such as event loop handling, wire types, buffer
+ * management, and replication.
+ */
@SuppressWarnings("this-escape")
public class SingleChronicleQueue extends AbstractCloseable implements RollingChronicleQueue {
@@ -142,6 +151,14 @@ public class SingleChronicleQueue extends AbstractCloseable implements RollingCh
private final long[] chunkCount = {0};
private final SyncMode syncMode;
+
+ /**
+ * Constructs a SingleChronicleQueue with the specified builder configuration.
+ * This constructor sets up various configurations like rolling cycle, epoch,
+ * buffering, path, wire type, and other queue-related settings based on the builder.
+ *
+ * @param builder the SingleChronicleQueueBuilder containing the configuration
+ */
protected SingleChronicleQueue(@NotNull final SingleChronicleQueueBuilder builder) {
try {
rollCycle = builder.rollCycle();
@@ -234,13 +251,20 @@ protected SingleChronicleQueue(@NotNull final SingleChronicleQueueBuilder builde
AnalyticsHolder.instance().sendEvent("started", additionalEventParameters);
- singleThreadedCheckDisabled(true);////
+ singleThreadedCheckDisabled(true);
} catch (Throwable t) {
close();
throw Jvm.rethrow(t);
}
}
+ /**
+ * Calculates the overlap size based on the block size.
+ * Ensures that the overlap size is capped at 1GB (1L << 30).
+ *
+ * @param blockSize the block size for the queue
+ * @return the calculated overlap size
+ */
private static long calcOverlapSize(long blockSize) {
final long overlapSize;
if (blockSize < OS.SAFE_PAGE_SIZE)
@@ -250,23 +274,46 @@ else if (blockSize < OS.SAFE_PAGE_SIZE * 4)
else if (blockSize < 4L << 30)
overlapSize = blockSize / 4;
else
- overlapSize = 1L << 30;
+ overlapSize = 1L << 30; // Maximum overlap size is 1GB
return overlapSize;
}
+ /**
+ * Sets a custom condition to be used for appender creation.
+ *
+ * @param createAppenderCondition the condition to be used for appender creation
+ */
protected void createAppenderCondition(@NotNull Condition createAppenderCondition) {
this.createAppenderCondition = createAppenderCondition;
}
+ /**
+ * Returns the default cycle calculator. The cycle calculator is responsible
+ * for determining the rolling intervals and cycles based on the time zone.
+ *
+ * @param zoneId the ZoneId used for cycle calculation
+ * @return the CycleCalculator instance
+ */
protected CycleCalculator cycleCalculator(ZoneId zoneId) {
return DefaultCycleCalculator.INSTANCE;
}
+ /**
+ * Converts a text string into a file using the queue's path and suffix.
+ *
+ * @param builder the SingleChronicleQueueBuilder containing the path configuration
+ * @return a Function that converts text to a File
+ */
@NotNull
private Function textToFile(@NotNull SingleChronicleQueueBuilder builder) {
return name -> new File(builder.path(), name + SUFFIX);
}
+ /**
+ * Converts a File object into a text string, stripping the queue file suffix.
+ *
+ * @return a Function that converts a File to its name as a String
+ */
@NotNull
private Function fileToText() {
return file -> {
@@ -275,25 +322,42 @@ private Function fileToText() {
};
}
+ /**
+ * Returns the source ID of this queue.
+ *
+ * @return the source ID as an integer
+ */
@Override
public int sourceId() {
return sourceId;
}
/**
- * when using replication to another host, this is the highest last index that has been confirmed to have been read by all of the remote host(s).
+ * Returns the highest last index that has been confirmed to be read by all remote hosts during replication.
+ * If replication is not enabled, returns -1.
+ *
+ * @return the last acknowledged replicated index or -1 if not available
*/
@Override
public long lastAcknowledgedIndexReplicated() {
return lastAcknowledgedIndexReplicated == null ? -1 : lastAcknowledgedIndexReplicated.getVolatileValue(-1);
}
+ /**
+ * Updates the last acknowledged index that has been replicated to all remote hosts.
+ *
+ * @param newValue the new last acknowledged index value
+ */
@Override
public void lastAcknowledgedIndexReplicated(long newValue) {
if (lastAcknowledgedIndexReplicated != null)
lastAcknowledgedIndexReplicated.setMaxValue(newValue);
}
+ /**
+ * Refreshes the directory listing, ensuring it is up-to-date.
+ * Throws an exception if the queue has been closed.
+ */
@Override
public void refreshDirectoryListing() {
throwExceptionIfClosed();
@@ -302,47 +366,85 @@ public void refreshDirectoryListing() {
}
/**
- * when using replication to another host, this is the maximum last index that has been sent to any of the remote host(s).
+ * Returns the maximum last index that has been sent to any remote host during replication.
+ * If replication is not enabled, returns -1.
+ *
+ * @return the last replicated index or -1 if not available
*/
@Override
public long lastIndexReplicated() {
return lastIndexReplicated == null ? -1 : lastIndexReplicated.getVolatileValue(-1);
}
+ /**
+ * Updates the last index that has been replicated to remote hosts.
+ *
+ * @param indexReplicated the new last replicated index value
+ */
@Override
public void lastIndexReplicated(long indexReplicated) {
if (lastIndexReplicated != null)
lastIndexReplicated.setMaxValue(indexReplicated);
}
+ /**
+ * Returns the last index that has been synchronized in milliseconds.
+ * If synchronization is not enabled, returns -1.
+ *
+ * @return the last synchronized index in milliseconds, or -1 if not available
+ */
@Override
public long lastIndexMSynced() {
return lastIndexMSynced == null ? -1 : lastIndexMSynced.getVolatileValue(-1);
}
+ /**
+ * Updates the last index that has been synchronized in milliseconds.
+ *
+ * @param lastIndexMSynced the new last synchronized index in milliseconds
+ */
@Override
public void lastIndexMSynced(long lastIndexMSynced) {
if (this.lastIndexMSynced != null)
this.lastIndexMSynced.setMaxValue(lastIndexMSynced);
}
+ /**
+ * Unsupported operation. Currently, clear is not implemented.
+ */
@Override
public void clear() {
throw new UnsupportedOperationException("Not yet implemented");
}
+ /**
+ * Returns the file representing the queue's directory path.
+ *
+ * @return the File object for the queue's directory
+ */
@Override
@NotNull
public File file() {
return path;
}
+ /**
+ * Returns the absolute path of the queue's directory as a string.
+ *
+ * @return the absolute path of the queue directory
+ */
@NotNull
@Override
public String fileAbsolutePath() {
return fileAbsolutePath;
}
+ /**
+ * Dumps the last header of the last cycle of the queue. This provides debugging
+ * information about the state of the last cycle's header.
+ *
+ * @return a string representation of the last cycle's header
+ */
@Override
public @NotNull String dumpLastHeader() {
StringBuilder sb = new StringBuilder(256);
@@ -352,6 +454,12 @@ public String fileAbsolutePath() {
return sb.toString();
}
+ /**
+ * Dumps the contents of the entire queue, including metadata and each cycle.
+ * This provides a full debugging view of the queue's state.
+ *
+ * @return a string representation of the queue's metadata and cycles
+ */
@NotNull
@Override
public String dump() {
@@ -366,6 +474,14 @@ public String dump() {
return sb.toString();
}
+ /**
+ * Dumps the contents of the queue from a given index range into a Writer.
+ * If there are no more messages in the queue or the target index is beyond the range, it terminates the dump.
+ *
+ * @param writer the Writer to output the queue contents to
+ * @param fromIndex the starting index from where to dump
+ * @param toIndex the ending index where the dump should stop
+ */
@Override
public void dump(@NotNull Writer writer, long fromIndex, long toIndex) {
try {
@@ -382,6 +498,8 @@ public void dump(@NotNull Writer writer, long fromIndex, long toIndex) {
try (ScopedResource> stlBytes = acquireBytesScoped()) {
Bytes> bytes = stlBytes.get();
TextWire text = new TextWire(bytes);
+
+ // Iterate through documents and dump their contents
while (true) {
try (DocumentContext dc = tailer.readingDocument()) {
if (!dc.isPresent()) {
@@ -418,52 +536,91 @@ public void dump(@NotNull Writer writer, long fromIndex, long toIndex) {
}
}
- // Used in testing.
+ /**
+ * Returns the chunk count. Used in testing.
+ *
+ * @return the chunk count
+ */
public long chunkCount() {
return chunkCount[0];
}
+ /**
+ * Returns the number of index entries.
+ *
+ * @return the index count
+ */
@Override
public int indexCount() {
return indexCount;
}
+ /**
+ * Returns the spacing between index entries.
+ *
+ * @return the index spacing
+ */
@Override
public int indexSpacing() {
return indexSpacing;
}
+ /**
+ * Returns the epoch time used for roll cycles.
+ *
+ * @return the epoch time
+ */
@Override
public long epoch() {
return epoch;
}
+ /**
+ * Returns the roll cycle used by the queue.
+ *
+ * @return the roll cycle
+ */
@Override
@NotNull
public RollCycle rollCycle() {
return this.rollCycle;
}
+ /**
+ * Returns the interval for delta checkpointing.
+ *
+ * @return the delta checkpoint interval
+ */
@Override
public int deltaCheckpointInterval() {
return deltaCheckpointInterval;
}
/**
- * @return if we uses async mode to buffer the appends, the Excerpts are written to the Chronicle Queue using a background thread
+ * Indicates whether the queue uses asynchronous buffering for appending.
+ * In asynchronous mode, appends are handled by a background thread.
+ *
+ * @return true if the queue uses asynchronous buffering, false otherwise
*/
public boolean buffered() {
return this.isBuffered;
}
+ /**
+ * Returns the event loop used by the queue.
+ *
+ * @return the event loop
+ */
@NotNull
public EventLoop eventLoop() {
return this.eventLoop;
}
/**
- * Construct a new {@link ExcerptAppender} once the {@link #createAppenderCondition} is met.
- * @return The new appender
+ * Constructs a new {@link ExcerptAppender} once the {@link #createAppenderCondition} is met.
+ *
+ * @return the new ExcerptAppender
+ * @throws InterruptedRuntimeException if the thread is interrupted while waiting for the condition
*/
@NotNull
protected ExcerptAppender createNewAppenderOnceConditionIsMet() {
@@ -482,7 +639,7 @@ protected ExcerptAppender createNewAppenderOnceConditionIsMet() {
* This is protected so sub-classes can override the creation of an appender,
* to create a new appender, sub-classes should call {@link #createNewAppenderOnceConditionIsMet()}
*
- * @return The new appender
+ * @return the new ExcerptAppender
*/
@NotNull
protected ExcerptAppender constructAppender() {
@@ -490,6 +647,11 @@ protected ExcerptAppender constructAppender() {
return new StoreAppender(this, newPool, checkInterrupts);
}
+ /**
+ * Returns the StoreFileListener used by the queue.
+ *
+ * @return the StoreFileListener
+ */
protected StoreFileListener storeFileListener() {
return storeFileListener;
}
@@ -499,6 +661,12 @@ WireStoreSupplier storeSupplier() {
return storeSupplier;
}
+ /**
+ * Acquires an {@link ExcerptAppender} from a thread-local pool of appenders.
+ * If the queue is in read-only mode, an IllegalStateException is thrown.
+ *
+ * @return the ExcerptAppender
+ */
@SuppressWarnings("deprecation")
@NotNull
@Override
@@ -506,6 +674,14 @@ public ExcerptAppender acquireAppender() {
return ThreadLocalAppender.acquireThreadLocalAppender(this);
}
+ /**
+ * Acquires a thread-local ExcerptAppender for the given queue.
+ * If the queue is in read-only mode, an IllegalStateException is thrown.
+ *
+ * @param queue the SingleChronicleQueue for which the appender is acquired
+ * @return the ExcerptAppender
+ * @throws IllegalStateException if the queue is in read-only mode
+ */
@NotNull
ExcerptAppender acquireThreadLocalAppender(@NotNull SingleChronicleQueue queue) {
queue.throwExceptionIfClosed();
@@ -520,6 +696,13 @@ ExcerptAppender acquireThreadLocalAppender(@NotNull SingleChronicleQueue queue)
return res;
}
+ /**
+ * Creates a new {@link ExcerptAppender}. If the queue is in read-only mode,
+ * an {@link IllegalStateException} is thrown.
+ *
+ * @return a new ExcerptAppender
+ * @throws IllegalStateException if the queue is read-only
+ */
@NotNull
@Override
public ExcerptAppender createAppender() {
@@ -532,12 +715,11 @@ public ExcerptAppender createAppender() {
}
/**
- * @return the {@link WriteLock} that is used to lock writes to the queue. This is the mechanism used to
- * coordinate writes from multiple threads and processes.
- * This lock should only be held for a short time, as it will block progress of any writers to
- * this queue. The default behaviour of {@link TableStoreWriteLock} is to override the lock after a timeout.
+ * Returns the {@link WriteLock} used to lock writes to the queue.
+ * This lock is used to coordinate writes from multiple threads and processes.
+ * It should be held only briefly as it blocks any other writers.
*
- *
This is also used to protect rolling to the next cycle
+ * @return the WriteLock for the queue
*/
@NotNull
WriteLock writeLock() {
@@ -545,13 +727,24 @@ WriteLock writeLock() {
}
/**
- * @return the {@link WriteLock} that is used to lock appends. This is only used by Queue Enterprise
- * sink replication handlers. See Queue Enterprise docs for more details.
+ * Returns the {@link WriteLock} used for appends, primarily for Queue Enterprise
+ * sink replication handlers.
+ *
+ * @return the WriteLock used for appends
*/
public WriteLock appendLock() {
return appendLock;
}
+ /**
+ * Creates an {@link ExcerptTailer} with a specific ID. The tailer will use the
+ * provided ID to track its position, and the preconditions for creating a tailer
+ * are verified before initialization.
+ *
+ * @param id the identifier for the tailer
+ * @return a new ExcerptTailer
+ * @throws NamedTailerNotAvailableException if the tailer is not available due to replication locks
+ */
@NotNull
@Override
public ExcerptTailer createTailer(String id) {
@@ -567,6 +760,13 @@ public ExcerptTailer createTailer(String id) {
return storeTailer;
}
+ /**
+ * Verifies the preconditions before creating a tailer. Ensures the queue is not
+ * closed and handles specific cases for replicated named tailers.
+ *
+ * @param id the identifier for the tailer, may be null for non-named tailers
+ * @throws NamedTailerNotAvailableException if a named tailer is not available
+ */
private void verifyTailerPreconditions(String id) {
// Preconditions for all tailer types
throwExceptionIfClosed();
@@ -578,17 +778,36 @@ private void verifyTailerPreconditions(String id) {
}
}
+ /**
+ * Acquires a LongValue object for a given ID from the metadata store.
+ * This is typically used to track indexes for specific IDs.
+ *
+ * @param id the identifier for which to acquire the index
+ * @return a LongValue representing the index for the given ID
+ */
@Override
@NotNull
public LongValue indexForId(@NotNull String id) {
return this.metaStore.doWithExclusiveLock((ts) -> ts.acquireValueFor("index." + id, 0L));
}
+ /**
+ * Acquires the version index for a given ID from the metadata store.
+ *
+ * @param id the identifier for which to acquire the version index
+ * @return a LongValue representing the version index for the given ID
+ */
@NotNull
public LongValue indexVersionForId(@NotNull String id) {
return this.metaStore.doWithExclusiveLock((ts) -> ts.acquireValueFor(String.format(INDEX_VERSION_FORMAT, id), -1L));
}
+ /**
+ * Creates a write lock specifically for versioned indexes identified by the given ID.
+ *
+ * @param id the identifier for which to create the write lock
+ * @return a new TableStoreWriteLock for the version index
+ */
@NotNull
public TableStoreWriteLock versionIndexLockForId(@NotNull String id) {
return new TableStoreWriteLock(
@@ -599,6 +818,12 @@ public TableStoreWriteLock versionIndexLockForId(@NotNull String id) {
);
}
+ /**
+ * Creates a default {@link ExcerptTailer} for the queue. If the queue is closed,
+ * an exception is thrown.
+ *
+ * @return a new ExcerptTailer
+ */
@NotNull
@Override
public ExcerptTailer createTailer() {
@@ -607,6 +832,17 @@ public ExcerptTailer createTailer() {
return createTailer(null);
}
+ /**
+ * Retrieves or creates a {@link SingleChronicleQueueStore} for a specific cycle.
+ * The store is acquired from the pool, and if createIfAbsent is true, a new store
+ * is created if it doesn't already exist.
+ *
+ * @param cycle the cycle for which to acquire the store
+ * @param epoch the epoch time
+ * @param createIfAbsent whether to create a store if it doesn't exist
+ * @param oldStore the previous store, if available
+ * @return the acquired or created SingleChronicleQueueStore, or null if unavailable
+ */
@Nullable
@Override
public final SingleChronicleQueueStore storeForCycle(int cycle, final long epoch, boolean createIfAbsent, SingleChronicleQueueStore oldStore) {
@@ -615,6 +851,14 @@ public final SingleChronicleQueueStore storeForCycle(int cycle, final long epoch
oldStore);
}
+ /**
+ * Returns the next cycle in the specified direction.
+ *
+ * @param cycle the current cycle
+ * @param direction the direction (forward or backward) in which to find the next cycle
+ * @return the next cycle
+ * @throws ParseException if there is an error parsing cycle data
+ */
@Override
public int nextCycle(int cycle, @NotNull TailerDirection direction) throws ParseException {
throwExceptionIfClosed();
@@ -626,10 +870,10 @@ public int nextCycle(int cycle, @NotNull TailerDirection direction) throws Parse
* Will give you the number of excerpts between 2 index?s ( as exists on the current file system ). If intermediate chronicle files are removed
* this will effect the result.
*
- * @param fromIndex the lower index
- * @param toIndex the higher index
- * @return will give you the number of excerpts between 2 index?s. It?s not as simple as just subtracting one number from the other.
- * @throws IllegalStateException if we are not able to read the chronicle files
+ * @param fromIndex the starting index
+ * @param toIndex the ending index
+ * @return the number of excerpts between the two indexes
+ * @throws IllegalStateException if unable to read the Chronicle files
*/
@Override
public long countExcerpts(long fromIndex, long toIndex) {
@@ -690,13 +934,15 @@ public long countExcerpts(long fromIndex, long toIndex) {
// is 1 except rather than zero
long l = tailer.excerptsInCycle(lowerCycle);
result += (l - lowerSeqNum);
- } else
+ } else {
throw new IllegalStateException("Cycle not found, lower-cycle=" + Long.toHexString(lowerCycle));
+ }
if (cycles.last() == upperCycle) {
result += upperSeqNum;
- } else
+ } else {
throw new IllegalStateException("Cycle not found, upper-cycle=" + Long.toHexString(upperCycle));
+ }
if (cycles.size() == 2)
return result;
@@ -711,12 +957,24 @@ public long countExcerpts(long fromIndex, long toIndex) {
}
}
+ /**
+ * Lists the cycles between the specified lower and upper cycle values.
+ *
+ * @param lowerCycle the starting cycle
+ * @param upperCycle the ending cycle
+ * @return a NavigableSet of Long values representing the cycles between the lower and upper cycle
+ */
public NavigableSet listCyclesBetween(int lowerCycle, int upperCycle) {
throwExceptionIfClosed();
return pool.listCyclesBetween(lowerCycle, upperCycle);
}
+ /**
+ * Adds a {@link Closeable} listener that will be closed when this queue is closed.
+ *
+ * @param key the Closeable to add to the close listeners
+ */
public void addCloseListener(Closeable key) {
synchronized (closers) {
if (!closers.isEmpty())
@@ -725,6 +983,10 @@ public void addCloseListener(Closeable key) {
}
}
+ /**
+ * Performs the closing operations for this queue. All resources are closed in a synchronized
+ * block, and special care is taken to close the event loop and other important components.
+ */
@Override
protected void performClose() {
synchronized (closers) {
@@ -747,11 +1009,16 @@ protected void performClose() {
closeQuietly(storeSupplier);
}
- // close it if we created it.
+ // If the event loop was created on demand, close it
if (eventLoop instanceof OnDemandEventLoop)
eventLoop.close();
}
+ /**
+ * Ensures that resources are properly closed during finalization if they were not closed earlier.
+ *
+ * @throws Throwable if there is an error during finalization
+ */
@SuppressWarnings({"deprecation", "removal"})
@Override
protected void finalize() throws Throwable {
@@ -759,20 +1026,41 @@ protected void finalize() throws Throwable {
warnAndCloseIfNotClosed();
}
+ /**
+ * Closes the specified {@link SingleChronicleQueueStore}.
+ *
+ * @param store the store to close, may be null
+ */
public final void closeStore(@Nullable SingleChronicleQueueStore store) {
if (store != null)
this.pool.closeStore(store);
}
+ /**
+ * Returns the current cycle based on the roll cycle, time provider, and epoch.
+ *
+ * @return the current cycle
+ */
@Override
public final int cycle() {
return cycleCalculator.currentCycle(rollCycle, time, epoch);
}
+ /**
+ * Returns the current cycle using a specified time provider.
+ *
+ * @param timeProvider the TimeProvider to use for cycle calculation
+ * @return the current cycle
+ */
public final int cycle(TimeProvider timeProvider) {
return cycleCalculator.currentCycle(rollCycle, timeProvider, epoch);
}
+ /**
+ * Returns the first index of the queue. If the first cycle is not available, returns Long.MAX_VALUE.
+ *
+ * @return the first index of the queue
+ */
@Override
public long firstIndex() {
int cycle = firstCycle();
@@ -782,6 +1070,12 @@ public long firstIndex() {
return rollCycle().toIndex(cycle, 0);
}
+ /**
+ * Returns the last index in the queue. This is a slow implementation that uses
+ * a {@link ExcerptTailer} to find the last non-metadata document.
+ *
+ * @return the last index in the queue, or -1 if no documents are found
+ */
@Override
public long lastIndex() {
// This is a slow implementation that gets a Tailer/DocumentContext to find the last index
@@ -800,7 +1094,8 @@ public long lastIndex() {
}
/**
- * This method creates a tailer and count the number of messages between the start of the queue ( see @link firstIndex() ) and the end.
+ * Counts the number of messages in the queue by calculating the number of excerpts
+ * between the start (firstIndex) and end (lastIndex) of the queue.
*
* @return the number of messages in the queue
*/
@@ -815,11 +1110,20 @@ public long entryCount() {
}
}
+ /**
+ * Returns the list of files in the queue's directory.
+ *
+ * @return an array of file names in the directory, or null if an error occurs
+ */
@Nullable
String[] getList() {
return path.list();
}
+ /**
+ * Sets the first and last cycle values based on the current system time and the directory listing.
+ * The directory listing is refreshed if necessary, either periodically or forced based on the time.
+ */
private void setFirstAndLastCycle() {
long now = time.currentTimeMillis();
if (now <= directoryListing.lastRefreshTimeMS()) {
@@ -830,6 +1134,12 @@ private void setFirstAndLastCycle() {
directoryListing.refresh(force);
}
+ /**
+ * Returns the first cycle available in the queue by setting the first and last cycle
+ * and then retrieving the minimum created cycle from the directory listing.
+ *
+ * @return the first cycle in the queue
+ */
@Override
public int firstCycle() {
setFirstAndLastCycle();
@@ -845,39 +1155,74 @@ void onRoll(int cycle) {
directoryListing.onRoll(cycle);
}
+ /**
+ * Returns the last cycle available in the queue by setting the first and last cycle
+ * and then retrieving the maximum created cycle from the directory listing.
+ *
+ * @return the last cycle in the queue
+ */
@Override
public int lastCycle() {
setFirstAndLastCycle();
return directoryListing.getMaxCreatedCycle();
}
+ /**
+ * Returns the consumer that handles {@link BytesRingBufferStats}.
+ *
+ * @return the consumer for ring buffer statistics
+ */
@NotNull
public Consumer onRingBufferStats() {
return this.onRingBufferStats;
}
+ /**
+ * Returns the block size used by the queue.
+ *
+ * @return the block size
+ */
public long blockSize() {
return this.blockSize;
}
- // *************************************************************************
- //
- // *************************************************************************
-
+ /**
+ * Returns the overlap size for memory mapping.
+ *
+ * @return the overlap size
+ */
public long overlapSize() {
return this.overlapSize;
}
+ /**
+ * Returns the {@link WireType} used by the queue.
+ *
+ * @return the WireType
+ */
@NotNull
@Override
public WireType wireType() {
return wireType;
}
+ /**
+ * Returns the buffer capacity used by the queue.
+ *
+ * @return the buffer capacity
+ */
public long bufferCapacity() {
return this.bufferCapacity;
}
+ /**
+ * Creates a {@link MappedFile} for the given file. The mapped file is created using the
+ * block size and overlap size for memory mapping. Sync mode is also set for the file.
+ *
+ * @param file the file to map
+ * @return the MappedFile instance
+ * @throws FileNotFoundException if the file cannot be found
+ */
@NotNull
@PackageLocal
MappedFile mappedFile(File file) throws FileNotFoundException {
@@ -887,10 +1232,20 @@ MappedFile mappedFile(File file) throws FileNotFoundException {
return mappedFile;
}
+ /**
+ * Returns whether the queue is in read-only mode.
+ *
+ * @return true if the queue is read-only, false otherwise
+ */
boolean isReadOnly() {
return readOnly;
}
+ /**
+ * Returns a string representation of the queue, showing the source ID and file path.
+ *
+ * @return a string representation of the queue
+ */
@NotNull
@Override
public String toString() {
@@ -900,26 +1255,53 @@ public String toString() {
'}';
}
+ /**
+ * Returns the {@link TimeProvider} used by the queue.
+ *
+ * @return the TimeProvider
+ */
@NotNull
public TimeProvider time() {
return time;
}
+ /**
+ * Creates a function that converts a file name into a cycle by parsing the name and removing the suffix.
+ *
+ * @return a function that converts a file name to a cycle
+ */
@NotNull
private ToIntFunction fileNameToCycleFunction() {
return name -> dateCache.parseCount(name.substring(0, name.length() - SUFFIX.length()));
}
+ /**
+ * Removes the specified {@link StoreTailer} from the close listeners.
+ *
+ * @param storeTailer the StoreTailer to remove
+ */
void removeCloseListener(final StoreTailer storeTailer) {
synchronized (closers) {
closers.remove(storeTailer);
}
}
+ /**
+ * Returns the metadata store used by the queue.
+ *
+ * @return the TableStore for metadata
+ */
public TableStore metaStore() {
return metaStore;
}
+ /**
+ * Puts a new value in the table store for the given key and index. If the index is Long.MIN_VALUE,
+ * it sets the value as volatile, otherwise, it sets the max value.
+ *
+ * @param key the key for the entry in the table store
+ * @param index the index value to set
+ */
public void tableStorePut(CharSequence key, long index) {
LongValue longValue = tableStoreAcquire(key, index);
if (longValue == null) return;
@@ -929,6 +1311,14 @@ public void tableStorePut(CharSequence key, long index) {
longValue.setMaxValue(index);
}
+ /**
+ * Acquires a {@link LongValue} from the table store for the given key, creating a new one
+ * if necessary. If the key does not exist, a new LongValue is created with the default value.
+ *
+ * @param key the key for the entry
+ * @param defaultValue the default value to use if the key does not exist
+ * @return the acquired LongValue, or null if an error occurs
+ */
@Nullable
protected LongValue tableStoreAcquire(CharSequence key, long defaultValue) {
try (final ScopedResource> bytesTl = acquireBytesScoped()) {
@@ -951,12 +1341,27 @@ protected LongValue tableStoreAcquire(CharSequence key, long defaultValue) {
}
}
+ /**
+ * Gets the value for the given key from the table store. If the key does not exist,
+ * returns Long.MIN_VALUE.
+ *
+ * @param key the key for the entry in the table store
+ * @return the value associated with the key, or Long.MIN_VALUE if not found
+ */
public long tableStoreGet(CharSequence key) {
LongValue longValue = tableStoreAcquire(key, Long.MIN_VALUE);
if (longValue == null) return Long.MIN_VALUE;
return longValue.getVolatileValue();
}
+ /**
+ * Converts a {@link CharSequence} key into a {@link BytesStore}. If the key is already
+ * a BytesStore, it is cast, otherwise the key is appended to a Bytes instance.
+ *
+ * @param key the key to convert
+ * @param bytes the Bytes instance used for conversion
+ * @return the BytesStore representation of the key
+ */
@SuppressWarnings("unchecked")
private BytesStore,Void> asBytes(CharSequence key, Bytes bytes) {
return key instanceof BytesStore
@@ -964,21 +1369,46 @@ private BytesStore,Void> asBytes(CharSequence key, Bytes bytes) {
: bytes.append(key);
}
+ /**
+ * A cached structure that stores cycle-related information, including the directory modification count
+ * and a map of cycle numbers to files.
+ */
private static final class CachedCycleTree {
private final long directoryModCount;
private final NavigableMap cachedCycleTree;
+ /**
+ * Constructs a CachedCycleTree with the specified directory modification count and cached cycle tree.
+ *
+ * @param directoryModCount the modification count of the directory
+ * @param cachedCycleTree the cached map of cycles to files
+ */
CachedCycleTree(final long directoryModCount, final NavigableMap cachedCycleTree) {
this.directoryModCount = directoryModCount;
this.cachedCycleTree = cachedCycleTree;
}
}
+ /**
+ * StoreSupplier is responsible for supplying {@link SingleChronicleQueueStore} instances
+ * for specific cycles. It manages the mapping of files to memory and caches these mappings.
+ * This class also handles the creation and retrieval of stores for different cycles.
+ */
class StoreSupplier extends AbstractCloseable implements WireStoreSupplier {
+
+ // A cached tree structure to store cycle-related data.
private final AtomicReference cachedTree = new AtomicReference<>();
+
+ // A cache for managing MappedFile and MappedBytes, used to map files into memory.
private final ReferenceCountedCache mappedFileCache;
+
+ // Indicates whether the queue path exists on disk.
private boolean queuePathExists;
+ /**
+ * Constructor for StoreSupplier. It initializes the mapped file cache
+ * and disables single-threaded checks.
+ */
private StoreSupplier() {
mappedFileCache = new ReferenceCountedCache<>(
MappedBytes::mappedBytes,
@@ -986,6 +1416,16 @@ private StoreSupplier() {
singleThreadedCheckDisabled(true);
}
+ /**
+ * Acquires a {@link SingleChronicleQueueStore} for the specified cycle.
+ * If the store doesn't exist and the strategy is {@link CreateStrategy.CREATE}, it will create a new store.
+ *
+ * @param cycle the cycle to acquire the store for
+ * @param createStrategy the strategy for creating or reading the store
+ * @return the acquired SingleChronicleQueueStore or null if the store doesn't exist and the strategy is not CREATE
+ * @throws IOException in case of IO errors
+ * @throws TimeoutException if acquiring the store times out
+ */
@Override
public SingleChronicleQueueStore acquire(int cycle, CreateStrategy createStrategy) {
throwExceptionIfClosed();
@@ -1090,6 +1530,14 @@ public SingleChronicleQueueStore acquire(int cycle, CreateStrategy createStrateg
}
}
+ /**
+ * Reads the value of the wire store from the wire. Ensures the first message
+ * is the header, and throws an exception if the header is not present.
+ *
+ * @param wire the Wire to read from
+ * @return the ValueIn object containing the wire store value
+ * @throws StreamCorruptedException if the first message is not the header
+ */
@NotNull
private ValueIn readWireStoreValue(@NotNull Wire wire) throws StreamCorruptedException {
try (ScopedResource stlSb = Wires.acquireStringBuilderScoped()) {
@@ -1102,7 +1550,13 @@ private ValueIn readWireStoreValue(@NotNull Wire wire) throws StreamCorruptedExc
}
}
- // Rename un-acquirable segment file to make sure no data is lost to try and recreate the segment
+ /**
+ * Renames an unacquirable cycle file to a backup file with a discard suffix, and attempts to recreate the segment.
+ *
+ * @param cycle the cycle number
+ * @param cycleFile the file that couldn't be acquired
+ * @return the strategy to either create a new file or use it as read-only
+ */
private CreateStrategy backupCycleFile(int cycle, File cycleFile) {
File cycleFileDiscard = new File(cycleFile.getParentFile(),
String.format("%s-%d%s", cycleFile.getName(), System.currentTimeMillis(), DISCARD_FILE_SUFFIX));
@@ -1118,6 +1572,15 @@ private CreateStrategy backupCycleFile(int cycle, File cycleFile) {
return success ? CreateStrategy.CREATE : CreateStrategy.READ_ONLY;
}
+ /**
+ * This method initializes the index for the wire store and updates the header.
+ * It ensures that all data structures are properly prepared before publishing the initial header.
+ * It also notifies the directory listing of the new file creation.
+ *
+ * @param wire the Wire object used for writing
+ * @param cycle the cycle number for which the index is created
+ * @param wireStore the wire store for the current cycle
+ */
@SuppressWarnings("deprecation")
private void createIndexThenUpdateHeader(Wire wire, int cycle, SingleChronicleQueueStore wireStore) {
// Should very carefully prepare all data structures before publishing initial header
@@ -1132,11 +1595,19 @@ private void createIndexThenUpdateHeader(Wire wire, int cycle, SingleChronicleQu
directoryListing.onFileCreated(path, cycle);
}
+ /**
+ * Closes the StoreSupplier by releasing the mapped file cache resources.
+ */
@Override
protected void performClose() {
mappedFileCache.close();
}
+ /**
+ * Creates a new file at the specified path. If the parent directory does not exist, it is created.
+ *
+ * @param path the path of the file to create
+ */
private void createFile(final File path) {
try {
File dir = path.getParentFile();
@@ -1152,7 +1623,11 @@ private void createFile(final File path) {
}
/**
- * @return cycleTree for the current directory / parentFile
+ * Returns a map of cycle files in the current directory. If necessary, it refreshes
+ * the directory listing and updates the cache.
+ *
+ * @param force whether to forcefully refresh the directory listing
+ * @return a NavigableMap of cycle numbers and their corresponding files
*/
@NotNull
private NavigableMap cycleTree(final boolean force) {
@@ -1194,6 +1669,14 @@ private NavigableMap cycleTree(final boolean force) {
return cachedValue.cachedCycleTree;
}
+ /**
+ * Finds the next cycle in the given direction. If the current cycle is no longer
+ * present in the tree, it logs an error.
+ *
+ * @param currentCycle the current cycle
+ * @param direction the direction to move (FORWARD or BACKWARD)
+ * @return the next cycle in the given direction
+ */
@Override
public int nextCycle(int currentCycle, @NotNull TailerDirection direction) {
throwExceptionIfClosed();
@@ -1242,6 +1725,12 @@ public int nextCycle(int currentCycle, @NotNull TailerDirection direction) {
}
}
+ /**
+ * Converts a map entry to a cycle number.
+ *
+ * @param entry the map entry
+ * @return the cycle number, or -1 if the entry is null
+ */
private int toCycle(@Nullable Map.Entry entry) {
if (entry == null || entry.getValue() == null)
return -1;
@@ -1249,11 +1738,11 @@ private int toCycle(@Nullable Map.Entry entry) {
}
/**
- * the cycles between a range, inclusive
+ * Returns a set of cycles between the given lower and upper cycle numbers, inclusive.
*
- * @param lowerCycle the lower cycle inclusive
- * @param upperCycle the upper cycle inclusive
- * @return the cycles between a range, inclusive
+ * @param lowerCycle the lower cycle (inclusive)
+ * @param upperCycle the upper cycle (inclusive)
+ * @return a NavigableSet of cycles between the given range
*/
@Override
public NavigableSet cycles(int lowerCycle, int upperCycle) {
@@ -1267,13 +1756,27 @@ public NavigableSet cycles(int lowerCycle, int upperCycle) {
return tree.subMap(lowerKey, true, upperKey, true).navigableKeySet();
}
+ /**
+ * Determines if a {@link SingleChronicleQueueStore} can be reused based on its cycle
+ * and the current directory listing.
+ *
+ * @param store the store to check
+ * @return true if the store can be reused, false otherwise
+ */
@Override
public boolean canBeReused(@NotNull SingleChronicleQueueStore store) {
- setFirstAndLastCycle();
+ setFirstAndLastCycle(); // Update the cycle range
int cycle = store.cycle();
return !store.isClosed() && cycle >= directoryListing.getMinCreatedCycle() && cycle <= directoryListing.getMaxCreatedCycle();
}
+ /**
+ * Converts a cycle number to a key used in the cycle tree.
+ *
+ * @param cyle the cycle number
+ * @param m the label to use in case of an error
+ * @return the key for the cycle tree
+ */
private Long toKey(int cyle, String m) {
final File file = dateCache.resourceFor(cyle).path;
if (!file.exists())
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilder.java b/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilder.java
index b9bf032785..7179869f62 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilder.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilder.java
@@ -60,6 +60,15 @@
import static net.openhft.chronicle.core.pool.ClassAliasPool.CLASS_ALIASES;
import static net.openhft.chronicle.queue.impl.single.SingleChronicleQueue.QUEUE_METADATA_FILE;
+/**
+ * Builder class for creating instances of {@link SingleChronicleQueue}.
+ * This class provides various configuration options such as buffer modes, wire type,
+ * roll cycle, and event loop settings. It is used to construct a queue with
+ * customized settings.
+ *
+ * This class uses the builder pattern to allow flexible configuration and
+ * creation of {@link SingleChronicleQueue} instances.
+ */
@SuppressWarnings("deprecation")
public class SingleChronicleQueueBuilder extends SelfDescribingMarshallable implements Cloneable, Builder {
public static final long SMALL_BLOCK_SIZE = OS.isWindows() ? OS.SAFE_PAGE_SIZE : OS.pageSize(); // the smallest safe block size on Windows 8+
@@ -70,6 +79,7 @@ public class SingleChronicleQueueBuilder extends SelfDescribingMarshallable impl
private static final Supplier TIMING_PAUSER_SUPPLIER = DefaultPauserSupplier.INSTANCE;
static {
+ // Registering class aliases for serialization/deserialization
CLASS_ALIASES.addAlias(WireType.class);
CLASS_ALIASES.addAlias(SCQMeta.class, "SCQMeta");
CLASS_ALIASES.addAlias(SCQRoll.class, "SCQSRoll");
@@ -121,7 +131,7 @@ public class SingleChronicleQueueBuilder extends SelfDescribingMarshallable impl
private transient TableStore metaStore;
- // enterprise stuff
+ // Enterprise-specific configurations
private int deltaCheckpointInterval = -1;
private Supplier, Bytes>>> encodingSupplier;
private Supplier, Bytes>>> decodingSupplier;
@@ -150,24 +160,45 @@ protected SingleChronicleQueueBuilder() {
*/
public static void addAliases() {
- // static initialiser.
+ // This is handled in the static initializer.
}
/**
- * @return an empty builder
+ * Creates a new builder instance for configuring a {@link SingleChronicleQueue}.
+ *
+ * @return a new instance of SingleChronicleQueueBuilder
*/
public static SingleChronicleQueueBuilder builder() {
return new SingleChronicleQueueBuilder();
}
+ /**
+ * Creates a new builder instance with the specified path and wire type.
+ *
+ * @param path the path to the queue directory
+ * @param wireType the wire type for serialization
+ * @return a new instance of SingleChronicleQueueBuilder
+ */
@NotNull
public static SingleChronicleQueueBuilder builder(@NotNull Path path, @NotNull WireType wireType) {
return builder(path.toFile(), wireType);
}
+ /**
+ * Creates a new builder instance with the specified file and wire type.
+ * If the file is a specific queue file, a warning is logged, and the parent directory is used as the path.
+ * Otherwise, the provided directory is used.
+ *
+ * @param file the file or directory to be used for the queue
+ * @param wireType the wire type for serialization
+ * @return a configured {@link SingleChronicleQueueBuilder} instance
+ * @throws IllegalArgumentException if the file is not a valid queue file
+ */
@NotNull
public static SingleChronicleQueueBuilder builder(@NotNull File file, @NotNull WireType wireType) {
SingleChronicleQueueBuilder result = builder().wireType(wireType);
+
+ // If the file is a specific queue file, warn the user and use the parent directory
if (file.isFile()) {
if (!file.getName().endsWith(SingleChronicleQueue.SUFFIX)) {
throw new IllegalArgumentException("Invalid file type: " + file.getName());
@@ -178,38 +209,81 @@ public static SingleChronicleQueueBuilder builder(@NotNull File file, @NotNull W
+ file.getParentFile());
result.path(file.getParentFile());
- } else
+ } else {
result.path(file);
-
+ }
return result;
}
+ /**
+ * Creates a new builder with the default wire type of {@link WireType#BINARY_LIGHT}.
+ *
+ * @return a {@link SingleChronicleQueueBuilder} instance with binary wire type
+ */
public static SingleChronicleQueueBuilder single() {
SingleChronicleQueueBuilder builder = builder();
builder.wireType(WireType.BINARY_LIGHT);
return builder;
}
+ /**
+ * Creates a new builder with a binary wire type for the specified base path.
+ *
+ * @param basePath the base path for the queue
+ * @return a {@link SingleChronicleQueueBuilder} instance
+ */
public static SingleChronicleQueueBuilder single(@NotNull String basePath) {
return binary(basePath);
}
+ /**
+ * Creates a new builder with a binary wire type for the specified base path as a {@link File}.
+ *
+ * @param basePath the base path for the queue
+ * @return a {@link SingleChronicleQueueBuilder} instance
+ */
public static SingleChronicleQueueBuilder single(@NotNull File basePath) {
return binary(basePath);
}
+ /**
+ * Creates a new builder with a binary wire type for the specified path as a {@link Path}.
+ *
+ * @param path the path for the queue
+ * @return a {@link SingleChronicleQueueBuilder} instance
+ */
public static SingleChronicleQueueBuilder binary(@NotNull Path path) {
return binary(path.toFile());
}
+ /**
+ * Creates a new builder with a binary wire type for the specified base path.
+ *
+ * @param basePath the base path for the queue
+ * @return a {@link SingleChronicleQueueBuilder} instance
+ */
public static SingleChronicleQueueBuilder binary(@NotNull String basePath) {
return binary(new File(basePath));
}
+ /**
+ * Creates a new builder with a binary wire type for the specified base path as a {@link File}.
+ *
+ * @param basePathFile the base path for the queue
+ * @return a {@link SingleChronicleQueueBuilder} instance
+ */
public static SingleChronicleQueueBuilder binary(@NotNull File basePathFile) {
return builder(basePathFile, WireType.BINARY_LIGHT);
}
+ /**
+ * Creates a new {@link SingleChronicleQueueStore} for the given queue and wire.
+ * This method initializes the store with the appropriate configuration and writes the header.
+ *
+ * @param queue the queue for which the store is created
+ * @param wire the wire to be used for the store
+ * @return a newly created {@link SingleChronicleQueueStore}
+ */
@NotNull
static SingleChronicleQueueStore createStore(@NotNull RollingChronicleQueue queue,
@NotNull Wire wire) {
@@ -225,10 +299,21 @@ static SingleChronicleQueueStore createStore(@NotNull RollingChronicleQueue queu
return wireStore;
}
+ /**
+ * Checks if enterprise features are available.
+ *
+ * @return true if enterprise features are available, false otherwise
+ */
public static boolean areEnterpriseFeaturesAvailable() {
return ENTERPRISE_QUEUE_CONSTRUCTOR != null;
}
+ /**
+ * Loads the default roll cycle based on the system property {@code QueueSystemProperties.DEFAULT_ROLL_CYCLE_PROPERTY}.
+ * If no property is set, the default roll cycle is {@link RollCycles#DEFAULT}.
+ *
+ * @return the default {@link RollCycle}
+ */
private static RollCycle loadDefaultRollCycle() {
String rollCycleProperty = Jvm.getProperty(QueueSystemProperties.DEFAULT_ROLL_CYCLE_PROPERTY);
if (null == rollCycleProperty) {
@@ -240,6 +325,7 @@ private static RollCycle loadDefaultRollCycle() {
try {
Class> rollCycleClass = Class.forName(rollCyclePropertyParts[0]);
if (Enum.class.isAssignableFrom(rollCycleClass)) {
+ // Handle roll cycle as an enum
if (rollCyclePropertyParts.length < 2) {
Jvm.warn().on(SingleChronicleQueueBuilder.class,
"Default roll cycle configured as enum, but enum value not specified: " + rollCycleProperty);
@@ -256,6 +342,7 @@ private static RollCycle loadDefaultRollCycle() {
}
}
} else {
+ // Handle roll cycle as a class instance
@SuppressWarnings("unchecked")
Object instance = ObjectUtils.newInstance(rollCycleClass);
if (instance instanceof RollCycle) {
@@ -274,10 +361,23 @@ private static RollCycle loadDefaultRollCycle() {
return RollCycles.DEFAULT;
}
+ /**
+ * Returns the store factory used to create wire stores.
+ *
+ * @return the {@link WireStoreFactory}
+ */
public WireStoreFactory storeFactory() {
return storeFactory;
}
+ /**
+ * Builds a new instance of {@link SingleChronicleQueue} based on the current configuration.
+ * This method first checks for enterprise feature requests, builds the appropriate type of queue,
+ * and performs any post-build tasks.
+ *
+ * @return a configured {@link SingleChronicleQueue} instance
+ * @throws IllegalStateException if enterprise features are requested but not available
+ */
@NotNull
public SingleChronicleQueue build() {
preBuild();
@@ -296,6 +396,13 @@ public SingleChronicleQueue build() {
return chronicleQueue;
}
+ /**
+ * Performs post-build tasks such as setting the appender condition.
+ * The condition is added after the queue is constructed to avoid circular dependencies
+ * by passing `this` during construction.
+ *
+ * @param chronicleQueue the queue that was just built
+ */
private void postBuild(@NotNull SingleChronicleQueue chronicleQueue) {
if (!readOnly()) {
/*
@@ -306,6 +413,13 @@ private void postBuild(@NotNull SingleChronicleQueue chronicleQueue) {
}
}
+ /**
+ * Checks if any enterprise-only features have been requested.
+ * If such features are requested, this method logs a warning and indicates that the
+ * enterprise version of Chronicle Queue should be used.
+ *
+ * @return true if enterprise features were requested, false otherwise
+ */
private boolean checkEnterpriseFeaturesRequested() {
boolean result = false;
@@ -327,12 +441,26 @@ private boolean checkEnterpriseFeaturesRequested() {
return result;
}
+ /**
+ * Logs a warning if a feature is only available in the enterprise version of Chronicle Queue.
+ *
+ * @param feature the name of the feature being requested
+ * @return true, indicating that the feature is enterprise-only
+ */
public static boolean onlyAvailableInEnterprise(final String feature) {
if (ENTERPRISE_QUEUE_CONSTRUCTOR == null)
Jvm.warn().on(SingleChronicleQueueBuilder.class, feature + " is only supported in Chronicle Queue Enterprise. If you would like to use this feature, please contact sales@chronicle.software for more information.");
return true;
}
+ /**
+ * Builds and returns an instance of the enterprise version of {@link SingleChronicleQueue}.
+ * Throws an {@link IllegalStateException} if enterprise features are requested but the enterprise version
+ * is not available in the classpath.
+ *
+ * @return an instance of {@link SingleChronicleQueue} for enterprise use
+ * @throws IllegalStateException if enterprise features are requested but unavailable
+ */
@NotNull
private SingleChronicleQueue buildEnterprise() {
if (ENTERPRISE_QUEUE_CONSTRUCTOR == null)
@@ -345,24 +473,47 @@ private SingleChronicleQueue buildEnterprise() {
}
}
+ /**
+ * Configures AES encryption using the provided key bytes. If the key is null, disables encryption.
+ *
+ * @param keyBytes the encryption key bytes, or null to disable encryption
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder aesEncryption(@Nullable byte[] keyBytes) {
if (keyBytes == null) {
- codingSuppliers(null, null);
+ codingSuppliers(null, null); // Disable encryption if no key is provided
return this;
}
- key = new SecretKeySpec(keyBytes, "AES");
+ key = new SecretKeySpec(keyBytes, "AES"); // Set the encryption key
return this;
}
+ /**
+ * Returns the message initializer. If no initializer is set, it defaults to clearing the bytes.
+ *
+ * @return the message initializer
+ */
public Updater> messageInitializer() {
return messageInitializer == null ? Bytes::clear : messageInitializer;
}
+ /**
+ * Returns the message header reader. If no reader is set, it defaults to a no-op.
+ *
+ * @return the message header reader
+ */
public Consumer> messageHeaderReader() {
return messageHeaderReader == null ? b -> {
} : messageHeaderReader;
}
+ /**
+ * Sets the message initializer and header reader for configuring message headers.
+ *
+ * @param messageInitializer the initializer to set up the message
+ * @param messageHeaderReader the reader for the message header
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder messageHeader(Updater> messageInitializer,
Consumer> messageHeaderReader) {
this.messageInitializer = messageInitializer;
@@ -370,20 +521,44 @@ public SingleChronicleQueueBuilder messageHeader(Updater> messageInitia
return this;
}
+ /**
+ * Sets the roll time for the queue using the provided {@link LocalTime}.
+ *
+ * @param rollTime the roll time to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder rollTime(@NotNull final LocalTime rollTime) {
rollTime(rollTime, rollTimeZone);
return this;
}
+ /**
+ * Returns the current roll time zone.
+ *
+ * @return the roll time zone
+ */
public ZoneId rollTimeZone() {
return rollTimeZone;
}
+ /**
+ * Sets the roll time zone for the queue using the provided {@link ZoneId}.
+ *
+ * @param rollTimeZone the time zone to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder rollTimeZone(@NotNull final ZoneId rollTimeZone) {
rollTime(rollTime, rollTimeZone);
return this;
}
+ /**
+ * Sets the roll time and time zone for the queue using the provided {@link LocalTime} and {@link ZoneId}.
+ *
+ * @param rollTime the roll time to set
+ * @param zoneId the time zone to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder rollTime(@NotNull final LocalTime rollTime, @NotNull final ZoneId zoneId) {
this.rollTime = rollTime;
this.rollTimeZone = zoneId;
@@ -392,6 +567,10 @@ public SingleChronicleQueueBuilder rollTime(@NotNull final LocalTime rollTime, @
return this;
}
+ /**
+ * Initializes the metadata for the queue, including roll cycle and metadata overrides.
+ * If in read-only mode and the metadata file is not found, falls back to a read-only table store.
+ */
protected void initializeMetadata() {
File metapath = metapath();
validateRollCycle(metapath);
@@ -405,13 +584,14 @@ protected void initializeMetadata() {
SCQMeta newMeta = metaStore.metadata();
sourceId(newMeta.sourceId());
+ // If the roll cycle has been overridden, adjust it
String format = newMeta.roll().format();
if (!format.equals(rollCycle().format())) {
// roll cycle changed
overrideRollCycleForFileName(format);
}
- // if it was overridden - reset
+ // Reset roll time and epoch if overridden
rollTime = newMeta.roll().rollTime();
rollTimeZone = newMeta.roll().rollTimeZone();
epoch = newMeta.roll().epoch();
@@ -423,10 +603,23 @@ protected void initializeMetadata() {
Jvm.warn().on(getClass(), "Failback to readonly tablestore " + ex);
else
Jvm.warn().on(getClass(), "Failback to readonly tablestore", ex);
+
+ // Fallback to read-only table store if metadata file is not found
metaStore = new ReadonlyTableStore<>(metadata);
}
}
+ /**
+ * Validates the roll cycle by checking the existence of metadata. If no metadata is found,
+ * it attempts to determine the roll cycle based on existing queue files in the directory.
+ * This method cannot validate certain larger roll cycles.
+ *
+ * For specific roll cycles like LARGE_HOURLY_SPARSE, LARGE_HOURLY_XSPARSE, LARGE_DAILY,
+ * XLARGE_DAILY, HUGE_DAILY, and HUGE_DAILY_XSPARSE, the correct roll cycle must be manually
+ * provided during queue creation.
+ *
+ * @param metapath the metadata path
+ */
private void validateRollCycle(File metapath) {
if (!metapath.exists()) {
// no metadata, so we need to check if there're cq4 files and if so try to validate roll cycle
@@ -435,20 +628,30 @@ private void validateRollCycle(File metapath) {
// for such cases user MUST use correct roll cycle when creating the queue
String[] list = path.list((d, name) -> name.endsWith(SingleChronicleQueue.SUFFIX));
if (list != null && list.length > 0) {
+ // Try to match the roll cycle by parsing the filename against known roll cycles
String filename = list[0];
for (RollCycle cycle : RollCycles.all()) {
try {
+ // Attempt to parse the filename using the cycle's format
DateTimeFormatter.ofPattern(cycle.format())
.parse(filename.substring(0, filename.length() - 4));
overrideRollCycle(cycle);
break;
} catch (Exception expected) {
+ // Ignore the exception and continue checking other cycles
}
}
}
}
}
+ /**
+ * Overrides the roll cycle using the given pattern from the metadata. If no matching roll cycle
+ * is found, an exception is thrown.
+ *
+ * @param pattern the roll cycle pattern from metadata
+ * @throws IllegalStateException if no matching roll cycle is found
+ */
private void overrideRollCycleForFileName(String pattern) {
for (RollCycle cycle : RollCycles.all()) {
if (cycle.format().equals(pattern)) {
@@ -459,12 +662,22 @@ private void overrideRollCycleForFileName(String pattern) {
throw new IllegalStateException("Can't find an appropriate RollCycles to override to of length " + pattern);
}
+ /**
+ * Sets the roll cycle to the specified {@link RollCycle}. Logs a warning if the roll cycle is being overridden.
+ *
+ * @param cycle the roll cycle to override to
+ */
private void overrideRollCycle(RollCycle cycle) {
if (rollCycle != cycle && rollCycle != null)
Jvm.warn().on(getClass(), "Overriding roll cycle from " + rollCycle + " to " + cycle);
rollCycle = cycle;
}
+ /**
+ * Constructs the metadata file path based on the queue's directory. Creates the directory if it does not exist.
+ *
+ * @return the file path for the queue's metadata
+ */
private File metapath() {
final File storeFilePath;
if ("".equals(path.getPath())) {
@@ -476,19 +689,25 @@ private File metapath() {
return storeFilePath;
}
+ /**
+ * Returns a factory for creating the {@link Condition} that will be used before creating a new appender.
+ * If no condition creator has been set, returns a no-op condition.
+ *
+ * @return a {@link Function} that creates a {@link Condition} for the appender
+ */
@NotNull
public Function createAppenderConditionCreator() {
if (createAppenderConditionCreator == null) {
- return q -> NoOpCondition.INSTANCE;
+ return q -> NoOpCondition.INSTANCE; // Default to no-op condition if not set
}
return createAppenderConditionCreator;
}
/**
- * @return Factory for the {@link Condition} that will be waited on before a new appender is created
- *
- * NOTE: The returned {@link Condition} will not block subsequent calls to acquireAppender from the
- * same thread, only when the call would result in the creation of a new appender.
+ * Sets the factory for creating the {@link Condition} that will be used before a new appender is created.
+ *
+ * @param creator the factory for creating the {@link Condition}
+ * @return the current builder instance for method chaining
*/
@NotNull
public SingleChronicleQueueBuilder createAppenderConditionCreator(Function creator) {
@@ -496,29 +715,50 @@ public SingleChronicleQueueBuilder createAppenderConditionCreator(Function metaStore() {
return metaStore;
}
/**
- * RingBuffer tailers need to be preallocated. Only set this if using readBufferMode=Asynchronous.
- * By default 1 tailer will be created for the user.
+ * Sets the maximum number of tailers that will be required for this queue when using asynchronous mode.
+ * This value does not include the draining tailer.
*
- * @param maxTailers number of tailers that will be required from this queue, not including the draining tailer
- * @return this
+ * @param maxTailers the number of tailers to preallocate
+ * @return the current builder instance for method chaining
*/
public SingleChronicleQueueBuilder maxTailers(int maxTailers) {
this.maxTailers = maxTailers;
@@ -526,14 +766,21 @@ public SingleChronicleQueueBuilder maxTailers(int maxTailers) {
}
/**
- * maxTailers
+ * Returns the maximum number of tailers that will be required for this queue, excluding the draining tailer.
*
- * @return number of tailers that will be required from this queue, not including the draining tailer
+ * @return the maximum number of tailers
*/
public int maxTailers() {
return maxTailers;
}
+ /**
+ * Sets the asynchronous buffer creator for the queue. This is used in asynchronous mode
+ * to control data visibility between processes or threads.
+ *
+ * @param asyncBufferCreator the buffer creator for asynchronous mode
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder asyncBufferCreator(AsyncBufferCreator asyncBufferCreator) {
this.bufferBytesStoreCreator = asyncBufferCreator;
return this;
@@ -555,7 +802,11 @@ public AsyncBufferCreator asyncBufferCreator() {
}
/**
- * Enable out-of-process pretoucher (AKA preloader) (Queue Enterprise feature)
+ * Enables the preloader (also known as the pretoucher) for out-of-process use. This is an enterprise feature.
+ * The preloader will preload data into memory at regular intervals.
+ *
+ * @param pretouchIntervalMillis the interval in milliseconds between preload operations
+ * @return the current builder instance for method chaining
*/
public SingleChronicleQueueBuilder enablePreloader(final long pretouchIntervalMillis) {
this.pretouchIntervalMillis = pretouchIntervalMillis;
@@ -563,63 +814,114 @@ public SingleChronicleQueueBuilder enablePreloader(final long pretouchIntervalMi
}
/**
- * Interval in ms to invoke out of process pretoucher. Default is not to turn on
+ * Returns the interval in milliseconds to invoke the out-of-process pretoucher.
+ * By default, this is not enabled unless explicitly configured.
*
- * @return interval ms
+ * @return the pretouch interval in milliseconds
*/
public long pretouchIntervalMillis() {
return pretouchIntervalMillis;
}
+ /**
+ * Checks if a pretouch interval has been set for the queue.
+ *
+ * @return true if the pretouch interval is set, false otherwise
+ */
public boolean hasPretouchIntervalMillis() {
return pretouchIntervalMillis != null;
}
+ /**
+ * Sets the path for the queue using a string representation of the path.
+ *
+ * @param path the path as a string
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder path(String path) {
return path(new File(path));
}
+ /**
+ * Sets the path for the queue using a {@link File} object.
+ *
+ * @param path the file representing the path
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder path(final File path) {
this.path = path;
return this;
}
+ /**
+ * Sets the path for the queue using a {@link Path} object.
+ *
+ * @param path the path object
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder path(final Path path) {
this.path = path.toFile();
return this;
}
/**
- * consumer will be called every second, also as there is data to report
+ * Sets a consumer to be called every second or when there is data to report regarding
+ * ring buffer statistics.
*
- * @param onRingBufferStats a consumer of the BytesRingBufferStats
- * @return this
+ * @param onRingBufferStats the consumer of {@link BytesRingBufferStats}
+ * @return the current builder instance for method chaining
*/
public SingleChronicleQueueBuilder onRingBufferStats(@NotNull Consumer onRingBufferStats) {
this.onRingBufferStats = onRingBufferStats;
return this;
}
+ /**
+ * Returns the consumer for ring buffer statistics that was set for this builder.
+ *
+ * @return the consumer of {@link BytesRingBufferStats}
+ */
public Consumer onRingBufferStats() {
return this.onRingBufferStats;
}
+ /**
+ * Returns the path set for this queue.
+ *
+ * @return the file representing the path of the queue
+ */
@NotNull
public File path() {
return this.path;
}
+ /**
+ * Sets the block size for memory-mapped files used by the queue.
+ * The block size must be at least {@link #SMALL_BLOCK_SIZE}.
+ *
+ * @param blockSize the block size in bytes
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder blockSize(long blockSize) {
this.blockSize = Math.max(SMALL_BLOCK_SIZE, blockSize);
return this;
}
+ /**
+ * Overloaded method to set the block size using an integer value.
+ *
+ * @param blockSize the block size in bytes
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder blockSize(int blockSize) {
return blockSize((long) blockSize);
}
/**
- * @return - this is the size of a memory mapping chunk, a queue is read/written by using a number of blocks, you should avoid changing this unnecessarily.
+ * Returns the block size for memory-mapped files used by the queue. If not explicitly set,
+ * it defaults to 64MB on 64-bit systems or {@link #SMALL_BLOCK_SIZE} on 32-bit systems.
+ *
+ * @return the block size in bytes
*/
public long blockSize() {
@@ -627,7 +929,7 @@ public long blockSize() {
? OS.is64Bit() ? 64L << 20 : SMALL_BLOCK_SIZE
: blockSize;
- // can add an index2index & an index in one go.
+ // Ensure the block size can accommodate both an index2index and an index in one operation
long minSize = Math.max(SMALL_BLOCK_SIZE, 32L * indexCount());
return Math.max(minSize, bs);
}
@@ -643,10 +945,17 @@ public long blockSize() {
* @return this
*/
public SingleChronicleQueueBuilder testBlockSize() {
- // small size for testing purposes only.
+ // Use a small block size for testing purposes only
return blockSize(SMALL_BLOCK_SIZE);
}
+ /**
+ * Sets the wire type to be used by the queue. If the wire type is {@link WireType#DELTA_BINARY},
+ * the delta checkpoint interval is set to 64.
+ *
+ * @param wireType the wire type for serialization
+ * @return the current builder instance for method chaining
+ */
@NotNull
public SingleChronicleQueueBuilder wireType(@NotNull WireType wireType) {
if (wireType == WireType.DELTA_BINARY)
@@ -655,20 +964,42 @@ public SingleChronicleQueueBuilder wireType(@NotNull WireType wireType) {
return this;
}
+ /**
+ * Sets the delta checkpoint interval, ensuring that it is a power of 2.
+ *
+ * @param deltaCheckpointInterval the interval to set
+ */
private void deltaCheckpointInterval(int deltaCheckpointInterval) {
assert checkIsPowerOf2(deltaCheckpointInterval);
this.deltaCheckpointInterval = deltaCheckpointInterval;
}
+ /**
+ * Checks if the given value is a power of 2, or zero is allowed.
+ *
+ * @param value the value to check
+ * @return true if the value is a power of 2, or zero, false otherwise
+ */
private boolean checkIsPowerOf2(long value) {
- return (value & (value - 1)) == 0;
+ return (value & (value - 1)) == 0; // Check if value is a power of 2
}
+ /**
+ * Returns the wire type set for the queue. If not explicitly set, defaults to {@link WireType#BINARY_LIGHT}.
+ *
+ * @return the wire type used by the queue
+ */
@NotNull
public WireType wireType() {
return this.wireType == null ? WireType.BINARY_LIGHT : wireType;
}
+ /**
+ * Sets the roll cycle for the queue, which determines how often the queue rolls to a new file.
+ *
+ * @param rollCycle the roll cycle to set
+ * @return the current builder instance for method chaining
+ */
@NotNull
public SingleChronicleQueueBuilder rollCycle(@NotNull RollCycle rollCycle) {
assert rollCycle != null;
@@ -676,13 +1007,21 @@ public SingleChronicleQueueBuilder rollCycle(@NotNull RollCycle rollCycle) {
return this;
}
+ /**
+ * Returns the roll cycle set for the queue. If not explicitly set, it loads the default roll cycle.
+ *
+ * @return the roll cycle used by the queue
+ */
@NotNull
public RollCycle rollCycle() {
return this.rollCycle == null ? loadDefaultRollCycle() : this.rollCycle;
}
/**
- * @return ring buffer capacity in bytes [ Chronicle-Ring is an enterprise product ]
+ * Returns the buffer capacity in bytes for the queue's ring buffer. The buffer capacity is capped
+ * at one-quarter of the block size, with a default of 2MB if not explicitly set.
+ *
+ * @return the buffer capacity in bytes
*/
public long bufferCapacity() {
return Math.min(blockSize() / 4, bufferCapacity == null ? 2 << 20 : bufferCapacity);
@@ -765,8 +1104,10 @@ public SingleChronicleQueueBuilder readBufferMode(BufferMode readBufferMode) {
}
/**
- * @return a new event loop instance if none has been set, otherwise the {@code eventLoop}
- * that was set
+ * Returns the event loop set for the queue. If no event loop has been set, it creates
+ * and returns a new {@link OnDemandEventLoop} with a {@link MediumEventLoop}.
+ *
+ * @return the current event loop or a new event loop instance if none has been set
*/
@NotNull
public EventLoop eventLoop() {
@@ -776,6 +1117,12 @@ public EventLoop eventLoop() {
return eventLoop;
}
+ /**
+ * Sets the event loop to be used by the queue.
+ *
+ * @param eventLoop the event loop to set
+ * @return the current builder instance for method chaining
+ */
@NotNull
public SingleChronicleQueueBuilder eventLoop(EventLoop eventLoop) {
this.eventLoop = eventLoop;
@@ -783,146 +1130,283 @@ public SingleChronicleQueueBuilder eventLoop(EventLoop eventLoop) {
}
/**
- * @return if the ring buffer's monitoring capability is turned on. Not available in OSS
+ * Checks if ring buffer monitoring is enabled. This feature is not available in the open-source version (OSS).
+ *
+ * @return true if ring buffer monitoring is enabled, false otherwise
*/
public boolean enableRingBufferMonitoring() {
return enableRingBufferMonitoring != null && enableRingBufferMonitoring;
}
+ /**
+ * Enables or disables the ring buffer monitoring feature.
+ *
+ * @param enableRingBufferMonitoring true to enable monitoring, false to disable it
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder enableRingBufferMonitoring(boolean enableRingBufferMonitoring) {
this.enableRingBufferMonitoring = enableRingBufferMonitoring;
return this;
}
/**
- * default value is {@code false} since 5.21ea0
+ * Checks if ring buffer reader processes can invoke the Chronicle Queue drainer.
+ * By default, this is disabled
*
- * @return if ring buffer reader processes can invoke the CQ drainer, otherwise only writer processes can
+ * @return true if ring buffer readers can invoke the drainer, false otherwise
*/
public boolean ringBufferReaderCanDrain() {
return ringBufferReaderCanDrain != null && ringBufferReaderCanDrain;
}
+ /**
+ * Sets whether ring buffer reader processes are allowed to invoke the Chronicle Queue drainer.
+ *
+ * @param ringBufferReaderCanDrain true to allow reader processes to invoke the drainer, false to restrict it
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder ringBufferReaderCanDrain(boolean ringBufferReaderCanDrain) {
this.ringBufferReaderCanDrain = ringBufferReaderCanDrain;
return this;
}
/**
- * @return whether to force creating a reader (to recover from crash)
+ * Checks if the queue is configured to force the creation of a ring buffer reader to recover from a crash.
+ *
+ * @return true if forcing creation of a ring buffer reader is enabled, false otherwise
*/
public boolean ringBufferForceCreateReader() {
return ringBufferForceCreateReader != null && ringBufferForceCreateReader;
}
+ /**
+ * Sets whether the queue should force creating a ring buffer reader to recover from crashes.
+ *
+ * @param ringBufferForceCreateReader true to force reader creation, false otherwise
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder ringBufferForceCreateReader(boolean ringBufferForceCreateReader) {
this.ringBufferForceCreateReader = ringBufferForceCreateReader;
return this;
}
/**
- * @return if ring buffer readers are not reset on close. If true then re-opening a reader puts you back
- * at the same place. If true, your reader can block writers if the reader is not open
+ * Checks if ring buffer readers are configured to reopen at the same position upon closing.
+ * If true, reopening a reader puts it back at the same position, but it may block writers if the reader is not open.
+ *
+ * @return true if the ring buffer readers reopen at the same position, false otherwise
*/
public boolean ringBufferReopenReader() {
return ringBufferReopenReader != null && ringBufferReopenReader;
}
+ /**
+ * Sets whether the ring buffer readers should reopen at the same position upon closing.
+ *
+ * @param ringBufferReopenReader true to reopen readers at the same position, false otherwise
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder ringBufferReopenReader(boolean ringBufferReopenReader) {
this.ringBufferReopenReader = ringBufferReopenReader;
return this;
}
/**
- * Priority for async mode drainer handler
+ * Returns the priority of the handler for the async mode drainer.
*
- * @return drainerPriority
+ * @return the priority for the drainer handler
*/
public HandlerPriority drainerPriority() {
return drainerPriority;
}
+ /**
+ * Sets the priority of the handler for the async mode drainer.
+ *
+ * @param drainerPriority the priority to set for the drainer handler
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder drainerPriority(HandlerPriority drainerPriority) {
this.drainerPriority = drainerPriority;
return this;
}
+ /**
+ * Returns the timeout for the drainer in milliseconds. If the timeout is not set or is less than or equal to 0,
+ * the default value of 10,000 milliseconds (10 seconds) is returned.
+ *
+ * @return the drainer timeout in milliseconds
+ */
public int drainerTimeoutMS() {
return drainerTimeoutMS <= 0 ? 10_000 : drainerTimeoutMS;
}
+ /**
+ * Sets the timeout for the drainer in milliseconds.
+ *
+ * @param timeout the timeout in milliseconds
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder drainerTimeoutMS(int timeout) {
drainerTimeoutMS = timeout;
return this;
}
/**
- * Pauser supplier for the pauser to be used by ring buffer when waiting
+ * Returns the {@link Pauser} supplier to be used by the ring buffer when waiting.
+ * If no supplier is set, it defaults to a busy-wait {@link Pauser}.
+ *
+ * @return the supplier of {@link Pauser} for the ring buffer
*/
public Supplier ringBufferPauserSupplier() {
return ringBufferPauserSupplier == null ? Pauser::busy : ringBufferPauserSupplier;
}
+ /**
+ * Sets the {@link Pauser} supplier to be used by the ring buffer when waiting.
+ *
+ * @param ringBufferPauserSupplier the supplier of {@link Pauser} for the ring buffer
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder ringBufferPauserSupplier(Supplier ringBufferPauserSupplier) {
this.ringBufferPauserSupplier = ringBufferPauserSupplier;
return this;
}
+ /**
+ * Sets the number of indices to be maintained in the queue's index. The number is rounded up to the next power of 2,
+ * with a minimum value of 8.
+ *
+ * @param indexCount the number of indices to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder indexCount(int indexCount) {
this.indexCount = Maths.nextPower2(indexCount, 8);
return this;
}
+ /**
+ * Returns the number of indices for the queue's index. If no index count is explicitly set,
+ * it defaults to the roll cycle's {@link RollCycle#defaultIndexCount()}.
+ *
+ * @return the number of indices for the queue
+ */
public int indexCount() {
return indexCount == null || indexCount <= 0 ? rollCycle().defaultIndexCount() : indexCount;
}
+ /**
+ * Sets the index spacing, ensuring the value is rounded up to the next power of 2.
+ *
+ * @param indexSpacing the index spacing to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder indexSpacing(int indexSpacing) {
this.indexSpacing = Maths.nextPower2(indexSpacing, 1);
return this;
}
+ /**
+ * Returns the index spacing. If no spacing is explicitly set, it defaults to the roll cycle's
+ * {@link RollCycle#defaultIndexSpacing()}.
+ *
+ * @return the index spacing for the queue
+ */
public int indexSpacing() {
return indexSpacing == null || indexSpacing <= 0 ? rollCycle().defaultIndexSpacing() :
indexSpacing;
}
+ /**
+ * Returns the {@link TimeProvider} for the queue. If not explicitly set, it defaults to the
+ * {@link SystemTimeProvider#INSTANCE}.
+ *
+ * @return the time provider used by the queue
+ */
public TimeProvider timeProvider() {
return timeProvider == null ? SystemTimeProvider.INSTANCE : timeProvider;
}
+ /**
+ * Sets the {@link TimeProvider} for the queue.
+ *
+ * @param timeProvider the time provider to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder timeProvider(TimeProvider timeProvider) {
this.timeProvider = timeProvider;
return this;
}
+ /**
+ * Returns the {@link TimingPauser} supplier used by the queue.
+ * If not explicitly set, it defaults to {@link DefaultPauserSupplier#INSTANCE}.
+ *
+ * @return the pauser supplier for the queue
+ */
public Supplier pauserSupplier() {
return pauserSupplier == null ? TIMING_PAUSER_SUPPLIER : pauserSupplier;
}
+ /**
+ * Sets the {@link TimingPauser} supplier for the queue.
+ *
+ * @param pauser the pauser supplier to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder pauserSupplier(Supplier pauser) {
this.pauserSupplier = pauser;
return this;
}
+ /**
+ * Sets the timeout in milliseconds for the queue.
+ *
+ * @param timeoutMS the timeout in milliseconds
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder timeoutMS(long timeoutMS) {
this.timeoutMS = timeoutMS;
return this;
}
+ /**
+ * Returns the timeout in milliseconds for the queue. If not set, defaults to 10,000 milliseconds (10 seconds).
+ *
+ * @return the timeout in milliseconds
+ */
public long timeoutMS() {
return timeoutMS == null ? 10_000L : timeoutMS;
}
+ /**
+ * Sets the {@link StoreFileListener} for the queue.
+ *
+ * @param storeFileListener the store file listener to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder storeFileListener(StoreFileListener storeFileListener) {
this.storeFileListener = storeFileListener;
return this;
}
+ /**
+ * Returns the {@link StoreFileListener} used by the queue. If not explicitly set,
+ * it defaults to {@link StoreFileListeners#DEBUG}.
+ *
+ * @return the store file listener for the queue
+ */
public StoreFileListener storeFileListener() {
return storeFileListener == null ? StoreFileListeners.DEBUG : storeFileListener;
}
+ /**
+ * Sets the source ID for the queue. The source ID must be a positive integer.
+ *
+ * @param sourceId the source ID to set
+ * @return the current builder instance for method chaining
+ * @throws IllegalArgumentException if the source ID is negative
+ */
public SingleChronicleQueueBuilder sourceId(int sourceId) {
if (sourceId < 0)
throw new IllegalArgumentException("Invalid source Id, must be positive");
@@ -930,14 +1414,32 @@ public SingleChronicleQueueBuilder sourceId(int sourceId) {
return this;
}
+ /**
+ * Returns the source ID for the queue. If not explicitly set, defaults to 0.
+ *
+ * @return the source ID for the queue
+ */
public int sourceId() {
return sourceId == null ? 0 : sourceId;
}
+ /**
+ * Checks if the queue is in read-only mode. On Windows, read-only mode is not supported and
+ * the queue defaults to read/write mode.
+ *
+ * @return true if the queue is in read-only mode, false otherwise
+ */
public boolean readOnly() {
return Boolean.TRUE.equals(readOnly) && !OS.isWindows();
}
+ /**
+ * Sets whether the queue is in read-only mode. If the platform is Windows, it logs a warning
+ * and defaults to read/write mode as read-only mode is not supported on Windows.
+ *
+ * @param readOnly true to enable read-only mode, false otherwise
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder readOnly(boolean readOnly) {
if (OS.isWindows() && readOnly)
Jvm.warn().on(SingleChronicleQueueBuilder.class,
@@ -948,6 +1450,12 @@ public SingleChronicleQueueBuilder readOnly(boolean readOnly) {
return this;
}
+ /**
+ * Checks if double-buffering is enabled for the queue. Double-buffering allows writing
+ * without waiting for the write lock, reducing the cost of serialization on contention.
+ *
+ * @return true if double-buffering is enabled, false otherwise
+ */
public boolean doubleBuffer() {
return doubleBuffer;
}
@@ -974,14 +1482,35 @@ public SingleChronicleQueueBuilder doubleBuffer(boolean doubleBuffer) {
return this;
}
+ /**
+ * Returns the encoding supplier used by the queue, if set. The encoding supplier is responsible
+ * for encoding data written to the queue.
+ *
+ * @return the encoding supplier, or null if not set
+ */
public Supplier, Bytes>>> encodingSupplier() {
return encodingSupplier;
}
+ /**
+ * Returns the decoding supplier used by the queue, if set. The decoding supplier is responsible
+ * for decoding data read from the queue.
+ *
+ * @return the decoding supplier, or null if not set
+ */
public Supplier, Bytes>>> decodingSupplier() {
return decodingSupplier;
}
+ /**
+ * Sets both the encoding and decoding suppliers for the queue. Both suppliers must be set together;
+ * if one is set to null, the other must also be null.
+ *
+ * @param encodingSupplier the encoding supplier for writing data
+ * @param decodingSupplier the decoding supplier for reading data
+ * @return the current builder instance for method chaining
+ * @throws UnsupportedOperationException if one supplier is set and the other is null
+ */
public SingleChronicleQueueBuilder codingSuppliers(@Nullable
Supplier, Bytes>>> encodingSupplier,
@Nullable Supplier, Bytes>>> decodingSupplier) {
@@ -992,10 +1521,20 @@ public SingleChronicleQueueBuilder codingSuppliers(@Nullable
return this;
}
+ /**
+ * Returns the {@link SecretKeySpec} used for AES encryption, if set.
+ *
+ * @return the encryption key, or null if no key is set
+ */
public SecretKeySpec key() {
return key;
}
+ /**
+ * Pre-build method that initializes metadata before constructing the queue.
+ * If an error occurs during initialization, the metadata store is closed and the exception is rethrown.
+ * It also resets the roll time if both {@code rollTime} and {@code rollTimeZone} are provided and the epoch is unset.
+ */
protected void preBuild() {
try {
initializeMetadata();
@@ -1004,13 +1543,15 @@ protected void preBuild() {
throw ex;
}
if ((epoch == null || epoch == 0) && (rollTime != null && rollTimeZone != null))
+ // Reset roll time if epoch is unset but rollTime and rollTimeZone are provided
rollTime(rollTime, rollTimeZone);
}
- // *************************************************************************
- //
- // *************************************************************************
-
+ /**
+ * Checks whether interrupts should be monitored, based on system properties or the configuration.
+ *
+ * @return true if interrupts should be checked, false otherwise
+ */
public boolean checkInterrupts() {
if (System.getProperties().contains("chronicle.queue.checkInterrupts"))
@@ -1019,15 +1560,32 @@ public boolean checkInterrupts() {
return checkInterrupts;
}
+ /**
+ * Sets whether the queue should monitor interrupts.
+ *
+ * @param checkInterrupts true to enable interrupt checking, false otherwise
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder checkInterrupts(boolean checkInterrupts) {
this.checkInterrupts = checkInterrupts;
return this;
}
+ /**
+ * Returns the interval in milliseconds for forcing a directory listing refresh.
+ *
+ * @return the refresh interval in milliseconds
+ */
public long forceDirectoryListingRefreshIntervalMs() {
return forceDirectoryListingRefreshIntervalMs;
}
+ /**
+ * Sets the interval in milliseconds for forcing a directory listing refresh.
+ *
+ * @param forceDirectoryListingRefreshIntervalMs the interval to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder forceDirectoryListingRefreshIntervalMs(long forceDirectoryListingRefreshIntervalMs) {
this.forceDirectoryListingRefreshIntervalMs = forceDirectoryListingRefreshIntervalMs;
return this;
@@ -1047,21 +1605,26 @@ public SingleChronicleQueueBuilder clone() {
}
/**
- * updates all the fields in {@code this} that are null, from the parentBuilder
+ * Copies all null fields in the current builder from the given parent builder.
+ * Only fields present in both builders' class hierarchies will be copied.
*
- * @param parentBuilder the parentBuilder Chronicle Queue Builder
- * @return that
+ * @param parentBuilder the parent builder to copy from
+ * @return the current builder instance for method chaining
+ * @throws IllegalArgumentException if the builders are not from the same class hierarchy
*/
public SingleChronicleQueueBuilder setAllNullFields(@Nullable SingleChronicleQueueBuilder parentBuilder) {
if (parentBuilder == null)
return this;
+ // Ensure both builders are from the same class hierarchy
if (!(this.getClass().isAssignableFrom(parentBuilder.getClass()) || parentBuilder.getClass().isAssignableFrom(this.getClass())))
throw new IllegalArgumentException("Classes are not in same implementation hierarchy");
+ // Get field information from both builders
List sourceFieldInfo = Wires.fieldInfos(parentBuilder.getClass());
+ // Copy null fields from the parentBuilder
for (final FieldInfo fieldInfo : Wires.fieldInfos(this.getClass())) {
if (!sourceFieldInfo.contains(fieldInfo))
continue;
@@ -1074,36 +1637,63 @@ public SingleChronicleQueueBuilder setAllNullFields(@Nullable SingleChronicleQue
return this;
}
+ /**
+ * Returns the append lock used for appending to the queue. If the queue is read-only,
+ * it returns a no-op lock; otherwise, it returns a standard {@link AppendLock}.
+ *
+ * @return the append lock for the queue
+ */
public WriteLock appendLock() {
return readOnly() ? WriteLock.NO_OP : new AppendLock(metaStore, pauserSupplier(), timeoutMS() * 3 / 2);
}
/**
- * Set an AppenderListener which is called when an Excerpt is actually written.
- * This is called while the writeLock is still held, after the messages has been written.
- * For asynchronous writes, this is called in the background thread.
+ * Sets an {@link AppenderListener} to be called when an excerpt is written.
+ * This listener is invoked while the write lock is still held, after the message has been written.
+ * In asynchronous writes, it is called in the background thread.
*
- * @param appenderListener to call
- * @return this
+ * @param appenderListener the listener to call when an excerpt is written
+ * @return the current builder instance for method chaining
*/
public SingleChronicleQueueBuilder appenderListener(AppenderListener appenderListener) {
this.appenderListener = appenderListener;
return this;
}
+ /**
+ * Returns the {@link AppenderListener} currently set for the queue.
+ *
+ * @return the appender listener
+ */
public AppenderListener appenderListener() {
return appenderListener;
}
+ /**
+ * Sets the synchronization mode for the queue's memory-mapped files.
+ *
+ * @param syncMode the sync mode to set
+ * @return the current builder instance for method chaining
+ */
public SingleChronicleQueueBuilder syncMode(SyncMode syncMode) {
this.syncMode = syncMode;
return this;
}
+ /**
+ * Returns the synchronization mode used for the queue's memory-mapped files.
+ * If not explicitly set, it defaults to the {@link MappedFile#DEFAULT_SYNC_MODE}.
+ *
+ * @return the sync mode for the queue
+ */
public SyncMode syncMode() {
return syncMode == null ? MappedFile.DEFAULT_SYNC_MODE : syncMode;
}
+ /**
+ * A default supplier for the {@link TimingPauser}, used when no explicit supplier is provided.
+ * This implementation returns a {@link YieldingPauser} with a 500,000 nanosecond yield duration.
+ */
enum DefaultPauserSupplier implements Supplier {
INSTANCE;
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueStore.java b/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueStore.java
index e56f59978a..9e7f38d007 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueStore.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueStore.java
@@ -39,8 +39,16 @@
import java.util.Objects;
import java.util.concurrent.TimeUnit;
+/**
+ * The SingleChronicleQueueStore class represents a store for Chronicle Queue.
+ * It is responsible for maintaining the indexed write position, sequence, and mapping
+ * the file to memory using {@link MappedBytes}. It handles reading and writing data in
+ * a queue and supports efficient roll cycles and indexing.
+ */
public class SingleChronicleQueueStore extends AbstractCloseable implements WireStore {
+
static {
+ // Register SCQIndexing with the class alias pool for serialization purposes
ClassAliasPool.CLASS_ALIASES.addAlias(SCQIndexing.class);
}
@@ -61,9 +69,10 @@ public class SingleChronicleQueueStore extends AbstractCloseable implements Wire
private int cycle;
/**
- * used by {@link net.openhft.chronicle.wire.Demarshallable}
+ * Constructor used by {@link net.openhft.chronicle.wire.Demarshallable} to create an instance of SingleChronicleQueueStore
+ * from a wire input. This constructor is used during deserialization.
*
- * @param wire a wire
+ * @param wire the wire input to read from
*/
@UsedViaReflection
@SuppressWarnings("this-escape")
@@ -98,11 +107,14 @@ private SingleChronicleQueueStore(@NotNull WireIn wire) {
}
/**
- * @param rollCycle the current rollCycle
- * @param wireType the wire type that is being used
- * @param mappedBytes used to mapped the data store file
- * @param indexCount the number of entries in each index.
- * @param indexSpacing the spacing between indexed entries.
+ * Constructs a new SingleChronicleQueueStore instance.
+ * This constructor is used for creating a new queue store from scratch.
+ *
+ * @param rollCycle the roll cycle configuration for the store
+ * @param wireType the wire type used for serialization
+ * @param mappedBytes the mapped bytes for memory mapping the data store file
+ * @param indexCount the number of entries in each index
+ * @param indexSpacing the spacing between indexed entries
*/
@SuppressWarnings("this-escape")
public SingleChronicleQueueStore(@NotNull RollCycle rollCycle,
@@ -126,11 +138,24 @@ public SingleChronicleQueueStore(@NotNull RollCycle rollCycle,
singleThreadedCheckDisabled(true);
}
+ /**
+ * Dumps the contents of a Chronicle Queue directory to a string for debugging.
+ *
+ * @param directoryFilePath the path to the directory to dump
+ * @return the dumped contents as a string
+ */
@NotNull
public static String dump(@NotNull String directoryFilePath) {
return ChronicleQueue.singleBuilder(directoryFilePath).build().dump();
}
+ /**
+ * Binds a value to the wire output for either int64 or int128, depending on the value type.
+ *
+ * @param wireOut the wire output to write to
+ * @param value the value to bind
+ * @return the updated wire output
+ */
private static WireOut intForBinding(ValueOut wireOut, final LongValue value) {
return value instanceof TwoLongValue ?
wireOut.int128forBinding(0L, 0L, (TwoLongValue) value) :
@@ -138,6 +163,13 @@ private static WireOut intForBinding(ValueOut wireOut, final LongValue value) {
}
+ /**
+ * Loads the write position from the wire input. Depending on the encoding, this could
+ * be a single long value or an array of two long values.
+ *
+ * @param wire the wire input to read from
+ * @return the loaded write position
+ */
private LongValue loadWritePosition(@NotNull WireIn wire) {
final ValueIn read = wire.read(MetaDataField.writePosition);
@@ -152,6 +184,7 @@ private LongValue loadWritePosition(@NotNull WireIn wire) {
wire.bytes().readPosition(start);
}
+ // Check if the write position is encoded as a two-long array
if (code == BinaryWireCode.I64_ARRAY) {
TwoLongValue result = wire.newTwoLongReference();
// when the write position is and array it also encodes the sequence number in the write position as the second long value
@@ -159,23 +192,43 @@ private LongValue loadWritePosition(@NotNull WireIn wire) {
return result;
}
+ // Otherwise, treat it as a single long value
final LongValue result = wire.newLongReference();
read.int64(result);
return result;
}
+ /**
+ * Returns the file associated with the store.
+ *
+ * @return the file used by the queue store
+ */
@NotNull
@Override
public File file() {
return mappedFile.file();
}
+ /**
+ * Dumps the contents of the queue using the specified {@link WireType}.
+ * This method provides a detailed string representation of the queue's contents.
+ *
+ * @param wireType the {@link WireType} used to interpret the contents of the queue
+ * @return a string representing the dumped contents of the queue
+ */
@Override
public String dump(WireType wireType) {
return dump(wireType, false);
}
+ /**
+ * Dumps the contents of the queue using the specified {@link WireType}, with an option to abbreviate.
+ *
+ * @param wireType the {@link WireType} used to interpret the contents of the queue
+ * @param abbrev whether to abbreviate the dumped contents
+ * @return a string representing the dumped contents of the queue
+ */
private String dump(WireType wireType, boolean abbrev) {
try (MappedBytes bytes = MappedBytes.mappedBytes(mappedFile)) {
bytes.readLimit(bytes.realCapacity());
@@ -185,6 +238,11 @@ private String dump(WireType wireType, boolean abbrev) {
}
}
+ /**
+ * Dumps the header of the queue. This is useful for examining metadata about the queue.
+ *
+ * @return a string representing the dumped header contents of the queue
+ */
@Override
public String dumpHeader() {
try (MappedBytes bytes = MappedBytes.mappedBytes(mappedFile)) {
@@ -196,11 +254,22 @@ public String dumpHeader() {
}
}
+ /**
+ * Retrieves the current write position in the queue.
+ *
+ * @return the current volatile write position
+ */
@Override
public long writePosition() {
return this.writePosition.getVolatileValue();
}
+ /**
+ * Sets the write position to the specified value. Ensures that the position is valid.
+ *
+ * @param position the new write position to set
+ * @return the current instance of {@link WireStore}
+ */
@NotNull
@Override
public WireStore writePosition(long position) {
@@ -212,11 +281,11 @@ public WireStore writePosition(long position) {
}
/**
- * Moves the position to the index
+ * Moves the excerpt context to the specified index for reading.
*
- * @param ec the data structure we are navigating
- * @param index the index we wish to move to
- * @return whether the index was found for reading.
+ * @param ec the excerpt context to navigate
+ * @param index the index to move to for reading
+ * @return the result of the scan, indicating whether the index was found
*/
@Nullable
@Override
@@ -231,10 +300,10 @@ public ScanResult moveToIndexForRead(@NotNull ExcerptContext ec, long index) {
}
/**
- * Moves the position to the start
+ * Moves the excerpt context to the start of the queue for reading.
*
- * @param ec the data structure we are navigating
- * @return whether the index was found for reading.
+ * @param ec the excerpt context to navigate
+ * @return the result of the scan, indicating whether the start was found
*/
@Nullable
@Override
@@ -262,6 +331,12 @@ public ScanResult moveToStartForRead(@NotNull ExcerptContext ec) {
}
}
+ /**
+ * Moves the wire to the end of the queue for reading.
+ *
+ * @param w the wire to navigate
+ * @return the index at the end of the queue
+ */
@Override
public long moveToEndForRead(@NotNull Wire w) {
throwExceptionIfClosed();
@@ -269,12 +344,16 @@ public long moveToEndForRead(@NotNull Wire w) {
return indexing.moveToEnd(w);
}
+ /**
+ * Handles the cleanup and closure of resources, ensuring that write position, indexing, and mapped file resources
+ * are properly released. This method is called when the store is being closed.
+ */
@Override
protected void performClose() {
Closeable.closeQuietly(writePosition);
Closeable.closeQuietly(indexing);
- // this can be null if we're partially initialised
+ // Release the mapped bytes and file if they are initialized
if (mappedBytes != null) {
mappedBytes.release(INIT);
try {
@@ -286,8 +365,10 @@ protected void performClose() {
}
/**
- * @return creates a new instance of mapped bytes, because, for example the tailer and appender
- * can be at different locations.
+ * Creates and returns a new instance of {@link MappedBytes}. Each instance of the tailer or appender
+ * can be at different positions, so a fresh instance is returned.
+ *
+ * @return a new instance of {@link MappedBytes} for the mapped file
*/
@NotNull
@Override
@@ -299,6 +380,15 @@ public MappedBytes bytes() {
return mbytes;
}
+ /**
+ * Retrieves the sequence number corresponding to the given position in the {@link ExcerptContext}.
+ *
+ * @param ec the excerpt context for reading
+ * @param position the position to find the sequence number for
+ * @param inclusive whether the position should be inclusive
+ * @return the sequence number at the given position
+ * @throws StreamCorruptedException if the stream is corrupted
+ */
@Override
public long sequenceForPosition(@NotNull final ExcerptContext ec, final long position, boolean inclusive) throws StreamCorruptedException {
throwExceptionIfClosed();
@@ -306,11 +396,24 @@ public long sequenceForPosition(@NotNull final ExcerptContext ec, final long pos
return indexing.sequenceForPosition(ec, position, inclusive);
}
+ /**
+ * Retrieves the last sequence number in the given {@link ExcerptContext}.
+ *
+ * @param ec the excerpt context for reading
+ * @return the last sequence number
+ * @throws StreamCorruptedException if the stream is corrupted
+ */
public long lastSequenceNumber(@NotNull ExcerptContext ec) throws StreamCorruptedException {
throwExceptionIfClosedInSetter();
return indexing.lastSequenceNumber(ec);
}
+ /**
+ * Returns a string representation of the store's current state, including details about indexing,
+ * write position, mapped file, and whether the store is closed.
+ *
+ * @return a string representation of the store
+ */
@NotNull
@Override
public String toString() {
@@ -326,6 +429,12 @@ public String toString() {
// Marshalling
// *************************************************************************
+ /**
+ * Writes the current state of the store to the given {@link WireOut}.
+ * This includes the write position, indexing, and data format version.
+ *
+ * @param wire the wire to write to
+ */
@Override
public void writeMarshallable(@NotNull WireOut wire) {
@@ -335,6 +444,11 @@ public void writeMarshallable(@NotNull WireOut wire) {
.write(MetaDataField.dataFormat).int32(dataVersion);
}
+ /**
+ * Initializes the index in the given {@link Wire}.
+ *
+ * @param wire the wire to initialize the index in
+ */
@Override
public void initIndex(@NotNull Wire wire) {
throwExceptionIfClosedInSetter();
@@ -346,11 +460,25 @@ public void initIndex(@NotNull Wire wire) {
}
}
+ /**
+ * Determines if the given index is valid and can be indexed.
+ *
+ * @param index the index to check
+ * @return true if the index is indexable, false otherwise
+ */
@Override
public boolean indexable(long index) {
return indexing.indexable(index);
}
+ /**
+ * Sets the position for the given sequence number in the {@link ExcerptContext}.
+ *
+ * @param ec the excerpt context for reading
+ * @param sequenceNumber the sequence number to set the position for
+ * @param position the position to set
+ * @throws StreamCorruptedException if the stream is corrupted
+ */
@Override
public void setPositionForSequenceNumber(@NotNull final ExcerptContext ec,
long sequenceNumber,
@@ -367,6 +495,15 @@ public void setPositionForSequenceNumber(@NotNull final ExcerptContext ec,
}
+ /**
+ * Performs a linear scan from a known index to another index in the given {@link ExcerptContext}.
+ *
+ * @param index the target index
+ * @param knownIndex the known starting index
+ * @param ec the excerpt context for reading
+ * @param knownAddress the known address to start scanning from
+ * @return the result of the scan
+ */
@Override
public ScanResult linearScanTo(final long index, final long knownIndex, final ExcerptContext ec, final long knownAddress) {
throwExceptionIfClosed();
@@ -374,13 +511,20 @@ public ScanResult linearScanTo(final long index, final long knownIndex, final Ex
return indexing.linearScanTo(index, knownIndex, ec, knownAddress);
}
+ /**
+ * Writes an end-of-file (EOF) marker in the given {@link Wire} and attempts to shrink the file.
+ *
+ * @param wire the wire to write the EOF marker to
+ * @param timeoutMS the timeout for writing the EOF marker
+ * @return true if the EOF marker was written successfully, false otherwise
+ */
@Override
public boolean writeEOF(@NotNull Wire wire, long timeoutMS) {
throwExceptionIfClosed();
String fileName = mappedFile.file().getAbsolutePath();
- // just in case we are about to release this
+ // Attempt to write the EOF marker, and ensure resources are released afterward
if (wire.bytes().tryReserve(this)) {
try {
return writeEOFAndShrink(wire, timeoutMS);
@@ -390,6 +534,7 @@ public boolean writeEOF(@NotNull Wire wire, long timeoutMS) {
}
}
+ // If unable to reserve bytes, create a new instance of MappedBytes and try again
try (MappedBytes bytes = MappedBytes.mappedBytes(mappedFile.file(), mappedFile.chunkSize())) {
Wire wire0 = WireType.valueOf(wire).apply(bytes);
return writeEOFAndShrink(wire0, timeoutMS);
@@ -400,15 +545,33 @@ public boolean writeEOF(@NotNull Wire wire, long timeoutMS) {
}
}
+ /**
+ * Writes an end-of-file (EOF) marker to the wire and shrinks the file if necessary.
+ *
+ * @param wire the wire to write the EOF marker to
+ * @param timeoutMS the timeout for writing the EOF marker
+ * @return true if the EOF marker was written successfully, false otherwise
+ */
boolean writeEOFAndShrink(@NotNull Wire wire, long timeoutMS) {
return wire.writeEndOfWire(timeoutMS, TimeUnit.MILLISECONDS, writePosition());
}
+ /**
+ * Returns the data format version used by this store.
+ *
+ * @return the data format version
+ */
@Override
public int dataVersion() {
return dataVersion;
}
+ /**
+ * Sets the cycle for this store.
+ *
+ * @param cycle the cycle to set
+ * @return the updated {@link SingleChronicleQueueStore}
+ */
public SingleChronicleQueueStore cycle(int cycle) {
throwExceptionIfClosedInSetter();
@@ -416,10 +579,20 @@ public SingleChronicleQueueStore cycle(int cycle) {
return this;
}
+ /**
+ * Returns the current cycle of the store.
+ *
+ * @return the current cycle
+ */
public int cycle() {
return cycle;
}
+ /**
+ * Returns the current file being used by this store.
+ *
+ * @return the file being used by this store
+ */
public File currentFile() {
return mappedFile.file();
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/StoreAppender.java b/src/main/java/net/openhft/chronicle/queue/impl/single/StoreAppender.java
index 3ed5f062f8..0b3411413a 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/StoreAppender.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/StoreAppender.java
@@ -50,12 +50,16 @@
import static net.openhft.chronicle.queue.impl.single.SingleChronicleQueue.WARN_SLOW_APPENDER_MS;
import static net.openhft.chronicle.wire.Wires.*;
+/**
+ * This class represents an appender for a single chronicle queue, allowing for appending
+ * excerpts to the queue. It manages the cycle of the queue, lock handling, and the state
+ * of the wire and store.
+ */
class StoreAppender extends AbstractCloseable
implements ExcerptAppender, ExcerptContext, InternalAppender, MicroTouched {
/**
- * Keep track of where we've normalised EOFs to, so we don't re-do immutable, older cycles every time.
- * This is the key in the table-store where we store that information
+ * Key for storing the normalised EOF information in the table-store to avoid re-normalizing older cycles.
*/
private static final String NORMALISED_EOFS_TO_TABLESTORE_KEY = "normalisedEOFsTo";
@NotNull
@@ -86,6 +90,14 @@ class StoreAppender extends AbstractCloseable
private Wire bufferWire = null;
private int count = 0;
+ /**
+ * Constructor for StoreAppender. Initializes the appender by finding the first open cycle
+ * and setting up the appropriate resources for writing.
+ *
+ * @param queue The chronicle queue to append to
+ * @param storePool The pool for managing wire stores
+ * @param checkInterrupts Flag to indicate whether to check for interrupts during operations
+ */
StoreAppender(@NotNull final SingleChronicleQueue queue,
@NotNull final WireStorePool storePool,
final boolean checkInterrupts) {
@@ -104,6 +116,7 @@ class StoreAppender extends AbstractCloseable
final WriteLock writeLock = this.queue.writeLock();
writeLock.lock();
try {
+ // Process cycles and handle EOF markers
if (firstCycle != Integer.MAX_VALUE) {
// Backing down until EOF-ed cycle is encountered
for (int eofCycle = lastExistingCycle; eofCycle >= firstCycle; eofCycle--) {
@@ -113,7 +126,7 @@ class StoreAppender extends AbstractCloseable
if (eofCycle > firstCycle)
normaliseEOFs0(eofCycle - 1);
- // If first non-EOF file is in the past, it's possible it will be replicated/backfilled to
+ // If a non-EOF file exists, ensure future cycles are set up
if (eofCycle < lastExistingCycle)
setCycle2(eofCycle + 1 /* TODO: Position on existing one? */, WireStoreSupplier.CreateStrategy.READ_ONLY);
break;
@@ -139,6 +152,11 @@ class StoreAppender extends AbstractCloseable
queue.addCloseListener(this);
}
+ /**
+ * Checks if the current cycle has an end-of-file (EOF) marker.
+ *
+ * @return true if the cycle has an EOF marker, false otherwise
+ */
private boolean cycleHasEOF() {
if (wire != null) {
assert this.queue.writeLock().locked();
@@ -157,28 +175,43 @@ private boolean cycleHasEOF() {
return false;
}
+ /**
+ * Releases the resources associated with the given wire, if any.
+ *
+ * @param w the wire whose resources are to be released
+ */
private static void releaseBytesFor(Wire w) {
if (w != null) {
w.bytes().release(INIT);
}
}
+ /**
+ * Checks the append lock to determine if appending is allowed. This version
+ * assumes that the process holding the lock is not the current process.
+ */
private void checkAppendLock() {
checkAppendLock(false);
}
/**
- * check the appendLock
+ * Checks the append lock, with an option to allow the current process to bypass the lock.
*
- * @param allowMyProcess this will only be true for any writes coming from the sink replicator
+ * @param allowMyProcess If true, allows the current process to bypass the append lock.
*/
private void checkAppendLock(boolean allowMyProcess) {
if (appendLock.locked())
checkAppendLockLocked(allowMyProcess);
}
+ /**
+ * Verifies if the append lock is held by another process and throws an exception if appending is not allowed.
+ * This method is called when the lock is held.
+ *
+ * @param allowMyProcess If true, the current process is allowed to append even if the lock is held.
+ */
private void checkAppendLockLocked(boolean allowMyProcess) {
- // separate method as this is in fast path
+ // Perform lock check only when the append lock is an instance of AbstractTSQueueLock
if (appendLock instanceof AbstractTSQueueLock) {
final AbstractTSQueueLock appendLock = (AbstractTSQueueLock) this.appendLock;
final long lockedBy = appendLock.lockedBy();
@@ -188,12 +221,15 @@ private void checkAppendLockLocked(boolean allowMyProcess) {
if (allowMyProcess && myPID)
return;
throw new IllegalStateException("locked: unable to append because a lock is being held by pid=" + (myPID ? "me" : lockedBy) + ", file=" + queue.file());
- } else
+ } else {
throw new IllegalStateException("locked: unable to append, file=" + queue.file());
+ }
}
/**
- * @param marshallable to write to excerpt.
+ * Writes a marshallable object to the excerpt.
+ *
+ * @param marshallable The object to write into the excerpt.
*/
@Override
public void writeBytes(@NotNull final WriteBytesMarshallable marshallable) {
@@ -203,11 +239,16 @@ public void writeBytes(@NotNull final WriteBytesMarshallable marshallable) {
Bytes> bytes = dc.wire().bytes();
long wp = bytes.writePosition();
marshallable.writeMarshallable(bytes);
+
+ // Rollback if no data was written
if (wp == bytes.writePosition())
dc.rollbackOnClose();
}
}
+ /**
+ * Handles the cleanup when the appender is closed, releasing resources and closing the store.
+ */
@Override
protected void performClose() {
releaseBytesFor(wireForIndex);
@@ -231,8 +272,8 @@ protected void performClose() {
}
/**
- * pretouch() has to be run on the same thread, as the thread that created the appender. If you want to use pretouch() in another thread, you must
- * first create or have an appender that was created on this thread, and then use this appender to call the pretouch()
+ * Ensures that the pretouch operation is performed on the same thread as the appender.
+ * If needed, creates a pretoucher to optimize disk IO.
*/
@Override
@SuppressWarnings("deprecation")
@@ -251,6 +292,11 @@ public void pretouch() {
}
}
+ /**
+ * Executes a micro-touch, which may optimize small data access for this appender.
+ *
+ * @return true if the micro-touch operation is successful, false otherwise.
+ */
@Override
public boolean microTouch() {
throwExceptionIfClosed();
@@ -261,6 +307,10 @@ public boolean microTouch() {
return microtoucher.execute();
}
+ /**
+ * Performs a background micro-touch operation on this appender.
+ * Throws an exception if the appender is already closed.
+ */
@Override
public void bgMicroTouch() {
if (isClosed())
@@ -272,37 +322,66 @@ public void bgMicroTouch() {
microtoucher.bgExecute();
}
+ /**
+ * @return the wire associated with this appender.
+ */
@Nullable
@Override
public Wire wire() {
return wire;
}
+ /**
+ * @return the wire used for indexing in this appender.
+ */
@Nullable
@Override
public Wire wireForIndex() {
return wireForIndex;
}
+ /**
+ * @return the timeout in milliseconds for operations in this appender.
+ */
@Override
public long timeoutMS() {
return queue.timeoutMS;
}
+ /**
+ * Sets the last index written by this appender.
+ *
+ * @param index The last index to be set.
+ */
void lastIndex(long index) {
this.lastIndex = index;
}
+ /**
+ * @return true if the appender should record history, false otherwise.
+ */
@Override
public boolean recordHistory() {
return sourceId() != 0;
}
+ /**
+ * Sets the cycle for this appender.
+ *
+ * @param cycle The cycle to be set.
+ */
void setCycle(int cycle) {
if (cycle != this.cycle)
setCycle2(cycle, WireStoreSupplier.CreateStrategy.CREATE);
}
+ /**
+ * Sets the cycle for this appender, managing the wire and store transitions if needed.
+ * It acquires a new store for the specified cycle and resets the wire positions accordingly.
+ *
+ * @param cycle The cycle to set for the appender.
+ * @param createStrategy The strategy used to create a new store.
+ */
private void setCycle2(final int cycle, final WireStoreSupplier.CreateStrategy createStrategy) {
queue.throwExceptionIfClosed();
if (cycle < 0)
@@ -313,16 +392,20 @@ private void setCycle2(final int cycle, final WireStoreSupplier.CreateStrategy c
SingleChronicleQueueStore oldStore = this.store;
+ // Acquire a new store for the specified cycle
SingleChronicleQueueStore newStore = storePool.acquire(cycle, createStrategy, oldStore);
+ // If the store has changed, update and close the old one
if (newStore != oldStore) {
this.store = newStore;
if (oldStore != null)
storePool.closeStore(oldStore);
}
+
+ // Reset the wires after changing the store
resetWires(queue);
- // only set the cycle after the wire is set.
+ // Set the cycle after the wire has been initialized
this.cycle = cycle;
if (this.store == null)
@@ -334,6 +417,12 @@ private void setCycle2(final int cycle, final WireStoreSupplier.CreateStrategy c
queue.onRoll(cycle);
}
+ /**
+ * Resets the wires (primary and indexing) for this appender based on the store.
+ * Releases any existing wire resources before creating new ones.
+ *
+ * @param queue The ChronicleQueue instance to reset wires for.
+ */
private void resetWires(@NotNull final ChronicleQueue queue) {
WireType wireType = queue.wireType();
{
@@ -350,6 +439,13 @@ private void resetWires(@NotNull final ChronicleQueue queue) {
}
}
+ /**
+ * Creates a new wire for the appender based on the wire type and store bytes.
+ * Sets the padding based on the data version.
+ *
+ * @param wireType The wire type used to create the wire.
+ * @return The created Wire object.
+ */
private Wire createWire(@NotNull final WireType wireType) {
final Wire w = wireType.apply(store.bytes());
w.usePadding(store.dataVersion() > 0);
@@ -357,8 +453,11 @@ private Wire createWire(@NotNull final WireType wireType) {
}
/**
- * @return true if the header number is changed, otherwise false
- * @throws UnrecoverableTimeoutException todo
+ * Resets the position of the wire to the last write position and updates the header number.
+ * Verifies that the position and header number are valid and consistent.
+ *
+ * @return true if the header number changed, otherwise false.
+ * @throws UnrecoverableTimeoutException If a timeout occurs during the operation.
*/
private boolean resetPosition() {
long originalHeaderNumber = wire.headerNumber();
@@ -389,12 +488,18 @@ private boolean resetPosition() {
}
}
+ /**
+ * Checks the validity of the header position in the wire's bytes.
+ *
+ * @param bytes The Bytes object representing the wire's data.
+ * @return true if the header position is valid, otherwise false.
+ */
private boolean checkPositionOfHeader(final Bytes> bytes) {
if (positionOfHeader == 0) {
return true;
}
int header = bytes.readVolatileInt(positionOfHeader);
- // ready or an incomplete message header?
+ // Check if the header is ready or incomplete
return isReadyData(header) || isReadyMetaData(header) || isNotComplete(header);
}
@@ -405,22 +510,37 @@ public DocumentContext writingDocument() {
return writingDocument(false); // avoid overhead of a default method.
}
+
+ /**
+ * Prepares and returns the write context for writing a document.
+ *
+ * @param metaData Whether the document contains metadata.
+ * @return The prepared DocumentContext for writing.
+ * @throws UnrecoverableTimeoutException If a timeout occurs while preparing the context.
+ */
@NotNull
@Override
// throws UnrecoverableTimeoutException
public DocumentContext writingDocument(final boolean metaData) {
- throwExceptionIfClosed();
- // we allow the sink process to write metaData
- checkAppendLock(metaData);
- count++;
+ throwExceptionIfClosed(); // Ensure the appender is not closed
+ checkAppendLock(metaData); // Check the append lock before writing
+ count++; // Increment the write count
try {
- return prepareAndReturnWriteContext(metaData);
+ return prepareAndReturnWriteContext(metaData); // Prepare and return the write context
} catch (RuntimeException e) {
- count--;
- throw e;
+ count--; // Decrement the count if an exception occurs
+ throw e; // Rethrow the exception
}
}
+ /**
+ * Prepares and returns the {@link StoreAppenderContext} for writing data. This method checks if
+ * the context needs to be reopened, locks the writeLock, handles double buffering if enabled,
+ * and ensures the wire and cycle are set correctly for appending.
+ *
+ * @param metaData indicates if the write context is for metadata
+ * @return the prepared {@link StoreAppenderContext} ready for writing
+ */
private StoreAppender.StoreAppenderContext prepareAndReturnWriteContext(boolean metaData) {
if (count > 1) {
assert metaData == context.metaData;
@@ -460,6 +580,10 @@ private StoreAppender.StoreAppenderContext prepareAndReturnWriteContext(boolean
return context;
}
+ /**
+ * Prepares the buffer for double buffering during writes. This involves allocating an elastic
+ * buffer and associating it with the current wire type for temporary writing.
+ */
private void prepareDoubleBuffer() {
context.isClosed = false;
context.rollbackOnClose = false;
@@ -472,17 +596,25 @@ private void prepareDoubleBuffer() {
context.metaData(false);
}
+ /**
+ * Acquires a document for writing, ensuring the context is prepared. If a document is already open
+ * in the context, it reuses the same context unless it's a new chain element.
+ *
+ * @param metaData indicates if the document is for metadata
+ * @return the current {@link DocumentContext} for writing
+ */
@Override
public DocumentContext acquireWritingDocument(boolean metaData) {
if (!DISABLE_SINGLE_THREADED_CHECK)
this.threadSafetyCheck(true);
if (context.wire != null && context.isOpen() && context.chainedElement())
- return context;
- return writingDocument(metaData);
+ return context; // Reuse the existing open context
+ return writingDocument(metaData); // Otherwise, open a new document for writing
}
/**
- * Ensure any missing EOF markers are added back to previous cycles
+ * Ensures that EOF markers are properly added to all cycles, normalizing older cycles to ensure they are complete.
+ * This method locks the writeLock and calls the internal {@link #normaliseEOFs0(int)} method for each cycle.
*/
public void normaliseEOFs() {
long start = System.nanoTime();
@@ -498,6 +630,12 @@ public void normaliseEOFs() {
}
}
+ /**
+ * Internal method to normalize EOFs for all cycles up to the specified cycle.
+ * Adds EOF markers where necessary and ensures all earlier cycles are finalized.
+ *
+ * @param cycle the target cycle up to which EOF normalization should occur
+ */
private void normaliseEOFs0(int cycle) {
int first = queue.firstCycle();
@@ -510,6 +648,7 @@ private void normaliseEOFs0(int cycle) {
Jvm.debug().on(StoreAppender.class, "Normalising from cycle " + eofCycle);
}
+ // Process each cycle and add EOF markers
for (; eofCycle < Math.min(queue.cycle(), cycle); ++eofCycle) {
setCycle2(eofCycle, WireStoreSupplier.CreateStrategy.REINITIALIZE_EXISTING);
if (wire != null) {
@@ -520,20 +659,33 @@ private void normaliseEOFs0(int cycle) {
}
}
+ /**
+ * Ensures the wire is set for the specified cycle, normalizing EOFs as needed.
+ * If no wire exists, it creates a new wire for the current cycle.
+ *
+ * @param cycle the cycle for which the wire should be set
+ */
private void setWireIfNull(final int cycle) {
normaliseEOFs0(cycle);
setCycle2(cycle, WireStoreSupplier.CreateStrategy.CREATE);
}
+ /**
+ * Writes a header for the current wire, ensuring the correct position and header number
+ * is set for the next write operation.
+ *
+ * @param wire the {@link Wire} to write the header to
+ * @param safeLength the safe length of data that can be written
+ * @return the position of the written header
+ */
private long writeHeader(@NotNull final Wire wire, final long safeLength) {
Bytes> bytes = wire.bytes();
// writePosition points at the last record in the queue, so we can just skip it and we're ready for write
long pos = positionOfHeader;
long lastPos = store.writePosition();
if (pos < lastPos) {
- // queue moved since we last touched it - recalculate header number
-
+ // Recalculate header number if the queue has moved since the last operation
try {
wire.headerNumber(queue.rollCycle().toIndex(cycle, store.lastSequenceNumber(this)));
} catch (StreamCorruptedException ex) {
@@ -547,6 +699,13 @@ private long writeHeader(@NotNull final Wire wire, final long safeLength) {
return wire.enterHeader(safeLength);
}
+ /**
+ * Opens a new write context for appending data, setting up the necessary parameters such as
+ * the header, write position, and metadata flag.
+ *
+ * @param metaData indicates if the context is for metadata
+ * @param safeLength the maximum length of data that can be safely written
+ */
private void openContext(final boolean metaData, final long safeLength) {
assert wire != null;
this.positionOfHeader = writeHeader(wire, safeLength); // sets wire.bytes().writePosition = position + 4;
@@ -557,8 +716,15 @@ private void openContext(final boolean metaData, final long safeLength) {
context.metaData(metaData);
}
+ /**
+ * Checks if the current header number matches the expected sequence in the queue.
+ * Throws an {@link AssertionError} if there is a mismatch.
+ *
+ * @return true if the header number is valid, false otherwise
+ */
boolean checkWritePositionHeaderNumber() {
if (wire == null || wire.headerNumber() == Long.MIN_VALUE) return true;
+
try {
long pos = positionOfHeader;
@@ -572,8 +738,7 @@ boolean checkWritePositionHeaderNumber() {
" header: " + wire.headerNumber() +
" seq1: " + seq1 +
" seq2: " + seq2;
- AssertionError ae = new AssertionError(message);
- throw ae;
+ throw new AssertionError(message);
}
} catch (Exception e) {
// TODO FIX
@@ -583,11 +748,22 @@ boolean checkWritePositionHeaderNumber() {
return true;
}
+ /**
+ * Returns the source ID of this appender's queue.
+ *
+ * @return the source ID
+ */
@Override
public int sourceId() {
return queue.sourceId;
}
+ /**
+ * Writes the provided {@link BytesStore} to the queue. Locks the queue before writing
+ * and ensures the wire and cycle are correctly set for the operation.
+ *
+ * @param bytes the {@link BytesStore} containing the data to be written
+ */
@Override
public void writeBytes(@NotNull final BytesStore, ?> bytes) {
throwExceptionIfClosed();
@@ -619,10 +795,24 @@ public void writeBytes(@NotNull final BytesStore, ?> bytes) {
}
}
+ /**
+ * Checks if the current wire is inside a valid header. For certain wire types, this method
+ * will validate if the current position is within a header.
+ *
+ * @param wire the {@link Wire} to check
+ * @return true if inside a valid header, false otherwise
+ */
private boolean isInsideHeader(Wire wire) {
return (wire instanceof AbstractWire) ? ((AbstractWire) wire).isInsideHeader() : true;
}
+ /**
+ * Writes the provided {@link BytesStore} to the queue at the specified index.
+ * Acquires a write lock before performing the operation.
+ *
+ * @param index the index to write at
+ * @param bytes the data to be written
+ */
@Override
public void writeBytes(final long index, @NotNull final BytesStore, ?> bytes) {
throwExceptionIfClosed();
@@ -710,6 +900,12 @@ private void position(final long position, final long startOfMessage) {
position0(position, startOfMessage, wire.bytes());
}
+ /**
+ * Returns the index of the last appended entry. If no entries have been appended,
+ * it throws an exception indicating that no data has been appended yet.
+ *
+ * @return the last appended index
+ */
@Override
public long lastIndexAppended() {
if (lastIndex != Long.MIN_VALUE)
@@ -729,6 +925,12 @@ public long lastIndexAppended() {
}
}
+ /**
+ * Returns the current cycle of the queue. If the cycle has not been set, it will determine the
+ * cycle based on the last cycle or the current cycle of the queue.
+ *
+ * @return the current cycle
+ */
@Override
public int cycle() {
if (cycle == Integer.MIN_VALUE) {
@@ -740,6 +942,11 @@ public int cycle() {
return cycle;
}
+ /**
+ * Returns the associated {@link SingleChronicleQueue} for this appender.
+ *
+ * @return the queue associated with this appender
+ */
@Override
@NotNull
public SingleChronicleQueue queue() {
@@ -754,15 +961,23 @@ public SingleChronicleQueue queue() {
void beforeAppend(final Wire wire, final long index) {
}
- /*
- * wire must be not null when this method is called
+ /**
+ * Rolls the current cycle of the queue to the specified cycle, performing necessary
+ * operations such as writing EOF markers and switching the current wire and store.
+ *
+ * @param toCycle the target cycle to roll to
*/
- // throws UnrecoverableTimeoutException
-
private void rollCycleTo(final int toCycle) {
rollCycleTo(toCycle, this.cycle > toCycle);
}
+ /**
+ * Rolls the current cycle to the specified target cycle. If the cycle is being rolled
+ * forward, it writes EOF markers to the current wire before rolling.
+ *
+ * @param cycle the target cycle to roll to
+ * @param suppressEOF flag to suppress writing EOF markers
+ */
private void rollCycleTo(final int cycle, boolean suppressEOF) {
// only a valid check if the wire was set.
@@ -776,6 +991,7 @@ private void rollCycleTo(final int cycle, boolean suppressEOF) {
int lastExistingCycle = queue.lastCycle();
+ // If we're behind the target cycle, roll forward to the last existing cycle first
if (lastExistingCycle < cycle && lastExistingCycle != this.cycle && lastExistingCycle >= 0) {
setCycle2(lastExistingCycle, WireStoreSupplier.CreateStrategy.READ_ONLY);
rollCycleTo(cycle);
@@ -784,17 +1000,33 @@ private void rollCycleTo(final int cycle, boolean suppressEOF) {
}
}
- // throws UnrecoverableTimeoutException
+ /**
+ * Writes the index for a given position in the queue. This method updates the sequence number
+ * for the given index and associates it with the provided position.
+ *
+ * @param index the index to write
+ * @param position the position associated with the index
+ * @throws StreamCorruptedException if the index is corrupted
+ */
void writeIndexForPosition(final long index, final long position) throws StreamCorruptedException {
long sequenceNumber = queue.rollCycle().toSequenceNumber(index);
store.setPositionForSequenceNumber(this, sequenceNumber, position);
}
+ /**
+ * Verifies that the index matches the expected sequence number for the given position.
+ * Throws an assertion error if the index is incorrect or if a discrepancy is found.
+ *
+ * @param index the index to check
+ * @param position the position associated with the index
+ * @return true if the index is correct, false otherwise
+ */
boolean checkIndex(final long index, final long position) {
try {
final long seq1 = queue.rollCycle().toSequenceNumber(index + 1) - 1;
final long seq2 = store.sequenceForPosition(this, position, true);
+ // If the sequence numbers don't match, log an error and perform a linear scan
if (seq1 != seq2) {
final long seq3 = store.indexing
.linearScanByPosition(wireForIndex(), position, 0, 0, true);
@@ -817,6 +1049,12 @@ boolean checkIndex(final long index, final long position) {
return true;
}
+ /**
+ * Returns a string representation of the current state of the StoreAppender,
+ * including information about the queue, cycle, position, last index, and last position.
+ *
+ * @return a string representation of the StoreAppender
+ */
@Override
public String toString() {
return "StoreAppender{" +
@@ -828,18 +1066,38 @@ public String toString() {
'}';
}
+ /**
+ * Sets the internal position and adjusts the {@link Bytes} instance to ensure the write limit
+ * and position are properly set. This method is used to manage the position of data within the
+ * queue.
+ *
+ * @param position the position to set
+ * @param startOfMessage the starting position of the message in the bytes
+ * @param bytes the {@link Bytes} instance associated with the current wire
+ */
void position0(final long position, final long startOfMessage, Bytes> bytes) {
this.positionOfHeader = position;
bytes.writeLimit(bytes.capacity());
bytes.writePosition(startOfMessage);
}
+ /**
+ * Returns the current file associated with this appender. If no store is available,
+ * returns null.
+ *
+ * @return the current file or null if no store is available
+ */
@Override
public File currentFile() {
SingleChronicleQueueStore store = this.store;
return store == null ? null : store.currentFile();
}
+ /**
+ * Synchronizes the data to disk by ensuring that any data written to memory is persisted. This
+ * method is typically used for {@link MappedBytesStore} instances. If no store or wire is
+ * available, this method does nothing.
+ */
@SuppressWarnings("rawtypes")
@Override
public void sync() {
@@ -855,16 +1113,28 @@ public void sync() {
}
}
+ /**
+ * Indicates whether the writing process is complete. This is determined by the context.
+ *
+ * @return true if writing is complete, false otherwise
+ */
@Override
public boolean writingIsComplete() {
return context.writingIsComplete();
}
+ /**
+ * Rolls back the current context if the writing process is not complete.
+ */
@Override
public void rollbackIfNotComplete() {
context.rollbackIfNotComplete();
}
+ /**
+ * Finalizer for the {@link StoreAppender}. If the appender is not properly closed, it rolls
+ * back the context and closes the resources, logging a warning.
+ */
private class Finalizer {
@SuppressWarnings({"deprecation", "removal"})
@Override
@@ -875,6 +1145,10 @@ protected void finalize() throws Throwable {
}
}
+ /**
+ * The inner class responsible for managing the context of a write operation in the {@link StoreAppender}.
+ * This context handles metadata, buffering, and rollback mechanisms for writing operations.
+ */
final class StoreAppenderContext implements WriteDocumentContext {
boolean isClosed = true;
@@ -887,11 +1161,19 @@ final class StoreAppenderContext implements WriteDocumentContext {
private StackTrace closedHere;
private boolean chainedElement;
+ /**
+ * Checks if the context is empty by examining the read remaining bytes of the wire.
+ *
+ * @return true if the context is empty, false otherwise
+ */
public boolean isEmpty() {
Bytes> bytes = wire().bytes();
return bytes.readRemaining() == 0;
}
+ /**
+ * Resets the context, clearing all flags and state variables.
+ */
@Override
public void reset() {
isClosed = true;
@@ -902,43 +1184,69 @@ public void reset() {
chainedElement = false;
}
+ /**
+ * Returns the source ID associated with the current queue.
+ *
+ * @return the source ID
+ */
@Override
public int sourceId() {
return StoreAppender.this.sourceId();
}
+ /**
+ * Indicates whether the context is currently present. This always returns false as
+ * this method is intended for metadata-only contexts.
+ *
+ * @return false always
+ */
@Override
public boolean isPresent() {
return false;
}
+ /**
+ * Returns the wire associated with this context.
+ *
+ * @return the wire for this context
+ */
@Override
public Wire wire() {
return wire;
}
+ /**
+ * Indicates whether the data being written is metadata.
+ *
+ * @return true if the data is metadata, false otherwise
+ */
@Override
public boolean isMetaData() {
return metaData;
}
/**
- * Call this if you have detected an error condition and you want the context rolled back when it is closed, rather than committed
+ * Marks the context to roll back when closed if an error condition is detected.
*/
@Override
public void rollbackOnClose() {
this.rollbackOnClose = true;
}
+ /**
+ * Closes the context, committing or rolling back the changes depending on the state.
+ */
@Override
public void close() {
close(true);
}
/**
- * Close this {@link StoreAppenderContext}.
+ * Close this {@link StoreAppenderContext}, finalizing the writing process and releasing
+ * resources. Depending on the conditions, this method either commits the written data,
+ * rolls it back, or clears the buffer.
*
- * @param unlock true if the {@link this#writeLock} should be unlocked.
+ * @param unlock true if the {@link StoreAppender#writeLock} should be unlocked.
*/
public void close(boolean unlock) {
if (!closePreconditionsAreSatisfied()) return;
@@ -950,11 +1258,13 @@ public void close(boolean unlock) {
if (wire == StoreAppender.this.wire) {
updateHeaderAndIndex();
} else if (wire != null) {
+ // If buffered, write from the buffer and clear it
if (buffered) {
writeBytes(wire.bytes());
unlock = false;
wire.clear();
} else {
+ // Write bytes normally if no buffering is involved
writeBytesInternal(wire.bytes(), metaData);
wire = StoreAppender.this.wire;
}
@@ -969,6 +1279,12 @@ public void close(boolean unlock) {
}
}
+ /**
+ * Rolls back the context when necessary. Returns true if rollback was performed,
+ * otherwise false.
+ *
+ * @return true if rollback was performed, false otherwise
+ */
private boolean handleRollbackOnClose() {
if (rollbackOnClose) {
doRollback();
@@ -978,17 +1294,21 @@ private boolean handleRollbackOnClose() {
}
/**
- * @return true if close preconditions are satisfied, otherwise false.
+ * Ensures that all preconditions for closing the context are satisfied. If not, the method
+ * will either skip the closing process or log a warning if the context has already been
+ * closed.
+ *
+ * @return true if preconditions for closing are met, false otherwise
*/
private boolean closePreconditionsAreSatisfied() {
- if (chainedElement)
+ if (chainedElement) // Don't close if part of a chain
return false;
if (isClosed) {
Jvm.warn().on(getClass(), "Already Closed, close was called twice.", new StackTrace("Second close", closedHere));
alreadyClosedFound = true;
return false;
}
- count--;
+ count--; // Decrement the count, skip closing if still processing
if (count > 0)
return false;
@@ -999,9 +1319,11 @@ private boolean closePreconditionsAreSatisfied() {
}
/**
- * Historically there have been problems with an interrupted thread causing exceptions in calls below, and we
- * saw half-written messages. If interrupt checking is enabled then check if interrupted and handle
- * appropriately.
+ * Handles potential interruptions in the current thread during the closing process.
+ * If interrupts are detected and handling is enabled, the method will throw an
+ * {@link InterruptedException}.
+ *
+ * @throws InterruptedException if the thread is interrupted
*/
private void handleInterrupts() throws InterruptedException {
final boolean interrupted = checkInterrupts && Thread.currentThread().isInterrupted();
@@ -1009,6 +1331,12 @@ private void handleInterrupts() throws InterruptedException {
throw new InterruptedException();
}
+ /**
+ * Updates the header and index after writing. Ensures that the correct position is stored
+ * and, if needed, notifies listeners about the appending process.
+ *
+ * @throws StreamCorruptedException if there is an error updating the header
+ */
private void updateHeaderAndIndex() throws StreamCorruptedException {
if (wire == null) throw new NullPointerException("Wire must not be null");
if (store == null) throw new NullPointerException("Store must not be null");
@@ -1036,9 +1364,10 @@ private void updateHeaderAndIndex() throws StreamCorruptedException {
}
/**
- * Clean-up after close.
+ * Performs cleanup tasks after closing the context. This includes setting the write position
+ * and unlocking the {@link StoreAppender#writeLock} if needed.
*
- * @param unlock true if the {@link this#writeLock} should be unlocked.
+ * @param unlock true if the {@link StoreAppender#writeLock} should be unlocked.
*/
private void closeCleanup(boolean unlock) {
if (wire == null) throw new NullPointerException("Wire must not be null");
@@ -1054,6 +1383,10 @@ private void closeCleanup(boolean unlock) {
}
}
+ /**
+ * Calls the appender listener to process the excerpt at the current position.
+ * The read and write positions of the wire are preserved during this operation.
+ */
private void callAppenderListener() {
final Bytes> bytes = wire.bytes();
long rp = bytes.readPosition();
@@ -1061,17 +1394,24 @@ private void callAppenderListener() {
try {
queue.appenderListener.onExcerpt(wire, lastIndex);
} finally {
+ // Restore the original read and write positions
bytes.readPosition(rp);
bytes.writePosition(wp);
}
}
+ /**
+ * Rolls back the current write operation, clearing any data that was written during the
+ * current context. This ensures that no incomplete or erroneous data is committed to the
+ * queue.
+ */
private void doRollback() {
if (buffered) {
+ // Clear the buffer if buffering is being used
assert wire != StoreAppender.this.wire;
wire.clear();
} else {
- // zero out all contents...
+ // Zero out the content written in this context
final Bytes> bytes = wire.bytes();
try {
for (long i = positionOfHeader; i <= bytes.writePosition(); i++)
@@ -1089,6 +1429,12 @@ private void doRollback() {
}
}
+ /**
+ * Returns the index of the current context. If the context is using double buffering, an
+ * {@link IndexNotAvailableException} will be thrown as the index is not available in this case.
+ *
+ * @return the index of the current context or {@link Long#MIN_VALUE} if the index is unavailable
+ */
@Override
public long index() {
if (buffered) {
@@ -1098,6 +1444,7 @@ public long index() {
return Long.MIN_VALUE;
if (this.wire.headerNumber() == Long.MIN_VALUE) {
try {
+ // Set the header number based on the last sequence number
wire.headerNumber(queue.rollCycle().toIndex(cycle, store.lastSequenceNumber(StoreAppender.this)));
long headerNumber0 = wire.headerNumber();
assert isInsideHeader(this.wire);
@@ -1110,39 +1457,70 @@ public long index() {
return isMetaData() ? Long.MIN_VALUE : this.wire.headerNumber() + 1;
}
+ /**
+ * @return true if the context is still open and not yet closed
+ */
@Override
public boolean isOpen() {
return !isClosed;
}
+ /**
+ * @return true if the context has not been fully completed yet
+ */
@Override
public boolean isNotComplete() {
return !isClosed;
}
+ /**
+ * Unsupported operation in this context.
+ *
+ * @throws UnsupportedOperationException if this method is called
+ */
@Override
public void start(boolean metaData) {
throw new UnsupportedOperationException();
}
+ /**
+ * Sets whether the context is for metadata or not.
+ *
+ * @param metaData true if the context is for metadata, false otherwise
+ */
public void metaData(boolean metaData) {
this.metaData = metaData;
}
+ /**
+ * @return true if the context is part of a chained operation, false otherwise
+ */
@Override
public boolean chainedElement() {
return chainedElement;
}
+ /**
+ * Sets whether the context is part of a chained operation.
+ *
+ * @param chainedElement true if the context is part of a chain, false otherwise
+ */
@Override
public void chainedElement(boolean chainedElement) {
this.chainedElement = chainedElement;
}
+ /**
+ * @return true if the writing process has been completed and the context is closed
+ */
public boolean writingIsComplete() {
return isClosed;
}
+ /**
+ * Rolls back the context if the writing process was not completed. This ensures that no
+ * incomplete data is written to the queue.
+ */
@Override
public void rollbackIfNotComplete() {
if (isClosed) return;
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/StoreTailer.java b/src/main/java/net/openhft/chronicle/queue/impl/single/StoreTailer.java
index 9e1b9d8a91..91f53b418c 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/StoreTailer.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/StoreTailer.java
@@ -50,7 +50,11 @@
import static net.openhft.chronicle.wire.Wires.isEndOfFile;
/**
- * Tailer
+ * This class represents a StoreTailer which reads excerpts from a Chronicle Queue.
+ * It is responsible for managing the current state, index, and reading direction
+ * of the tailer as it processes the queue's messages. It allows for movement through
+ * the queue in both forward and backward directions, ensuring efficient retrieval
+ * of entries by utilizing the appropriate WireStore and cycle mechanism.
*/
class StoreTailer extends AbstractCloseable
implements ExcerptTailer, SourceContext, ExcerptContext {
@@ -78,10 +82,23 @@ class StoreTailer extends AbstractCloseable
private boolean readingDocumentFound = false;
private boolean striding = false;
+ /**
+ * Constructs a StoreTailer to read from the provided queue with the given store pool.
+ *
+ * @param queue the queue to be read
+ * @param storePool the pool that manages WireStore instances
+ */
public StoreTailer(@NotNull final SingleChronicleQueue queue, WireStorePool storePool) {
this(queue, storePool, null);
}
+ /**
+ * Constructs a StoreTailer with the option to provide an index updater.
+ *
+ * @param queue the queue to be read
+ * @param storePool the pool that manages WireStore instances
+ * @param indexUpdater optional index updater for reading from specific positions
+ */
public StoreTailer(@NotNull final SingleChronicleQueue queue, WireStorePool storePool, IndexUpdater indexUpdater) {
boolean error = true;
try {
@@ -126,6 +143,13 @@ public void singleThreadedCheckReset() {
}
}
+ /**
+ * Reads a document using the provided marshallable reader. If the document is available,
+ * it is read and the reader is used to process the document's content.
+ *
+ * @param reader the marshallable reader to process the document's content
+ * @return true if a document was read, false otherwise
+ */
@Override
public boolean readDocument(@NotNull final ReadMarshallable reader) {
throwExceptionIfClosed();
@@ -138,6 +162,11 @@ public boolean readDocument(@NotNull final ReadMarshallable reader) {
return true;
}
+ /**
+ * Retrieves a {@link DocumentContext} for reading the next document.
+ *
+ * @return the document context for reading the next document, or an empty one if no document is present
+ */
@Override
@NotNull
public DocumentContext readingDocument() {
@@ -149,6 +178,9 @@ public DocumentContext readingDocument() {
return readingDocument(false);
}
+ /**
+ * Closes the tailer and releases resources like the index updater and wire.
+ */
@Override
protected void performClose() {
Closeable.closeQuietly(indexUpdater);
@@ -168,6 +200,12 @@ public Wire wire() {
return privateWire();
}
+ /**
+ * Retrieves the private wire associated with the tailer.
+ * This wire is the one used by the tailer's context for reading excerpts.
+ *
+ * @return The wire used by the tailer's context, or null if none is available
+ */
public @Nullable Wire privateWire() {
return context.wire();
}
@@ -189,6 +227,12 @@ public int sourceId() {
return queue.sourceId;
}
+ /**
+ * Provides a string representation of the StoreTailer, showing its current state.
+ *
+ * @return A string describing the StoreTailer, including the current index sequence,
+ * index cycle, store, and queue.
+ */
@NotNull
@Override
public String toString() {
@@ -202,6 +246,13 @@ public String toString() {
", queue=" + queue + '}';
}
+ /**
+ * Reads a document from the queue with an option to include metadata.
+ * If the document's wire has excessive remaining bytes, an AssertionError is thrown.
+ *
+ * @param includeMetaData Whether to include metadata in the document context
+ * @return The document context for reading the document
+ */
@NotNull
@Override
public DocumentContext readingDocument(final boolean includeMetaData) {
@@ -213,6 +264,12 @@ public DocumentContext readingDocument(final boolean includeMetaData) {
return documentContext;
}
+ /**
+ * Internal method to handle the reading of a document.
+ *
+ * @param includeMetaData Whether to include metadata in the document context
+ * @return The document context for reading the document
+ */
DocumentContext readingDocument0(final boolean includeMetaData) {
throwExceptionIfClosed();
@@ -246,7 +303,7 @@ DocumentContext readingDocument0(final boolean includeMetaData) {
} catch (StreamCorruptedException e) {
throw new IllegalStateException(e);
} catch (UnrecoverableTimeoutException notComplete) {
- // so treat as empty.
+ // Treat the document as empty if the operation timed out.
} catch (DecoratedBufferUnderflowException e) {
// read-only tailer view is fixed, a writer could continue past the end of the view
// at the point this tailer was created. Log a warning and return no document.
@@ -255,6 +312,12 @@ DocumentContext readingDocument0(final boolean includeMetaData) {
return INSTANCE;
}
+ /**
+ * Handles a buffer underflow exception during document reading.
+ * Logs a warning for read-only queues, otherwise rethrows the exception.
+ *
+ * @param e The buffer underflow exception encountered during reading
+ */
private void readingDocumentDBUE(DecoratedBufferUnderflowException e) {
if (queue.isReadOnly()) {
Jvm.warn().on(StoreTailer.class,
@@ -265,6 +328,11 @@ private void readingDocumentDBUE(DecoratedBufferUnderflowException e) {
}
}
+ /**
+ * Handles the case where the current cycle is not found during document reading.
+ *
+ * @param next Indicates whether there are more entries to read
+ */
private void readingDocumentCycleNotFound(boolean next) {
RollCycle rollCycle = queue.rollCycle();
if (state == CYCLE_NOT_FOUND && direction == FORWARD) {
@@ -278,16 +346,30 @@ private void readingDocumentCycleNotFound(boolean next) {
}
}
- // throws UnrecoverableTimeoutException
+ /**
+ * Attempts to move to the next document in the queue, taking into account the current state.
+ * This method handles various states such as uninitialised, not reached in cycle, found in cycle, and more.
+ * If unable to progress through the cycles after several attempts, an exception is thrown.
+ *
+ * @param includeMetaData Whether metadata should be included in the document
+ * @return true if a document is found, otherwise false
+ * @throws StreamCorruptedException If the wire data is corrupted
+ * @throws UnrecoverableTimeoutException If the operation times out while trying to access the queue
+ */
private boolean next0(final boolean includeMetaData) throws StreamCorruptedException {
+ // Reset the metadata flag before processing
context.metaData(false);
+
+ // Try up to 1000 iterations to progress through the queue cycles
for (int i = 0; i < 1000; i++) {
switch (state) {
case UNINITIALISED:
+ // Handle uninitialised state by moving to the first index in the queue
final long firstIndex = queue.firstIndex();
if (firstIndex == Long.MAX_VALUE)
return false;
if (includeMetaData) {
+ // If moving to a new cycle succeeds, check for metadata and return
if (moveToCycle(queue.rollCycle().toCycle(firstIndex))) {
final Bytes> bytes = wire().bytes();
inACycleFound(bytes);
@@ -296,18 +378,21 @@ private boolean next0(final boolean includeMetaData) throws StreamCorruptedExcep
return true;
}
} else {
+ // Move to the internal index and return if unsuccessful
if (!moveToIndexInternal(firstIndex))
return false;
}
break;
case NOT_REACHED_IN_CYCLE:
+ // Retry moving to the internal index if not yet reached in cycle
if (!moveToIndexInternal(index))
return false;
break;
case FOUND_IN_CYCLE: {
try {
+ // Attempt to read the next document if found in the current cycle
return inACycle(includeMetaData);
} catch (EOFException eof) {
state = TailerState.END_OF_CYCLE;
@@ -316,6 +401,7 @@ private boolean next0(final boolean includeMetaData) throws StreamCorruptedExcep
}
case END_OF_CYCLE:
+ // Handle reaching the end of the current cycle
if (endOfCycle()) {
continue;
}
@@ -323,11 +409,13 @@ private boolean next0(final boolean includeMetaData) throws StreamCorruptedExcep
return false;
case BEYOND_START_OF_CYCLE:
+ // Handle going beyond the start of a cycle based on the tailer direction
if (beyondStartOfCycle())
continue;
return false;
case CYCLE_NOT_FOUND:
+ // Handle the case where the next cycle is not found
if (nextCycleNotFound())
continue;
return false;
@@ -337,9 +425,16 @@ private boolean next0(final boolean includeMetaData) throws StreamCorruptedExcep
}
}
+ // If unable to progress, throw an exception after the limit of retries is reached
throw new IllegalStateException("Unable to progress to the next cycle, state=" + state);
}
+ /**
+ * Handles the situation when the tailer reaches the end of the current cycle.
+ * It tries to move to the next cycle if available.
+ *
+ * @return true if the next cycle was found and processed, otherwise false
+ */
private boolean endOfCycle() {
final long oldIndex = this.index();
final int currentCycle = queue.rollCycle().toCycle(oldIndex);
@@ -353,6 +448,14 @@ private boolean endOfCycle() {
return false;
}
+ /**
+ * Handles moving beyond the start of a cycle based on the tailer's direction.
+ * If the direction is forward, it resets to the uninitialised state.
+ * If the direction is backward, it processes the backward movement.
+ *
+ * @return true if the state changes and should continue, otherwise false
+ * @throws StreamCorruptedException If the wire data is corrupted
+ */
private boolean beyondStartOfCycle() throws StreamCorruptedException {
if (direction == FORWARD) {
state = UNINITIALISED;
@@ -363,6 +466,13 @@ private boolean beyondStartOfCycle() throws StreamCorruptedException {
throw new AssertionError("direction not set, direction=" + direction);
}
+ /**
+ * Moves the tailer to the next cycle, if available.
+ * Updates the state accordingly based on the result of the move.
+ *
+ * @param nextCycle The next cycle to move to
+ * @return true if the tailer successfully moved to the next cycle, otherwise false
+ */
private boolean nextEndOfCycle(final int nextCycle) {
if (moveToCycle(nextCycle)) {
state = FOUND_IN_CYCLE;
@@ -375,7 +485,7 @@ private boolean nextEndOfCycle(final int nextCycle) {
return false;
}
if (cycle < queue.lastCycle()) {
- // we have encountered an empty file without an EOF marker
+ // Encountered an empty file without an EOF marker, marking as end of cycle
state = END_OF_CYCLE;
return true;
}
@@ -386,11 +496,23 @@ private boolean nextEndOfCycle(final int nextCycle) {
return false;
}
+ /**
+ * Retrieves the index of the last message read by this tailer.
+ *
+ * @return The last read index as a long value
+ */
@Override
public long lastReadIndex() {
return this.lastReadIndex;
}
+ /**
+ * Handles moving beyond the start of a cycle when the direction is backward.
+ * This method checks the position of the last entry and attempts to move to the last available cycle.
+ *
+ * @return true if successfully moved beyond the start of the cycle, otherwise false
+ * @throws StreamCorruptedException If the wire data is corrupted
+ */
private boolean beyondStartOfCycleBackward() throws StreamCorruptedException {
// give the position of the last entry and
// flag we want to count it even though we don't know if it will be meta data or not.
@@ -399,9 +521,11 @@ private boolean beyondStartOfCycleBackward() throws StreamCorruptedException {
if (index < 0)
return false;
+ // Move to the appropriate cycle based on the current index
final boolean foundCycle = cycle(queue.rollCycle().toCycle(index()));
if (foundCycle && this.cycle >= 0) {
+ // Get the last sequence number in this cycle and move to that index
final long lastSequenceNumberInThisCycle = store().sequenceForPosition(this, Long.MAX_VALUE, false);
final long nextIndex = queue.rollCycle().toIndex(this.cycle, lastSequenceNumberInThisCycle);
moveToIndexInternal(nextIndex);
@@ -409,6 +533,7 @@ private boolean beyondStartOfCycleBackward() throws StreamCorruptedException {
return true;
}
+ // If no valid cycle is found, try moving to the next available cycle
final int cycle = queue.rollCycle().toCycle(index());
final long nextIndex = nextIndexWithNextAvailableCycle(cycle);
@@ -418,10 +543,16 @@ private boolean beyondStartOfCycleBackward() throws StreamCorruptedException {
return true;
}
+ // Mark the state as beyond the start of the cycle
state = BEYOND_START_OF_CYCLE;
return false;
}
+ /**
+ * Handles the situation when the next cycle is not found.
+ *
+ * @return true if the next cycle was found and successfully moved to, otherwise false
+ */
private boolean nextCycleNotFound() {
if (index() == Long.MIN_VALUE) {
if (this.store != null)
@@ -437,6 +568,13 @@ private boolean nextCycleNotFound() {
return false;
}
+ /**
+ * Processes the current cycle and handles different scenarios such as replication acknowledgment and direction.
+ *
+ * @param includeMetaData Whether metadata should be included in the document
+ * @return true if successfully processed the current cycle, otherwise false
+ * @throws EOFException If the end of the file is reached
+ */
private boolean inACycle(final boolean includeMetaData) throws EOFException {
if (readAfterReplicaAcknowledged && inACycleCheckRep()) return false;
@@ -452,9 +590,19 @@ private boolean inACycle(final boolean includeMetaData) throws EOFException {
return inACycle2(includeMetaData, wire, bytes);
}
+ /**
+ * Processes the current cycle's data and metadata.
+ *
+ * @param includeMetaData Whether metadata should be included in the document
+ * @param wire The wire object representing the current cycle
+ * @param bytes The byte buffer containing the data
+ * @return true if a valid document is found, otherwise false
+ * @throws EOFException If the end of the file is reached
+ */
private boolean inACycle2(boolean includeMetaData, Wire wire, Bytes> bytes) throws EOFException {
bytes.readLimitToCapacity();
+ // Read the data header and set the context based on whether it's metadata or actual data
switch (wire.readDataHeader(includeMetaData)) {
case NONE:
// no more polling - appender will always write (or recover) EOF
@@ -476,24 +624,36 @@ private boolean inACycle2(boolean includeMetaData, Wire wire, Bytes> bytes) th
private AcknowledgedIndexReplicatedCheck acknowledgedIndexReplicatedCheck
= (index, lastSequenceAck) -> index == lastSequenceAck;
+ /**
+ * Checks if the current index should not be read due to replication acknowledgment.
+ *
+ * @return true if the message should not be read, otherwise false
+ */
private boolean inACycleCheckRep() {
final long lastSequenceAck = queue.lastAcknowledgedIndexReplicated();
final long index = index();
if (lastSequenceAck == -1)
- return true; // don't allow the message to be read
+ return true; // Prevent reading the message
if (index == Long.MIN_VALUE)
- return false; // allow the message to be read
+ return false; // Allow reading the message
- // check both are in the same roll cycle
+ // If the index and acknowledgment are in different cycles, prevent reading the message
if (queue.rollCycle().toCycle(index) != queue.rollCycle().toCycle(lastSequenceAck))
// don't allow the message to be read, until the ack has caught up with the current cycle
// as trying to calculate the number of message between the last cycle and the current cycle
// to too closely in terms of performance to calculate.
return true;
+ // Check if the acknowledgment matches the current index
return !acknowledgedIndexReplicatedCheck.acknowledgedIndexReplicatedCheck(index, lastSequenceAck);
}
+ /**
+ * Processes the cycle when the tailer's direction is not forward.
+ * Attempts to move to the correct index or adjust accordingly if necessary.
+ *
+ * @return true if successfully moved to the index, otherwise false
+ */
private boolean inACycleNotForward() {
if (!moveToIndexInternal(index())) {
try {
@@ -519,6 +679,12 @@ private boolean inACycleNotForward() {
return true;
}
+ /**
+ * Adjusts the internal state to indicate that a message was found within the current cycle.
+ * This method updates the context's read limits and sets the wire's length based on the read position.
+ *
+ * @param bytes The byte buffer representing the current wire's data
+ */
private void inACycleFound(@NotNull final Bytes> bytes) {
context.closeReadLimit(bytes.capacity());
privateWire().readAndSetLength(bytes.readPosition());
@@ -526,18 +692,34 @@ private void inACycleFound(@NotNull final Bytes> bytes) {
context.closeReadPosition(end);
}
+ /**
+ * Attempts to retrieve the next index in the next available cycle. If the store file is missing,
+ * the directory listing is refreshed and another attempt is made.
+ *
+ * @param cycle The current cycle to evaluate
+ * @return The next index in the next available cycle or Long.MIN_VALUE if not found
+ */
private long nextIndexWithNextAvailableCycle(final int cycle) {
try {
return nextIndexWithNextAvailableCycle0(cycle);
} catch (MissingStoreFileException e) {
+ // Refresh the directory listing in case the store file is missing
queue.refreshDirectoryListing();
return nextIndexWithNextAvailableCycle0(cycle);
}
}
+ /**
+ * Internal logic to find the next index in the next available cycle.
+ * If the next cycle is found, retrieves the next available index.
+ *
+ * @param cycle The current cycle to evaluate
+ * @return The next index in the next available cycle or Long.MIN_VALUE if not found
+ */
private long nextIndexWithNextAvailableCycle0(final int cycle) {
assert cycle != Integer.MIN_VALUE : "cycle == Integer.MIN_VALUE";
+ // Return if beyond the last cycle or if direction is not set
if (cycle > queue.lastCycle() || direction == TailerDirection.NONE) {
return Long.MIN_VALUE;
}
@@ -545,10 +727,13 @@ private long nextIndexWithNextAvailableCycle0(final int cycle) {
long nextIndex;
final int nextCycle = cycle + direction.add();
final boolean found = cycle(nextCycle);
+
+ // If the next cycle is found, determine the next index within it
if (found)
nextIndex = nextIndexWithinFoundCycle(nextCycle);
else
try {
+ // Attempt to retrieve the next cycle based on the current state
final int nextCycle0 = queue.nextCycle(this.cycle, direction);
if (nextCycle0 == -1)
return Long.MIN_VALUE;
@@ -586,6 +771,12 @@ private long nextIndexWithNextAvailableCycle0(final int cycle) {
return nextIndex;
}
+ /**
+ * Determines the next index within the given found cycle. Adjusts the tailer's state based on the current direction.
+ *
+ * @param nextCycle The cycle in which to find the next index
+ * @return The next available index within the found cycle
+ */
private long nextIndexWithinFoundCycle(final int nextCycle) {
state = FOUND_IN_CYCLE;
if (direction == FORWARD)
@@ -605,18 +796,33 @@ private long nextIndexWithinFoundCycle(final int nextCycle) {
}
/**
- * @return provides an index that includes the cycle number
+ * Retrieves the current index, which includes the cycle number.
+ * If an index updater is present, it retrieves the volatile value from it.
+ *
+ * @return The current index
*/
@Override
public long index() {
return indexUpdater == null ? this.index : indexUpdater.index().getValue();
}
+ /**
+ * Retrieves the current cycle number.
+ *
+ * @return The current cycle number
+ */
@Override
public int cycle() {
return this.cycle;
}
+ /**
+ * Moves the tailer to the specified index. Depending on the current state and whether the index is near
+ * the last move, this method uses various optimization strategies to efficiently reach the target index.
+ *
+ * @param index The target index to move to
+ * @return true if successfully moved to the target index, otherwise false
+ */
@Override
public boolean moveToIndex(final long index) {
throwExceptionIfClosed();
@@ -638,6 +844,12 @@ public boolean moveToIndex(final long index) {
return moveToIndexInternal(index);
}
+ /**
+ * Moves the tailer to the specified cycle.
+ *
+ * @param cycle The cycle to move to
+ * @return true if successfully moved to the target cycle, otherwise false
+ */
@Override
public boolean moveToCycle(final int cycle) {
throwExceptionIfClosed();
@@ -647,6 +859,12 @@ public boolean moveToCycle(final int cycle) {
return scanResult == FOUND;
}
+ /**
+ * Sets the wire's position if it is not null and returns whether the index was found.
+ *
+ * @param found Indicates whether the target index was found.
+ * @return true if the wire is set and index was found, otherwise false.
+ */
private boolean setAddress(final boolean found) {
final Wire wire = privateWire();
if (wire == null) {
@@ -655,6 +873,13 @@ private boolean setAddress(final boolean found) {
return found;
}
+ /**
+ * Attempts to move the tailer to the specified cycle and sets the index accordingly.
+ * If the cycle is invalid, the method will return {@link ScanResult#NOT_REACHED}.
+ *
+ * @param cycle The cycle to move to.
+ * @return A {@link ScanResult} indicating whether the cycle was reached or not.
+ */
private ScanResult moveToCycleResult0(final int cycle) {
if (cycle < 0)
return NOT_REACHED;
@@ -678,6 +903,12 @@ private ScanResult moveToCycleResult0(final int cycle) {
return FOUND;
}
+ /**
+ * Attempts to move the tailer to the specified index. If the index is invalid, the method will return {@link ScanResult#NOT_REACHED}.
+ *
+ * @param index The index to move to.
+ * @return A {@link ScanResult} indicating whether the index was reached or not.
+ */
private ScanResult moveToIndexResult0(final long index) {
if (index < 0)
return NOT_REACHED;
@@ -697,9 +928,9 @@ private ScanResult moveToIndexResult0(final long index) {
switch (scanResult) {
case FOUND:
Wire privateWire = privateWire();
- if (privateWire == null)
+ if (privateWire == null) {
state = END_OF_CYCLE;
- else {
+ } else {
state = FOUND_IN_CYCLE;
moveToState.onSuccessfulLookup(index, direction, privateWire.bytes().readPosition());
}
@@ -722,11 +953,22 @@ private ScanResult moveToIndexResult0(final long index) {
return scanResult;
}
+ /**
+ * Moves the tailer to the specified index.
+ *
+ * @param index The index to move to.
+ * @return The {@link ScanResult} indicating whether the move was successful or not.
+ */
ScanResult moveToIndexResult(final long index) {
final ScanResult scanResult = moveToIndexResult0(index);
return scanResult;
}
+ /**
+ * Moves the tailer to the start of the queue, resetting the position and state.
+ *
+ * @return The current tailer instance.
+ */
private ExcerptTailer doToStart() {
assert direction != BACKWARD;
final int firstCycle = queue.firstCycle();
@@ -735,7 +977,7 @@ private ExcerptTailer doToStart() {
return this;
}
if (firstCycle != this.cycle) {
- // moves to the expected cycle
+ // Move to the first cycle if it differs
final boolean found = cycle(firstCycle);
if (found)
state = FOUND_IN_CYCLE;
@@ -752,6 +994,11 @@ else if (store != null)
return this;
}
+ /**
+ * Moves the tailer to the start of the queue. Throws an exception if a document is being read when the method is called.
+ *
+ * @return The current tailer instance.
+ */
@NotNull
@Override
public final ExcerptTailer toStart() {
@@ -766,6 +1013,12 @@ public final ExcerptTailer toStart() {
}
}
+ /**
+ * Internally moves the tailer to the specified index.
+ *
+ * @param index The index to move to.
+ * @return true if the move was successful, false otherwise.
+ */
private boolean moveToIndexInternal(final long index) {
moveToState.indexMoveCount++;
final ScanResult scanResult = moveToIndexResult0(index);
@@ -773,10 +1026,9 @@ private boolean moveToIndexInternal(final long index) {
}
/**
- * gives approximately the last index, can not be relied on as the last index may have changed just after this was called. For this reason, this
- * code is not in queue as it should only be an internal method
+ * Provides an approximate last index, based on the current last cycle. This index may change after the method is called.
*
- * @return the last index at the time this method was called, or Long.MIN_VALUE if none.
+ * @return The approximate last index, or Long.MIN_VALUE if no index exists.
*/
private long approximateLastIndex() {
@@ -792,6 +1044,15 @@ private long approximateLastIndex() {
}
}
+ /**
+ * Approximates the last cycle for the specified cycle, and attempts to retrieve the last sequence number within that cycle.
+ * If the sequence number is -1, it tries to find the last cycle that contains data.
+ *
+ * @param lastCycle The last cycle to approximate the last entry for.
+ * @return The index of the last entry in the specified cycle, or the index of the last entry in the queue.
+ * @throws StreamCorruptedException if there is a corruption in the data stream.
+ * @throws MissingStoreFileException if the store file for the specified cycle is missing.
+ */
private long approximateLastCycle2(int lastCycle) throws StreamCorruptedException, MissingStoreFileException {
final RollCycle rollCycle = queue.rollCycle();
final SingleChronicleQueueStore wireStore = (cycle == lastCycle) ? this.store : queue.storeForCycle(
@@ -831,6 +1092,13 @@ private long approximateLastCycle2(int lastCycle) throws StreamCorruptedExceptio
return rollCycle.toIndex(lastCycle, sequenceNumber);
}
+ /**
+ * Performs a header number check by comparing the expected header number with the actual header number.
+ * If the actual header number does not match the expected value, a warning is logged.
+ *
+ * @param wire The wire to perform the header check on.
+ * @return true if the header check passes, false otherwise.
+ */
private boolean headerNumberCheck(@NotNull final Wire wire) {
if (!(wire instanceof AbstractWire)) {
return true;
@@ -855,6 +1123,10 @@ private boolean headerNumberCheck(@NotNull final Wire wire) {
return true;
}
+ /**
+ * Resets the wire for both the main context and index, ensuring the wires are properly aligned with the store.
+ * This is called when switching to a different cycle or when refreshing the wire state.
+ */
private void resetWires() {
final WireType wireType = queue.wireType();
@@ -881,6 +1153,13 @@ private void resetWires() {
}
}
+ /**
+ * Reads the wire, ensuring it can be read from any position within the file.
+ * This method sets the wire's read limit to its capacity.
+ *
+ * @param wire The wire to read from.
+ * @return The wire instance after applying padding and resetting the read position.
+ */
@NotNull
private Wire readAnywhere(@NotNull final Wire wire) {
final Bytes> bytes = wire.bytes();
@@ -893,6 +1172,13 @@ private Wire readAnywhere(@NotNull final Wire wire) {
return wire;
}
+ /**
+ * Moves the tailer to the end of the queue. If the tailer is reading in a backward direction, it calls the original implementation.
+ * If not, it attempts to move to the end using an optimized approach.
+ *
+ * @return The tailer instance after being moved to the end.
+ * @throws IllegalStateException if the tailer is currently reading a document.
+ */
@NotNull
@Override
public ExcerptTailer toEnd() {
@@ -908,6 +1194,12 @@ public ExcerptTailer toEnd() {
return callOptimizedToEnd();
}
+ /**
+ * Calls an optimized method to move the tailer to the end of the queue.
+ * If a store file is missing, it refreshes the directory listing and retries using the original method.
+ *
+ * @return The tailer instance after being moved to the end.
+ */
private ExcerptTailer callOptimizedToEnd() {
try {
return optimizedToEnd();
@@ -917,6 +1209,12 @@ private ExcerptTailer callOptimizedToEnd() {
}
}
+ /**
+ * Moves the tailer to the end of the Chronicle Queue, attempting the original end operation first.
+ * If a {@link NotReachedException} occurs due to a race condition, it retries after refreshing the directory listing.
+ *
+ * @return The tailer positioned at the end of the queue or at the start if the end could not be reached.
+ */
@NotNull
private ExcerptTailer callOriginalToEnd() {
try {
@@ -935,6 +1233,12 @@ private ExcerptTailer callOriginalToEnd() {
}
}
+ /**
+ * Enables or disables striding mode on the tailer.
+ *
+ * @param striding True to enable striding mode, false to disable.
+ * @return The current tailer instance with the updated striding setting.
+ */
@Override
public ExcerptTailer striding(final boolean striding) {
throwExceptionIfClosedInSetter();
@@ -943,11 +1247,21 @@ public ExcerptTailer striding(final boolean striding) {
return this;
}
+ /**
+ * Indicates whether striding mode is enabled for the tailer.
+ *
+ * @return True if striding is enabled, false otherwise.
+ */
@Override
public boolean striding() {
return striding;
}
+ /**
+ * Optimized method to move the tailer to the end of the queue, reducing overhead for faster performance.
+ *
+ * @return The tailer positioned at the end of the queue.
+ */
@NotNull
private ExcerptTailer optimizedToEnd() {
final RollCycle rollCycle = queue.rollCycle();
@@ -998,6 +1312,11 @@ private ExcerptTailer optimizedToEnd() {
return this;
}
+ /**
+ * Moves the tailer to the original end of the queue, checking each index.
+ *
+ * @return The tailer positioned at the end of the queue.
+ */
@NotNull
public ExcerptTailer originalToEnd() {
throwExceptionIfClosed();
@@ -1052,6 +1371,13 @@ public ExcerptTailer originalToEnd() {
}
+ /**
+ * Loop condition to check if the original end logic should continue.
+ *
+ * @param approximateLastIndex The approximate last index in the queue.
+ * @param index The current index being checked.
+ * @return True if the loop should continue, false otherwise.
+ */
private boolean originalToEndLoopCondition(long approximateLastIndex, long index) {
if (direction == FORWARD) {
return true;
@@ -1063,11 +1389,22 @@ private boolean originalToEndLoopCondition(long approximateLastIndex, long index
}
}
+ /**
+ * Retrieves the current direction of the tailer.
+ *
+ * @return The current {@link TailerDirection} of the tailer.
+ */
@Override
public TailerDirection direction() {
return direction;
}
+ /**
+ * Sets the direction of the tailer and moves to the current index if switching from BACKWARD to FORWARD.
+ *
+ * @param direction The new {@link TailerDirection} for the tailer.
+ * @return The current tailer instance with the updated direction.
+ */
@NotNull
@Override
public ExcerptTailer direction(@NotNull final TailerDirection direction) {
@@ -1083,16 +1420,28 @@ public ExcerptTailer direction(@NotNull final TailerDirection direction) {
return this;
}
+ /**
+ * Returns the associated {@link ChronicleQueue} instance.
+ *
+ * @return The ChronicleQueue that this tailer is reading from.
+ */
@Override
@NotNull
public ChronicleQueue queue() {
return queue;
}
+ /**
+ * Increments the index based on the current {@link TailerDirection}.
+ * If moving forward and the sequence number overflows, it rolls over to the next cycle.
+ * If moving backward and the sequence number is negative, it winds back to the previous cycle.
+ */
@PackageLocal
void incrementIndex() {
final RollCycle rollCycle = queue.rollCycle();
final long index = this.index();
+
+ // Handle the case where no index is set and the direction is forward
if (index == -1 && direction == FORWARD) {
index0(0);
return;
@@ -1103,6 +1452,7 @@ void incrementIndex() {
seq += direction.add();
switch (direction) {
case NONE:
+ // No action needed for NONE
break;
case FORWARD:
// if it runs out of seq number it will flow over to tomorrows cycle file
@@ -1117,28 +1467,44 @@ void incrementIndex() {
}
break;
case BACKWARD:
+ // Wind back if the sequence number is negative
if (seq < 0) {
windBackCycle(cycle);
return;
} else if (seq > 0 && striding) {
+ // Adjust sequence number to stride boundary if striding is enabled
seq -= seq % rollCycle.defaultIndexSpacing();
}
break;
}
+
+ // Set the new index
index0(rollCycle.toIndex(cycle, seq));
}
+ /**
+ * Moves the tailer back to the previous cycle if possible.
+ *
+ * @param cycle The current cycle number.
+ */
private void windBackCycle(int cycle) {
final long first = queue.firstCycle();
while (--cycle >= first)
if (tryWindBack(cycle))
return;
+ // If no previous cycle is found, set the index to -1 and update state
this.index(queue.rollCycle().toIndex(cycle, -1));
this.state = BEYOND_START_OF_CYCLE;
}
+ /**
+ * Attempts to move back to a previous cycle and update the index accordingly.
+ *
+ * @param cycle The cycle to attempt winding back to.
+ * @return True if the cycle was successfully moved to, false otherwise.
+ */
private boolean tryWindBack(final int cycle) {
final long count = excerptsInCycle(cycle);
if (count <= 0)
@@ -1152,6 +1518,11 @@ private boolean tryWindBack(final int cycle) {
}
}
+ /**
+ * Updates the internal index to the specified value.
+ *
+ * @param index The new index value to set.
+ */
void index0(final long index) {
if (indexUpdater == null) {
this.index = index;
@@ -1171,6 +1542,12 @@ void index(final long index) {
moveToState.reset();
}
+ /**
+ * Moves the tailer to the specified cycle, resetting wires and state if necessary.
+ *
+ * @param cycle The cycle number to move to.
+ * @return True if the cycle was successfully moved to, false otherwise.
+ */
private boolean cycle(final int cycle) {
if (this.cycle == cycle && (state == FOUND_IN_CYCLE || state == NOT_REACHED_IN_CYCLE))
return true;
@@ -1206,6 +1583,9 @@ private boolean cycle(final int cycle) {
return true;
}
+ /**
+ * Releases the current store and resets the state to UNINITIALISED.
+ */
void releaseStore() {
if (store != null) {
storePool.closeStore(store);
@@ -1214,18 +1594,33 @@ void releaseStore() {
state = UNINITIALISED;
}
+ /**
+ * Enables or disables reading after replica acknowledgment.
+ *
+ * @param readAfterReplicaAcknowledged True to enable reading after acknowledgment, false otherwise.
+ */
@Override
public void readAfterReplicaAcknowledged(final boolean readAfterReplicaAcknowledged) {
throwExceptionIfClosed();
this.readAfterReplicaAcknowledged = readAfterReplicaAcknowledged;
}
+ /**
+ * Sets the {@link AcknowledgedIndexReplicatedCheck} to be used for determining if an index has been replicated.
+ *
+ * @param acknowledgedIndexReplicatedCheck The check implementation for replicated indexes.
+ */
@Override
public void acknowledgedIndexReplicatedCheck(final @NotNull AcknowledgedIndexReplicatedCheck acknowledgedIndexReplicatedCheck) {
readAfterReplicaAcknowledged(true);
this.acknowledgedIndexReplicatedCheck = acknowledgedIndexReplicatedCheck;
}
+ /**
+ * Checks if the tailer is reading after replica acknowledgment.
+ *
+ * @return True if reading after replica acknowledgment is enabled, false otherwise.
+ */
@Override
public boolean readAfterReplicaAcknowledged() {
throwExceptionIfClosed();
@@ -1233,6 +1628,12 @@ public boolean readAfterReplicaAcknowledged() {
return readAfterReplicaAcknowledged;
}
+ /**
+ * Returns the number of excerpts (messages) in the given cycle.
+ *
+ * @param cycle The cycle to check.
+ * @return The number of excerpts in the cycle, or -1 if the cycle could not be found.
+ */
@Override
public long excerptsInCycle(int cycle) {
throwExceptionIfClosed();
@@ -1245,6 +1646,11 @@ public long excerptsInCycle(int cycle) {
}
}
+ /**
+ * Returns the current state of the tailer.
+ *
+ * @return The current {@link TailerState}.
+ */
@NotNull
@Override
public TailerState state() {
@@ -1333,6 +1739,13 @@ public ExcerptTailer afterWrittenMessageAtIndex(@NotNull final ChronicleQueue qu
}
}
+ /**
+ * Provides additional debugging information in case of failure when moving to an index.
+ *
+ * @param tailer The tailer that encountered the failure.
+ * @param messageHistory The message history for additional context.
+ * @return A formatted string containing extra debug information.
+ */
@SuppressWarnings("deprecation")
private String extraInfo(@NotNull final ExcerptTailer tailer, @NotNull final VanillaMessageHistory messageHistory) {
return String.format(
@@ -1342,6 +1755,11 @@ private String extraInfo(@NotNull final ExcerptTailer tailer, @NotNull final Van
tailer.queue().fileAbsolutePath(), Long.toHexString(tailer.index()), WireType.TEXT.asString(messageHistory));
}
+ /**
+ * Sets the cycle to the specified value.
+ *
+ * @param cycle The new cycle value to set.
+ */
public void setCycle(final int cycle) {
this.cycle = cycle;
}
@@ -1351,6 +1769,11 @@ int getIndexMoveCount() {
return moveToState.indexMoveCount;
}
+ /**
+ * Returns the current store, initializing it if necessary.
+ *
+ * @return The current {@link SingleChronicleQueueStore}.
+ */
@NotNull
private SingleChronicleQueueStore store() {
if (store == null)
@@ -1358,12 +1781,22 @@ private SingleChronicleQueueStore store() {
return store;
}
+ /**
+ * Returns the file corresponding to the current cycle in the store.
+ *
+ * @return The current file or null if no store is available.
+ */
@Override
public File currentFile() {
SingleChronicleQueueStore store = this.store;
return store == null ? null : store.currentFile();
}
+ /**
+ * Synchronizes the tailer's underlying store to disk. This ensures that all data up to the
+ * current read position is flushed to disk, providing durability for the queue.
+ * If the store is null, the method returns without performing any operation.
+ */
@SuppressWarnings("rawtypes")
@Override
public void sync() {
@@ -1372,6 +1805,8 @@ public void sync() {
final Bytes> bytes = privateWire().bytes();
BytesStore store = bytes.bytesStore();
+
+ // If the byte store is memory-mapped, synchronize it
if (store instanceof MappedBytesStore) {
MappedBytesStore mbs = (MappedBytesStore) store;
mbs.syncUpTo(bytes.readPosition());
@@ -1379,12 +1814,23 @@ public void sync() {
}
}
+ /**
+ * Helper class that tracks the state of index movement within the tailer.
+ * This is used to optimize subsequent index moves by reusing state information.
+ */
static final class MoveToState {
private long lastMovedToIndex = Long.MIN_VALUE;
private TailerDirection directionAtLastMoveTo = TailerDirection.NONE;
private long readPositionAtLastMove = Long.MIN_VALUE;
private int indexMoveCount = 0;
+ /**
+ * Updates the state after successfully looking up an index.
+ *
+ * @param movedToIndex The index moved to.
+ * @param direction The direction of the movement.
+ * @param readPosition The read position at the new index.
+ */
void onSuccessfulLookup(final long movedToIndex,
final TailerDirection direction,
final long readPosition) {
@@ -1393,6 +1839,13 @@ void onSuccessfulLookup(final long movedToIndex,
this.readPositionAtLastMove = readPosition;
}
+ /**
+ * Updates the state after successfully scanning to an index.
+ *
+ * @param movedToIndex The index moved to.
+ * @param direction The direction of the movement.
+ * @param readPosition The read position at the new index.
+ */
void onSuccessfulScan(final long movedToIndex,
final TailerDirection direction,
final long readPosition) {
@@ -1401,12 +1854,25 @@ void onSuccessfulScan(final long movedToIndex,
this.readPositionAtLastMove = readPosition;
}
+ /**
+ * Resets the state, clearing all stored values.
+ */
void reset() {
lastMovedToIndex = Long.MIN_VALUE;
directionAtLastMoveTo = TailerDirection.NONE;
readPositionAtLastMove = Long.MIN_VALUE;
}
+ /**
+ * Determines if the last index move can be reused based on proximity to the target index
+ * and if the state and direction match.
+ *
+ * @param index The target index.
+ * @param state The current tailer state.
+ * @param direction The direction of movement.
+ * @param queue The ChronicleQueue instance.
+ * @return True if the last move can be reused, false otherwise.
+ */
private boolean indexIsCloseToAndAheadOfLastIndexMove(final long index,
final TailerState state,
final TailerDirection direction,
@@ -1419,6 +1885,16 @@ private boolean indexIsCloseToAndAheadOfLastIndexMove(final long index,
index > lastMovedToIndex;
}
+ /**
+ * Checks if the last index move can be reused based on the current state and index.
+ *
+ * @param index The target index.
+ * @param state The current tailer state.
+ * @param direction The direction of movement.
+ * @param queue The ChronicleQueue instance.
+ * @param wire The current wire.
+ * @return True if the last index move can be reused, false otherwise.
+ */
private boolean canReuseLastIndexMove(final long index,
final TailerState state,
final TailerDirection direction,
@@ -1432,6 +1908,10 @@ private boolean canReuseLastIndexMove(final long index,
}
}
+ /**
+ * Finalizer class to warn and close resources if the StoreTailer has not been closed properly.
+ * This helps in detecting resource leaks by generating warnings when the object is garbage collected.
+ */
private class Finalizer {
@SuppressWarnings({"deprecation", "removal"})
@Override
@@ -1441,21 +1921,38 @@ protected void finalize() throws Throwable {
}
}
+ /**
+ * Context class used by the StoreTailer to manage reading documents and tracking state.
+ * Extends BinaryReadDocumentContext and adds methods for managing wire and metadata.
+ */
class StoreTailerContext extends BinaryReadDocumentContext {
StoreTailerContext() {
super(null);
}
+ /**
+ * Returns the index being processed by the tailer.
+ *
+ * @return The index value.
+ */
@Override
public long index() {
return StoreTailer.this.index();
}
+ /**
+ * Returns the source ID of the tailer.
+ *
+ * @return The source ID.
+ */
@Override
public int sourceId() {
return StoreTailer.this.sourceId();
}
+ /**
+ * Closes the context, and if necessary, increments the index after reading a document.
+ */
@Override
public void close() {
if (rollbackIfNeeded())
@@ -1467,10 +1964,21 @@ public void close() {
super.close();
}
+ /**
+ * Updates the presence flag for the current document.
+ *
+ * @param present Whether the document is present.
+ * @return The updated presence flag.
+ */
boolean present(final boolean present) {
return this.present = present;
}
+ /**
+ * Sets the wire for the context, releasing the old wire if necessary.
+ *
+ * @param wire The new wire to set.
+ */
public void wire(@Nullable final Wire wire) {
if (wire == this.wire)
return;
@@ -1482,6 +1990,11 @@ public void wire(@Nullable final Wire wire) {
oldWire.bytes().release(INIT); // might be held elsewhere if used for another purpose.
}
+ /**
+ * Marks whether the current document is metadata or not.
+ *
+ * @param metaData True if the document is metadata, false otherwise.
+ */
public void metaData(boolean metaData) {
this.metaData = metaData;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListing.java b/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListing.java
index a2cc5e9cc9..817755b9ce 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListing.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListing.java
@@ -31,6 +31,12 @@
import java.nio.file.Path;
import java.util.function.ToIntFunction;
+/**
+ * TableDirectoryListing manages the cycle metadata for a Chronicle Queue stored in a table.
+ * This class is responsible for keeping track of the minimum and maximum cycle numbers created in the queue.
+ * It ensures that the cycle information is properly synchronized and updated, allowing for the detection of new files
+ * and handling the queue's directory listing.
+ */
class TableDirectoryListing extends AbstractCloseable implements DirectoryListing {
private static final String HIGHEST_CREATED_CYCLE = "listing.highestCycle";
@@ -40,6 +46,7 @@ class TableDirectoryListing extends AbstractCloseable implements DirectoryListin
static final int UNSET_MIN_CYCLE = Integer.MAX_VALUE;
static final String INITIAL_MIN_FILENAME = Character.toString(Character.MAX_VALUE);
static final String INITIAL_MAX_FILENAME = Character.toString(Character.MIN_VALUE);
+
private final TableStore> tableStore;
private final Path queuePath;
private final ToIntFunction fileNameToCycleFunction;
@@ -49,6 +56,13 @@ class TableDirectoryListing extends AbstractCloseable implements DirectoryListin
private volatile LongValue modCount;
private long lastRefreshTimeMS = 0;
+ /**
+ * Constructs a new TableDirectoryListing with the specified table store, queue path, and filename to cycle function.
+ *
+ * @param tableStore The table store that holds the cycle metadata.
+ * @param queuePath The path to the Chronicle Queue directory.
+ * @param fileNameToCycleFunction Function to convert filenames to cycle numbers.
+ */
TableDirectoryListing(
final @NotNull TableStore> tableStore,
final Path queuePath,
@@ -63,12 +77,20 @@ class TableDirectoryListing extends AbstractCloseable implements DirectoryListin
singleThreadedCheckDisabled(true);
}
+ /**
+ * Ensures that this listing is only used for writable queues. Throws an exception if the table store is read-only.
+ *
+ * @param tableStore The table store to check.
+ */
protected void checkReadOnly(@NotNull TableStore> tableStore) {
if (tableStore.readOnly()) {
throw new IllegalArgumentException(getClass().getSimpleName() + " should only be used for writable queues");
}
}
+ /**
+ * Initializes the directory listing by acquiring values from the table store.
+ */
@Override
public void init() {
throwExceptionIfClosedInSetter();
@@ -83,12 +105,20 @@ public void init() {
});
}
+ /**
+ * Acquires the necessary LongValues (maxCycle, minCycle, modCount) from the table store.
+ */
protected void initLongValues() {
maxCycleValue = tableStore.acquireValueFor(HIGHEST_CREATED_CYCLE);
minCycleValue = tableStore.acquireValueFor(LOWEST_CREATED_CYCLE);
modCount = tableStore.acquireValueFor(MOD_COUNT);
}
+ /**
+ * Refreshes the directory listing, updating the cycle values if needed. Only refreshes if the force flag is set.
+ *
+ * @param force Whether to force a refresh of the directory listing.
+ */
@Override
public void refresh(final boolean force) {
@@ -110,6 +140,8 @@ public void refresh(final boolean force) {
final String[] fileNamesList = queuePath.toFile().list();
String minFilename = INITIAL_MIN_FILENAME;
String maxFilename = INITIAL_MAX_FILENAME;
+
+ // Scan directory for cycle files and update min/max filenames
if (fileNamesList != null) {
for (String fileName : fileNamesList) {
if (fileName.endsWith(SingleChronicleQueue.SUFFIX)) {
@@ -122,6 +154,7 @@ public void refresh(final boolean force) {
}
}
+ // Convert the min and max filenames to cycle numbers
int min = UNSET_MIN_CYCLE;
if (!INITIAL_MIN_FILENAME.equals(minFilename))
min = fileNameToCycleFunction.applyAsInt(minFilename);
@@ -130,6 +163,7 @@ public void refresh(final boolean force) {
if (!INITIAL_MAX_FILENAME.equals(maxFilename))
max = fileNameToCycleFunction.applyAsInt(maxFilename);
+ // Update cycle values if they have changed
if (currentMin0 == min && currentMax0 == max) {
modCount.addAtomicValue(1);
return;
@@ -144,11 +178,22 @@ public void refresh(final boolean force) {
}
}
+ /**
+ * Handles the creation of a new file by updating the cycle metadata.
+ *
+ * @param file The file that was created.
+ * @param cycle The cycle associated with the file.
+ */
@Override
public void onFileCreated(final File file, final int cycle) {
onRoll(cycle);
}
+ /**
+ * Updates the minimum and maximum cycle values when the queue rolls to a new cycle.
+ *
+ * @param cycle The new cycle number.
+ */
@Override
public void onRoll(int cycle) {
minCycleValue.setMinValue(cycle);
@@ -156,39 +201,77 @@ public void onRoll(int cycle) {
modCount.addAtomicValue(1);
}
+ /**
+ * Returns the timestamp of the last directory listing refresh.
+ *
+ * @return The last refresh time in milliseconds.
+ */
@Override
public long lastRefreshTimeMS() {
return lastRefreshTimeMS;
}
+ /**
+ * Returns the highest cycle number created in the queue.
+ *
+ * @return The highest created cycle.
+ */
@Override
public int getMaxCreatedCycle() {
return getMaxCycleValue();
}
+ /**
+ * Returns the lowest cycle number created in the queue.
+ *
+ * @return The lowest created cycle.
+ */
@Override
public int getMinCreatedCycle() {
return getMinCycleValue();
}
+ /**
+ * Returns the modification count, representing how many times the directory listing has been modified.
+ *
+ * @return The modification count.
+ */
@Override
public long modCount() {
return modCount.getVolatileValue();
}
+ /**
+ * Provides a string representation of the table store's content in binary format.
+ *
+ * @return A string representing the table store's content.
+ */
@Override
public String toString() {
return tableStore.dump(WireType.BINARY_LIGHT);
}
+ /**
+ * Closes the directory listing by releasing resources associated with the LongValues.
+ */
protected void performClose() {
Closeable.closeQuietly(minCycleValue, maxCycleValue, modCount);
}
+ /**
+ * Returns the volatile value of the maximum cycle.
+ *
+ * @return The maximum cycle value.
+ */
private int getMaxCycleValue() {
return (int) maxCycleValue.getVolatileValue();
}
+ /**
+ * Returns the volatile value of the minimum cycle.
+ *
+ * @return The minimum cycle value.
+ */
private int getMinCycleValue() {
return (int) minCycleValue.getVolatileValue();
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListingReadOnly.java b/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListingReadOnly.java
index e568325f5b..78986c4948 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListingReadOnly.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/TableDirectoryListingReadOnly.java
@@ -25,6 +25,11 @@
import java.io.File;
+/**
+ * TableDirectoryListingReadOnly is a specialized version of TableDirectoryListing intended for read-only queues.
+ * It overrides behavior to ensure that read-only queues do not modify cycle metadata or perform any operations
+ * that require writing. Instead, the operations such as file creation and rolling cycles are treated as no-ops.
+ */
class TableDirectoryListingReadOnly extends TableDirectoryListing {
TableDirectoryListingReadOnly(final @NotNull TableStore> tableStore,
@@ -32,16 +37,25 @@ class TableDirectoryListingReadOnly extends TableDirectoryListing {
super(tableStore, null, null, time);
}
+ /**
+ * Overrides the check for read-only mode, as this implementation is inherently read-only.
+ *
+ * @param tableStore The TableStore to check.
+ */
@Override
protected void checkReadOnly(@NotNull TableStore> tableStore) {
- // no-op
+ // no-op, as this class is designed for read-only usage
}
+ /**
+ * Initializes the directory listing for read-only queues, ensuring that partially written long values are handled.
+ * Retries the initialization for up to 500 milliseconds if it fails due to incomplete writes.
+ */
@Override
public void init() {
throwExceptionIfClosedInSetter();
- // it is possible if r/o queue created at same time as r/w queue for longValues to be only half-written
+ // Retry logic to handle cases where the long values might be only partially written
final long timeoutMillis = System.currentTimeMillis() + 500;
while (true) {
try {
@@ -55,18 +69,34 @@ public void init() {
}
}
+ /**
+ * Refreshes the directory listing, but this is a no-op for read-only queues.
+ *
+ * @param force Whether to force a refresh (ignored in this implementation).
+ */
@Override
public void refresh(final boolean force) {
- // no-op
+ // no-op, as refresh is not needed for read-only queues
}
+ /**
+ * Handles file creation in the read-only queue, but this is treated as a no-op.
+ *
+ * @param file The created file (ignored in this implementation).
+ * @param cycle The cycle associated with the file (ignored in this implementation).
+ */
@Override
public void onFileCreated(final File file, final int cycle) {
onRoll(cycle);
}
+ /**
+ * Handles cycle rolling in the read-only queue, but this is treated as a no-op.
+ *
+ * @param cycle The new cycle (ignored in this implementation).
+ */
@Override
public void onRoll(int cycle) {
- // no-op
+ // no-op, as rolling cycles is not applicable to read-only queues
}
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLock.java b/src/main/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLock.java
index 3da1f922a5..2040baa359 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLock.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLock.java
@@ -35,10 +35,13 @@
import static net.openhft.chronicle.core.Jvm.warn;
/**
- * Implements {@link WriteLock} using memory access primitives - see {@link AbstractTSQueueLock}.
- *
- * WARNING: the default behaviour (see also {@code queue.dont.recover.lock.timeout} system property) is
- * for a timed-out lock to be overridden.
+ * The `TableStoreWriteLock` class provides an implementation of {@link WriteLock} using memory access primitives
+ * to ensure safe concurrent access. It uses {@link AbstractTSQueueLock} to manage locking behavior.
+ *
+ *
It provides a non-reentrant locking mechanism that guarantees to acquire the lock or throw an exception
+ * after a timeout. This class supports forceful unlocking depending on the {@link UnlockMode}.
+ * The write lock is used to protect write operations in Chronicle Queue, ensuring that only one thread or process
+ * can write at a time.
*/
public class TableStoreWriteLock extends AbstractTSQueueLock implements WriteLock, Closeable {
private static final String STORE_LOCK_THREAD = "chronicle.store.lock.thread";
@@ -49,18 +52,36 @@ public class TableStoreWriteLock extends AbstractTSQueueLock implements WriteLoc
private Thread lockedByThread = null;
private StackTrace lockedHere;
+ /**
+ * Constructs a {@code TableStoreWriteLock} with a specified table store, pauser, timeout, and lock key.
+ *
+ * @param tableStore The {@link TableStore} object used for acquiring and managing lock state.
+ * @param pauser A {@link Supplier} providing the {@link TimingPauser} instance for pausing between lock retries.
+ * @param timeoutMs The timeout in milliseconds to wait before giving up on acquiring the lock.
+ * @param lockKey The key used for identifying the lock.
+ */
public TableStoreWriteLock(final TableStore> tableStore, Supplier pauser, Long timeoutMs, final String lockKey) {
super(lockKey, tableStore, pauser);
timeout = timeoutMs;
}
+ /**
+ * Constructs a {@code TableStoreWriteLock} with a specified table store, pauser, and timeout, using the default lock key.
+ *
+ * @param tableStore The {@link TableStore} object used for acquiring and managing lock state.
+ * @param pauser A {@link Supplier} providing the {@link TimingPauser} instance for pausing between lock retries.
+ * @param timeoutMs The timeout in milliseconds to wait before giving up on acquiring the lock.
+ */
public TableStoreWriteLock(final TableStore> tableStore, Supplier pauser, Long timeoutMs) {
- this(tableStore, pauser, timeoutMs, LOCK_KEY);
+ this(tableStore, pauser, timeoutMs, LOCK_KEY); // Calls the constructor with default lock key
}
/**
- * Guaranteed to succeed in getting the lock (may involve timeout and recovery) or else throw.
- * This is not re-entrant i.e. if you lock and try and lock again it will timeout and recover
+ * Attempts to acquire the write lock. If the lock is already held by another thread/process, it will retry
+ * until the timeout is reached. If the lock cannot be acquired within the timeout, it may force the unlock
+ * based on the {@link UnlockMode}.
+ *
+ * @throws UnrecoverableTimeoutException if the lock could not be acquired and recovery is not allowed.
*/
@Override
public void lock() {
@@ -100,6 +121,12 @@ private void checkStoreLockThread() {
}
}
+ /**
+ * Handles the case where lock acquisition times out. Depending on the {@link UnlockMode}, it may either
+ * force the unlock or throw an {@link UnrecoverableTimeoutException}.
+ *
+ * @param currentLockValue The current lock value when the timeout occurred.
+ */
private void handleTimeoutEx(long currentLockValue) {
final String lockedBy = getLockedBy(currentLockValue);
final String warningMsg = lockHandleTimeoutExCreateWarningMessage(lockedBy);
@@ -125,6 +152,12 @@ private String lockHandleTimeoutExCreateWarningMessage(String lockedBy) {
"Lock was held by " + lockedBy;
}
+ /**
+ * Returns the process/thread that holds the lock.
+ *
+ * @param value The current lock value.
+ * @return A string representing the process holding the lock, or "me" if held by the current process.
+ */
@SuppressWarnings("deprecation")
@NotNull
protected String getLockedBy(long value) {
@@ -134,6 +167,11 @@ protected String getLockedBy(long value) {
: Long.toString((int) value) + " (TID " + threadId + ")";
}
+ /**
+ * Helper method to check if the lock is already held by the current thread.
+ *
+ * @return {@code true} if the lock is not already held by the current thread, otherwise throws an assertion error.
+ */
private boolean checkNotAlreadyLocked() {
if (!locked())
return true;
@@ -144,6 +182,9 @@ private boolean checkNotAlreadyLocked() {
return true;
}
+ /**
+ * Releases the write lock. If the lock is not held by the current process, a warning is logged.
+ */
@Override
public void unlock() {
throwExceptionIfClosed();
@@ -161,6 +202,11 @@ public void unlock() {
lockedHere = null;
}
+ /**
+ * Checks whether the lock is currently held by any thread or process.
+ *
+ * @return {@code true} if the lock is held, {@code false} otherwise.
+ */
@Override
public boolean locked() {
throwExceptionIfClosed();
@@ -168,7 +214,8 @@ public boolean locked() {
}
/**
- * Don't use this - for internal use only
+ * Forcefully unlocks the lock if it is held, without considering ownership.
+ * This is primarily for internal use.
*/
public void forceUnlock() {
throwExceptionIfClosed();
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/ThreadLocalAppender.java b/src/main/java/net/openhft/chronicle/queue/impl/single/ThreadLocalAppender.java
index f90be4cdac..b939d28272 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/ThreadLocalAppender.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/ThreadLocalAppender.java
@@ -1,3 +1,21 @@
+/*
+ * Copyright 2014-2020 chronicle.software
+ *
+ * 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.queue.ChronicleQueue;
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/WriteLock.java b/src/main/java/net/openhft/chronicle/queue/impl/single/WriteLock.java
index 3c539bc589..392fb8e73a 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/WriteLock.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/WriteLock.java
@@ -20,27 +20,59 @@
import java.io.Closeable;
import java.util.function.LongConsumer;
+/**
+ * The WriteLock interface provides methods to control locking mechanisms in a Chronicle Queue.
+ * It defines locking, unlocking, and checking mechanisms, ensuring exclusive access to resources
+ * while preventing race conditions.
+ *
+ * This interface is non-reentrant, meaning that once a lock is acquired, it cannot be reacquired by
+ * the same process until it is explicitly released.
+ */
public interface WriteLock extends Closeable {
+ /**
+ * A no-operation implementation of the WriteLock interface. This version of WriteLock performs no actions
+ * when methods are invoked, making it suitable as a default or placeholder.
+ */
WriteLock NO_OP = new WriteLock() {
+ /**
+ * No-op implementation for lock.
+ */
@Override
public void lock() {
}
+ /**
+ * No-op implementation for unlock.
+ */
@Override
public void unlock() {
}
+ /**
+ * No-op implementation for close.
+ */
@Override
public void close() {
}
+ /**
+ * Simulates a successful unlock for a dead process.
+ *
+ * @return always returns {@code true} indicating that the lock is unlocked.
+ */
@Override
public boolean forceUnlockIfProcessIsDead() {
return true;
}
+ /**
+ * Simulates that the current process holds the lock.
+ *
+ * @param notCurrentProcessConsumer a consumer for processes that do not hold the lock.
+ * @return always returns {@code true}.
+ */
@Override
public boolean isLockedByCurrentProcess(LongConsumer notCurrentProcessConsumer) {
return true;
@@ -48,18 +80,31 @@ public boolean isLockedByCurrentProcess(LongConsumer notCurrentProcessConsumer)
};
/**
- * Guaranteed to succeed in getting the lock (may involve timeout and recovery) or else throw.
- *
This is not re-entrant i.e. if you lock and try and lock again it will timeout and recover
+ * Acquires the lock, ensuring exclusive access to the resource.
+ *
+ * This method is guaranteed to succeed in acquiring the lock, even if it involves waiting for a timeout or recovering
+ * from a previously held lock. The lock is not reentrant, meaning it cannot be reacquired by the same thread until it
+ * is released.
*/
void lock();
/**
- * May not unlock. If it does not there will be a log.warn
+ * Releases the lock, allowing other threads or processes to acquire it.
+ *
+ * This method may fail to unlock, in which case a warning will be logged.
*/
void unlock();
+ /**
+ * Closes the lock resource. This method is invoked when the lock is no longer needed.
+ */
void close();
+ /**
+ * Checks if the lock is currently held by any process.
+ *
+ * @return {@code false} by default, as this method is designed to be overridden by implementations that support checking.
+ */
default boolean locked() {
return false;
}
@@ -89,5 +134,14 @@ default boolean locked() {
*/
boolean forceUnlockIfProcessIsDead();
+ /**
+ * Checks if the current process holds the lock and provides a callback if not.
+ *
+ * This method verifies if the current process holds the lock. If the lock is held by another process,
+ * the provided {@link LongConsumer} is invoked with the ID of the process holding the lock.
+ *
+ * @param notCurrentProcessConsumer a {@link LongConsumer} that is invoked when the lock is held by another process.
+ * @return {@code true} if the current process holds the lock, otherwise {@code false}.
+ */
boolean isLockedByCurrentProcess(LongConsumer notCurrentProcessConsumer);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdater.java b/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdater.java
index 2c43e89ee1..c021917a21 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdater.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdater.java
@@ -1,3 +1,21 @@
+/*
+ * Copyright 2016-2020 chronicle.software
+ *
+ * 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.namedtailer;
import net.openhft.chronicle.core.values.LongValue;
@@ -5,19 +23,31 @@
import java.io.Closeable;
/**
- * Facilitates updating the persistent index of a named tailer.
+ * The {@code IndexUpdater} interface is responsible for managing and updating the persistent index
+ * of a named tailer. This ensures that the position of the tailer in the queue is saved and can be
+ * retrieved or modified as needed.
+ *
+ * Implementations of this interface should allow for the updating of the index in a persistent manner,
+ * ensuring that the tailer's position in the queue is accurately maintained across operations.
*/
public interface IndexUpdater extends Closeable {
/**
- * Update the persistent index of the named tailer.
+ * Updates the persistent index of the named tailer to the specified {@code index}.
+ *
+ * This method sets the new index value, allowing the tailer to resume from the updated position
+ * when reading from the queue.
*
- * @param index new index value
+ * @param index the new index value to set
*/
void update(long index);
/**
- * @return current index value
+ * Retrieves the current index value of the named tailer.
+ *
+ * The returned {@link LongValue} represents the position of the tailer in the queue.
+ *
+ * @return the current index value of the named tailer
*/
LongValue index();
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdaterFactory.java b/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdaterFactory.java
index 7f1547f499..f89ef4f4a0 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdaterFactory.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/single/namedtailer/IndexUpdaterFactory.java
@@ -12,12 +12,21 @@
import static net.openhft.chronicle.core.io.Closeable.closeQuietly;
/**
- * Factory for creating instances of {@link IndexUpdater}.
+ * The {@code IndexUpdaterFactory} class provides factory methods for creating instances of
+ * {@link IndexUpdater}. Depending on the type of tailer (named or unnamed, replicated or not),
+ * the appropriate {@link IndexUpdater} implementation is returned.
*/
public class IndexUpdaterFactory {
/**
- * Create an instance of an {@link IndexUpdater} depending on the values provided.
+ * Creates an {@link IndexUpdater} based on the provided {@code tailerName} and {@code queue}.
+ *
+ * If the tailer is unnamed, this method returns {@code null}. For replicated named tailers,
+ * a versioned updater is returned. Otherwise, a standard unversioned index updater is used.
+ *
+ * @param tailerName the name of the tailer, or {@code null} if unnamed
+ * @param queue the {@link SingleChronicleQueue} instance
+ * @return the appropriate {@link IndexUpdater} instance, or {@code null} for unnamed tailers
*/
@Nullable
public static IndexUpdater createIndexUpdater(@Nullable String tailerName, @NotNull SingleChronicleQueue queue) {
@@ -40,26 +49,47 @@ public static IndexUpdater createIndexUpdater(@Nullable String tailerName, @NotN
}
/**
- * An index updater that simply sets the index value on update. No versioning.
+ * The {@code StandardIndexUpdater} class implements {@link IndexUpdater} for tailers that do not
+ * require versioning. It simply updates the index value without any additional tracking.
*/
public static class StandardIndexUpdater implements IndexUpdater, Closeable {
private final LongValue indexValue;
+ /**
+ * Constructs a new {@code StandardIndexUpdater} with the provided index value.
+ *
+ * @param indexValue the {@link LongValue} representing the current index
+ */
public StandardIndexUpdater(@NotNull LongValue indexValue) {
this.indexValue = indexValue;
}
+ /**
+ * Closes this {@code StandardIndexUpdater}, releasing any resources held.
+ *
+ * @throws IOException if an I/O error occurs
+ */
@Override
public void close() throws IOException {
closeQuietly(indexValue);
}
+ /**
+ * Updates the index value to the specified {@code index}.
+ *
+ * @param index the new index value
+ */
@Override
public void update(long index) {
indexValue.setValue(index);
}
+ /**
+ * Returns the current index value.
+ *
+ * @return the {@link LongValue} representing the current index
+ */
@Override
public LongValue index() {
return indexValue;
@@ -67,16 +97,23 @@ public LongValue index() {
}
/**
- * An index updater that increments a version field on every update.
+ * The {@code VersionedIndexUpdater} class extends {@link IndexUpdater} for replicated named tailers.
+ * In addition to updating the index, it increments a version field on each update, ensuring version tracking.
*/
public static class VersionedIndexUpdater implements IndexUpdater, Closeable {
private final TableStoreWriteLock versionIndexLock;
-
private final LongValue indexValue;
-
private final LongValue indexVersionValue;
+ /**
+ * Constructs a new {@code VersionedIndexUpdater} with the provided values.
+ *
+ * @param tailerName the name of the tailer
+ * @param queue the {@link SingleChronicleQueue} instance
+ * @param indexValue the {@link LongValue} representing the current index
+ * @param indexVersionValue the {@link LongValue} representing the version of the index
+ */
public VersionedIndexUpdater(@NotNull String tailerName,
@NotNull SingleChronicleQueue queue,
@NotNull LongValue indexValue,
@@ -87,11 +124,22 @@ public VersionedIndexUpdater(@NotNull String tailerName,
this.indexVersionValue = indexVersionValue;
}
+ /**
+ * Closes this {@code VersionedIndexUpdater}, releasing any resources held.
+ *
+ * @throws IOException if an I/O error occurs
+ */
@Override
public void close() throws IOException {
closeQuietly(versionIndexLock, indexValue, indexVersionValue);
}
+ /**
+ * Updates the index value and increments the version field.
+ * This operation is performed within a lock to ensure consistency.
+ *
+ * @param index the new index value
+ */
@Override
public void update(long index) {
try {
@@ -103,6 +151,11 @@ public void update(long index) {
}
}
+ /**
+ * Returns the current index value.
+ *
+ * @return the {@link LongValue} representing the current index
+ */
@Override
public LongValue index() {
return indexValue;
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/table/AbstractTSQueueLock.java b/src/main/java/net/openhft/chronicle/queue/impl/table/AbstractTSQueueLock.java
index 02a6b53662..523416a9ae 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/table/AbstractTSQueueLock.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/table/AbstractTSQueueLock.java
@@ -33,10 +33,14 @@
import static net.openhft.chronicle.core.Jvm.getProcessId;
/**
- * Implements a lock using {@link LongValue} and primitives such as CAS.
+ * Implements a lock using {@link LongValue} and primitives such as Compare-And-Swap (CAS).
*
- * WARNING: the default behaviour (see also {@code queue.dont.recover.lock.timeout} system property) is
- * for a timed-out lock to be overridden.
+ * The lock is associated with a specific table store and can be used to ensure exclusive access
+ * to resources. The locking mechanism allows for forced unlocking in case of a dead process,
+ * with an optional recovery feature based on the {@code queue.force.unlock.mode} system property.
+ *
+ * WARNING: By default, the lock can be overridden if it times out, but this behavior can be controlled
+ * with the system property {@code queue.force.unlock.mode}.
*/
@SuppressWarnings("this-escape")
public abstract class AbstractTSQueueLock extends AbstractCloseable implements Closeable {
@@ -53,6 +57,13 @@ public abstract class AbstractTSQueueLock extends AbstractCloseable implements C
protected final TableStore> tableStore;
private final String lockKey;
+ /**
+ * Constructor for creating an AbstractTSQueueLock.
+ *
+ * @param lockKey The unique key associated with this lock.
+ * @param tableStore The table store this lock will manage.
+ * @param pauserSupplier A supplier for creating a {@link TimingPauser}.
+ */
public AbstractTSQueueLock(final String lockKey, final TableStore> tableStore, final Supplier pauserSupplier) {
this.tableStore = tableStore;
this.lock = tableStore.doWithExclusiveLock(ts -> ts.acquireValueFor(lockKey));
@@ -72,12 +83,17 @@ public AbstractTSQueueLock(final String lockKey, final TableStore> tableStore,
singleThreadedCheckDisabled(true);
}
+ /**
+ * Performs cleanup and releases resources when the lock is closed.
+ */
protected void performClose() {
Closeable.closeQuietly(lock);
}
/**
- * will only force unlock if you give it the correct pid
+ * Forces the lock to be unlocked, only if the current process owns the lock.
+ *
+ * @param value The value representing the current lock holder (process ID and thread).
*/
protected void forceUnlock(long value) {
boolean unlocked = lock.compareAndSwapValue(value, UNLOCKED);
@@ -89,9 +105,15 @@ protected void forceUnlock(long value) {
new StackTrace("Forced unlock"));
}
+ /**
+ * Checks if the lock is held by the current process.
+ *
+ * @param notCurrentProcessConsumer A consumer that will be called if the lock is not held by the current process.
+ * @return {@code true} if the lock is held by the current process, {@code false} otherwise.
+ */
public boolean isLockedByCurrentProcess(LongConsumer notCurrentProcessConsumer) {
final long pid = this.lock.getVolatileValue();
- // mask off thread (if used)
+ // Mask off the thread part, if present
int realPid = (int) pid;
if (realPid == PID)
return true;
@@ -110,6 +132,12 @@ public boolean forceUnlockIfProcessIsDead() {
return forceUnlockIfProcessIsDead(true);
}
+ /**
+ * Forces an unlock if the process holding the lock is no longer alive.
+ *
+ * @param warn If {@code true}, log a warning message; otherwise, log a debug message.
+ * @return {@code true} if the lock was unlocked, otherwise {@code false}.
+ */
protected boolean forceUnlockIfProcessIsDead(boolean warn) {
long pid;
for (; ; ) {
@@ -129,8 +157,9 @@ protected boolean forceUnlockIfProcessIsDead(boolean warn) {
}
if (lock.compareAndSwapValue(pid, UNLOCKED))
return true;
- } else
+ } else {
break;
+ }
}
if (Jvm.isDebugEnabled(this.getClass()))
// don't make this a WARN as this method should only unlock if process is dead or current process.
@@ -140,12 +169,19 @@ protected boolean forceUnlockIfProcessIsDead(boolean warn) {
}
/**
- * @return the pid that had the locked or the returns UNLOCKED if it is not locked
+ * Gets the process ID (PID) that currently holds the lock.
+ *
+ * @return The process ID holding the lock, or {@code UNLOCKED} if it is not locked.
*/
public long lockedBy() {
return lock.getVolatileValue();
}
+ /**
+ * Provides a string representation of the lock, including the lock key and path.
+ *
+ * @return A string describing the lock.
+ */
@Override
public String toString() {
return this.getClass().getSimpleName() + "{" +
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/table/Metadata.java b/src/main/java/net/openhft/chronicle/queue/impl/table/Metadata.java
index c0310f89f6..8311350b2d 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/table/Metadata.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/table/Metadata.java
@@ -23,21 +23,54 @@
import net.openhft.chronicle.wire.WriteMarshallable;
import org.jetbrains.annotations.NotNull;
+/**
+ * The {@code Metadata} interface provides a structure for classes that handle metadata operations
+ * within a Chronicle Queue. It extends {@link Demarshallable} and {@link WriteMarshallable} to support
+ * both deserialization and serialization of metadata objects.
+ */
public interface Metadata extends Demarshallable, WriteMarshallable {
+ /**
+ * Allows overriding of metadata fields with values from another {@code Metadata} object.
+ * This is a default method and can be overridden by implementing classes if needed.
+ *
+ * @param metadata The metadata object to override from.
+ * @param The type of metadata.
+ */
default void overrideFrom(T metadata) {
}
+ /**
+ * An enumeration representing a no-op metadata instance.
+ * Typically used as a placeholder where no actual metadata is needed.
+ */
enum NoMeta implements Metadata {
+ /**
+ * Singleton instance representing the absence of metadata.
+ */
INSTANCE;
+ /**
+ * No-arg constructor for {@code NoMeta}.
+ */
NoMeta() {
}
+ /**
+ * Constructor for {@code NoMeta} that accepts a {@link WireIn} for deserialization.
+ * This constructor is required for classes that implement {@link Demarshallable}.
+ *
+ * @param in The {@link WireIn} instance for deserialization.
+ */
@SuppressWarnings("unused")
NoMeta(@NotNull WireIn in) {
}
+ /**
+ * Writes nothing to the provided {@link WireOut} as this is a no-op metadata implementation.
+ *
+ * @param wire The {@link WireOut} instance to which this metadata is written.
+ */
@Override
public void writeMarshallable(@NotNull WireOut wire) {
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/table/ReadonlyTableStore.java b/src/main/java/net/openhft/chronicle/queue/impl/table/ReadonlyTableStore.java
index 5e88484c64..6fadc65851 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/table/ReadonlyTableStore.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/table/ReadonlyTableStore.java
@@ -29,70 +29,134 @@
import java.io.File;
import java.util.function.Function;
+/**
+ * The {@code ReadonlyTableStore} class is a read-only implementation of the {@link TableStore} interface.
+ * It provides metadata access while throwing {@link UnsupportedOperationException} for any modification attempts.
+ *
+ * @param the type of the metadata used by this table store
+ */
public class ReadonlyTableStore extends AbstractCloseable implements TableStore {
private final T metadata;
+ /**
+ * Constructs a {@code ReadonlyTableStore} with the specified metadata.
+ *
+ * @param metadata the metadata associated with this store
+ */
@SuppressWarnings("this-escape")
public ReadonlyTableStore(T metadata) {
this.metadata = metadata;
singleThreadedCheckDisabled(true);
}
+ /**
+ * Returns the metadata associated with this store.
+ *
+ * @return the metadata
+ */
@Override
public T metadata() {
return metadata;
}
+ /**
+ * No-op close method for read-only table store.
+ */
@Override
protected void performClose() {
}
+ /**
+ * Unsupported operation for acquiring a value in a read-only store.
+ *
+ * @param key the key
+ * @param defaultValue the default value
+ * @return never returns as this is unsupported
+ * @throws UnsupportedOperationException always thrown
+ */
@Override
public LongValue acquireValueFor(CharSequence key, long defaultValue) {
throw new UnsupportedOperationException("Read only");
}
+ /**
+ * Unsupported operation for iterating over keys in a read-only store.
+ *
+ * @param accumulator the accumulator
+ * @param tsIterator the iterator
+ * @throws UnsupportedOperationException always thrown
+ */
@Override
public void forEachKey(T accumulator, TableStoreIterator tsIterator) {
throw new UnsupportedOperationException("Read only");
}
+ /**
+ * Unsupported operation for exclusive locking in a read-only store.
+ *
+ * @param code the code block to execute with the lock
+ * @param the result type
+ * @return never returns as this is unsupported
+ * @throws UnsupportedOperationException always thrown
+ */
@Override
public R doWithExclusiveLock(Function, ? extends R> code) {
- UnsupportedOperationException read_only = new UnsupportedOperationException("Read only");
- throw read_only;
+ throw new UnsupportedOperationException("Read only");
}
+ /**
+ * Unsupported operation for retrieving the file associated with this store.
+ *
+ * @return never returns as this is unsupported
+ * @throws UnsupportedOperationException always thrown
+ */
@Nullable
@Override
public File file() {
throwExceptionIfClosed();
-
- UnsupportedOperationException read_only = new UnsupportedOperationException("Read only");
- throw read_only;
+ throw new UnsupportedOperationException("Read only");
}
+ /**
+ * Unsupported operation for retrieving the bytes associated with this store.
+ *
+ * @return never returns as this is unsupported
+ * @throws UnsupportedOperationException always thrown
+ */
@NotNull
@Override
public MappedBytes bytes() {
throwExceptionIfClosed();
-
- UnsupportedOperationException read_only = new UnsupportedOperationException("Read only");
- throw read_only;
+ throw new UnsupportedOperationException("Read only");
}
+ /**
+ * Returns a string representation of the metadata for dumping.
+ *
+ * @param wireType the wire type to format the output
+ * @return the string representation of the metadata
+ */
@Override
public String dump(WireType wireType) {
return metadata.toString();
}
+ /**
+ * Unsupported operation for writing marshallable data in a read-only store.
+ *
+ * @param wire the wire to write to
+ * @throws UnsupportedOperationException always thrown
+ */
@Override
public void writeMarshallable(@NotNull WireOut wire) {
-
- UnsupportedOperationException read_only = new UnsupportedOperationException("Read only");
- throw read_only;
+ throw new UnsupportedOperationException("Read only");
}
+ /**
+ * Returns true indicating that this store is read-only.
+ *
+ * @return true, as this store is read-only
+ */
@Override
public boolean readOnly() {
return true;
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableBuilder.java b/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableBuilder.java
index 219b7fff51..2cdcfbdc6c 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableBuilder.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableBuilder.java
@@ -125,7 +125,7 @@ public TableStore build() {
// to allocate the first byte store and that will cause lock overlap
bytes.readVolatileInt(0);
Wire wire = wireType.apply(bytes);
- if (readOnly)
+ if (readOnly) {
return SingleTableStore.doWithSharedLock(file, v -> {
try {
return readTableStore(wire);
@@ -133,7 +133,7 @@ public TableStore build() {
throw Jvm.rethrow(ex);
}
}, () -> null);
- else {
+ } else {
MappedBytes finalBytes = bytes;
return SingleTableStore.doWithExclusiveLock(file, v -> {
try {
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableStore.java b/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableStore.java
index e62a6a5e98..a9fe082211 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableStore.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/table/SingleTableStore.java
@@ -46,6 +46,13 @@
import java.util.function.Function;
import java.util.function.Supplier;
+/**
+ * The {@code SingleTableStore} class is a concrete implementation of {@link TableStore}.
+ * It provides functionality for managing metadata and working with mapped bytes in Chronicle Queue.
+ * It handles both shared and exclusive file locks for thread-safety during operations.
+ *
+ * @param Metadata type, which extends {@link Metadata}.
+ */
public class SingleTableStore extends AbstractCloseable implements TableStore {
public static final String SUFFIX = ".cq4t";
private static final int EXCLUSIVE_LOCK_SIZE = 1;
@@ -69,9 +76,9 @@ public class SingleTableStore extends AbstractCloseable impl
private final Wire mappedWire;
/**
- * used by {@link Demarshallable}
+ * Constructor for creating a {@code SingleTableStore} via deserialization using {@link Demarshallable}.
*
- * @param wire a wire
+ * @param wire The {@link WireIn} instance used to read the serialized data.
*/
@SuppressWarnings("unused")
@UsedViaReflection
@@ -96,8 +103,11 @@ private SingleTableStore(@NotNull final WireIn wire) {
}
/**
- * @param wireType the wire type that is being used
- * @param mappedBytes used to mapped the data store file
+ * Constructs a {@code SingleTableStore} with the provided wire type, mapped bytes, and metadata.
+ *
+ * @param wireType The {@link WireType} being used.
+ * @param mappedBytes The {@link MappedBytes} for the data store file.
+ * @param metadata The {@link Metadata} associated with this store.
*/
SingleTableStore(@NotNull final WireType wireType,
@NotNull final MappedBytes mappedBytes,
@@ -112,19 +122,49 @@ private SingleTableStore(@NotNull final WireIn wire) {
singleThreadedCheckDisabled(true);
}
+ /**
+ * Executes a code block with a shared lock on the specified file.
+ *
+ * @param file The file to lock.
+ * @param code The function to execute with the locked file.
+ * @param target A supplier providing the target object for the code block.
+ * @param Type of the target object.
+ * @param Return type of the function.
+ * @return The result of the function applied to the target.
+ */
public static R doWithSharedLock(@NotNull final File file,
@NotNull final Function code,
@NotNull final Supplier target) {
return doWithLock(file, code, target, true);
}
+ /**
+ * Executes a code block with an exclusive lock on the specified file.
+ *
+ * @param file The file to lock.
+ * @param code The function to execute with the locked file.
+ * @param target A supplier providing the target object for the code block.
+ * @param Type of the target object.
+ * @param Return type of the function.
+ * @return The result of the function applied to the target.
+ */
public static R doWithExclusiveLock(@NotNull final File file,
@NotNull final Function code,
@NotNull final Supplier target) {
return doWithLock(file, code, target, false);
}
- // shared vs exclusive - see https://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileChannel.html
+ /**
+ * Handles file locking and executes the provided code block.
+ *
+ * @param file The file to lock.
+ * @param code The function to execute.
+ * @param target The target supplier.
+ * @param shared Whether to use a shared lock.
+ * @param Type of the target object.
+ * @param Return type of the function.
+ * @return The result of the function applied to the target.
+ */
private static R doWithLock(@NotNull final File file,
@NotNull final Function code,
@NotNull final Supplier target,
@@ -170,6 +210,13 @@ public String dump(WireType wireType) {
return dump(wireType, false);
}
+ /**
+ * Dumps the contents of the {@code Wire} in either a verbose or abbreviated format.
+ *
+ * @param wireType The type of wire being dumped.
+ * @param abbrev Whether the output should be abbreviated.
+ * @return A string representing the contents of the wire.
+ */
private String dump(@NotNull WireType wireType, final boolean abbrev) {
final MappedBytes bytes = MappedBytes.mappedBytes(mappedFile);
@@ -215,6 +262,11 @@ private void onCleanup() {
mappedBytes.releaseLast();
}
+ /**
+ * Writes the table store data into the given {@link WireOut} for marshalling.
+ *
+ * @param wire The wire into which the table store data is written.
+ */
@Override
public void writeMarshallable(@NotNull final WireOut wire) {
@@ -227,6 +279,14 @@ public void writeMarshallable(@NotNull final WireOut wire) {
wire.writeAlignTo(Integer.BYTES, 0);
}
+ /**
+ * Acquires a {@link LongValue} mapped to a specific key in the table store.
+ * If the key does not exist, it will create a new entry with the specified default value.
+ *
+ * @param key The key for the value to acquire.
+ * @param defaultValue The default value to use if the key doesn't exist.
+ * @return The acquired {@link LongValue}.
+ */
@Override
public synchronized LongValue acquireValueFor(CharSequence key, final long defaultValue) {
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/table/TableStoreIterator.java b/src/main/java/net/openhft/chronicle/queue/impl/table/TableStoreIterator.java
index fff6740d1d..a0f45c10c4 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/table/TableStoreIterator.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/table/TableStoreIterator.java
@@ -20,6 +20,20 @@
import net.openhft.chronicle.wire.ValueIn;
+/**
+ * The {@code TableStoreIterator} interface provides a method for iterating over key-value pairs in a {@link TableStore}.
+ * The iteration is performed by accepting an accumulator and applying the key-value pair to it.
+ *
+ * @param The type of the accumulator used during the iteration.
+ */
public interface TableStoreIterator {
+
+ /**
+ * Accepts a key-value pair from the table store and applies it to the provided accumulator.
+ *
+ * @param accumulator The accumulator to which the key-value pair is applied.
+ * @param key The key from the table store.
+ * @param value The value associated with the key, represented by {@link ValueIn}.
+ */
void accept(A accumulator, CharSequence key, ValueIn value);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/impl/table/UnlockMode.java b/src/main/java/net/openhft/chronicle/queue/impl/table/UnlockMode.java
index 9a1f4dde87..ffd3a0953b 100644
--- a/src/main/java/net/openhft/chronicle/queue/impl/table/UnlockMode.java
+++ b/src/main/java/net/openhft/chronicle/queue/impl/table/UnlockMode.java
@@ -19,19 +19,23 @@
package net.openhft.chronicle.queue.impl.table;
/**
- * Determines the action to take if lock acquisition times out
+ * Enum representing the action to take when lock acquisition times out in a {@link TableStore}.
+ * It defines different strategies for handling lock acquisition failures.
*/
public enum UnlockMode {
/**
- * force unlock and re-acquire
+ * Always force unlock and re-acquire the lock, regardless of the state of the locking process.
*/
ALWAYS,
+
/**
- * throw exception
+ * Never force unlock. Instead, an exception will be thrown if lock acquisition times out.
*/
NEVER,
+
/**
- * force unlock and re-acquire only if the locking process is dead, otherwise throw exception
+ * Force unlock and re-acquire the lock only if the process holding the lock is no longer alive.
+ * If the locking process is still running, an exception is thrown.
*/
LOCKING_PROCESS_DEAD
}
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/AnalyticsHolder.java b/src/main/java/net/openhft/chronicle/queue/internal/AnalyticsHolder.java
index 32f73db6e3..cfbfcccdda 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/AnalyticsHolder.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/AnalyticsHolder.java
@@ -21,17 +21,30 @@
import net.openhft.chronicle.core.analytics.AnalyticsFacade;
import net.openhft.chronicle.core.pom.PomProperties;
+/**
+ * The {@code AnalyticsHolder} class is a utility to manage the initialization and access to an {@link AnalyticsFacade} instance.
+ *
+ * It fetches the Chronicle Queue version and builds the analytics facade using standard configurations, including
+ * a debug logger.
+ */
public enum AnalyticsHolder {
- ; // none
+ ; // Enum with no instances, acting as a static holder
+ // Fetches the current version of Chronicle Queue from the POM properties
private static final String VERSION = PomProperties.version("net.openhft", "chronicle-queue");
+ // Builds the analytics facade with standard settings, including a debug logger
private static final AnalyticsFacade ANALYTICS = AnalyticsFacade.standardBuilder("G-4K5MBLGPLE", "k1hK3x2qQaKk4F5gL-PBhQ", VERSION)
//.withReportDespiteJUnit()
.withDebugLogger(System.out::println)
//.withUrl("https://www.google-analytics.com/debug/mp/collect")
.build();
+ /**
+ * Provides access to the singleton {@link AnalyticsFacade} instance for use in reporting analytics data.
+ *
+ * @return The singleton {@link AnalyticsFacade} instance
+ */
public static AnalyticsFacade instance() {
return ANALYTICS;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/domestic/QueueOffsetSpec.java b/src/main/java/net/openhft/chronicle/queue/internal/domestic/QueueOffsetSpec.java
index db34679b9a..edce8ca22c 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/domestic/QueueOffsetSpec.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/domestic/QueueOffsetSpec.java
@@ -26,28 +26,67 @@
import java.util.Arrays;
import java.util.function.Function;
+/**
+ * The {@code QueueOffsetSpec} class represents a specification for defining the offset of a Chronicle Queue.
+ * The offset can be defined based on an epoch, roll time, or none.
+ * It provides methods to create, parse, and apply different types of offsets to a {@link SingleChronicleQueueBuilder}.
+ *
+ *
The class is final, and instances are immutable.
+ */
public final class QueueOffsetSpec {
private static final String TOKEN_DELIMITER = ";";
private final Type type;
private final String[] spec;
+
+ /**
+ * Private constructor for initializing {@code QueueOffsetSpec} with a type and corresponding spec arguments.
+ *
+ * @param type the type of offset (EPOCH, ROLL_TIME, or NONE)
+ * @param spec the corresponding arguments for the offset type
+ */
private QueueOffsetSpec(final Type type, final String[] spec) {
this.type = type;
this.spec = spec;
}
+ /**
+ * Creates a {@code QueueOffsetSpec} based on a given epoch time.
+ *
+ * @param epoch the epoch time in milliseconds
+ * @return a new {@code QueueOffsetSpec} instance for epoch-based offset
+ */
public static QueueOffsetSpec ofEpoch(final long epoch) {
return new QueueOffsetSpec(Type.EPOCH, new String[]{Long.toString(epoch)});
}
+ /**
+ * Creates a {@code QueueOffsetSpec} based on a roll time and zone ID.
+ *
+ * @param time the time of the day to roll
+ * @param zoneId the zone ID for the time zone
+ * @return a new {@code QueueOffsetSpec} instance for roll-time-based offset
+ */
public static QueueOffsetSpec ofRollTime(@NotNull final LocalTime time, @NotNull final ZoneId zoneId) {
return new QueueOffsetSpec(Type.ROLL_TIME, new String[]{time.toString(), zoneId.toString()});
}
+ /**
+ * Creates a {@code QueueOffsetSpec} representing no offset.
+ *
+ * @return a new {@code QueueOffsetSpec} instance representing no offset
+ */
public static QueueOffsetSpec ofNone() {
return new QueueOffsetSpec(Type.NONE, new String[]{});
}
+ /**
+ * Parses a string definition to create a corresponding {@code QueueOffsetSpec}.
+ *
+ * @param definition the string representation of the offset specification
+ * @return a new {@code QueueOffsetSpec} instance parsed from the string
+ * @throws IllegalArgumentException if the string format is invalid or the type is unknown
+ */
public static QueueOffsetSpec parse(@NotNull final String definition) {
final String[] tokens = definition.split(TOKEN_DELIMITER);
final Type type = Type.valueOf(tokens[0]);
@@ -66,14 +105,32 @@ public static QueueOffsetSpec parse(@NotNull final String definition) {
}
}
+ /**
+ * Formats an epoch-based offset as a string.
+ *
+ * @param epochOffset the epoch offset value
+ * @return the formatted string representing the epoch offset
+ */
public static String formatEpochOffset(final long epochOffset) {
return String.format("%s;%s", Type.EPOCH.name(), epochOffset);
}
+ /**
+ * Formats a roll-time-based offset as a string.
+ *
+ * @param time the time of day for roll
+ * @param zoneId the time zone ID
+ * @return the formatted string representing the roll time
+ */
public static String formatRollTime(final LocalTime time, final ZoneId zoneId) {
return String.format("%s;%s;%s", Type.ROLL_TIME.name(), time.toString(), zoneId.toString());
}
+ /**
+ * Formats a "none" offset type as a string.
+ *
+ * @return the formatted string representing the "none" offset
+ */
public static String formatNone() {
return Type.NONE.name();
}
@@ -92,6 +149,12 @@ private static void expectArgs(final String[] tokens, final int expectedLength)
}
}
+ /**
+ * Applies the current offset specification to a {@link SingleChronicleQueueBuilder}.
+ *
+ * @param builder the queue builder to apply the offset to
+ * @throws IllegalArgumentException if the offset type is unknown
+ */
public void apply(final SingleChronicleQueueBuilder builder) {
switch (type) {
case EPOCH:
@@ -107,10 +170,20 @@ public void apply(final SingleChronicleQueueBuilder builder) {
}
}
+ /**
+ * Formats the current offset specification as a string.
+ *
+ * @return the formatted string representing the offset specification
+ */
public String format() {
return type.name() + TOKEN_DELIMITER + type.argFormatter.apply(spec);
}
+ /**
+ * Validates the current offset specification by checking the correctness of its arguments.
+ *
+ * @throws IllegalArgumentException if the offset arguments are invalid
+ */
public void validate() {
switch (type) {
case EPOCH:
@@ -127,6 +200,12 @@ public void validate() {
}
}
+ /**
+ * The {@code Type} enum represents the possible types of queue offset specifications:
+ * - EPOCH: Based on an epoch timestamp
+ * - ROLL_TIME: Based on a roll time and zone ID
+ * - NONE: No offset
+ */
public enum Type {
EPOCH(args -> args[0]),
ROLL_TIME(args -> args[0] + TOKEN_DELIMITER + args[1]),
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalBenchmarkMain.java b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalBenchmarkMain.java
index 285dfcb5b3..b0aa5490e8 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalBenchmarkMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalBenchmarkMain.java
@@ -35,6 +35,16 @@
import java.util.concurrent.locks.LockSupport;
+/**
+ * Internal benchmark utility for testing Chronicle Queue throughput.
+ *
+ * The benchmark can be configured via system properties:
+ *
+ * - {@code throughput} - the target throughput in MB/s (default 250)
+ * - {@code runtime} - the benchmark runtime in seconds (default 300)
+ * - {@code path} - the base path for Chronicle Queue (default OS temp directory)
+ *
+ */
public class InternalBenchmarkMain {
static volatile boolean running = true;
static int throughput = Integer.getInteger("throughput", 250); // MB/s
@@ -48,20 +58,38 @@ public class InternalBenchmarkMain {
System.setProperty("jvm.safepoint.enabled", "true");
}
+ /**
+ * The main method executes the benchmark. The throughput, runtime, and base path can be
+ * configured using system properties.
+ *
+ * @param args Command-line arguments (not used)
+ */
public static void main(String[] args) {
+ // Print the current configuration
System.out.println(
"-Dthroughput=" + throughput
+ " -Druntime=" + runtime
+ " -Dpath=" + basePath);
+
+ // Perform a warmup to prepare the system
MappedFile.warmup();
System.out.println("Warming up");
- benchmark(128);
+ benchmark(128); // Run a benchmark with a 128-byte message size for warmup
System.out.println("Warmed up");
+
+ // Perform benchmarks with increasing message sizes, up to 16 MB
for (int size = 64; size <= 16 << 20; size *= 4) {
benchmark(size);
}
}
+ /**
+ * Runs the benchmark for a specified message size.
+ * Measures write, transport, and read latencies, and controls the
+ * flow of writing and reading through ChronicleQueue.
+ *
+ * @param messageSize the size of each message in bytes
+ */
static void benchmark(int messageSize) {
Histogram writeTime = new Histogram(32, 7);
Histogram transportTime = new Histogram(32, 7);
@@ -85,6 +113,7 @@ static void benchmark(int messageSize) {
Histogram loopTime = new Histogram();
+ // Start a thread to read from the queue
Thread reader = new Thread(() -> {
// try (ChronicleQueue queue2 = createQueue(path))
ExcerptTailer tailer = queue.createTailer().toEnd();
@@ -97,6 +126,7 @@ static void benchmark(int messageSize) {
// if (readerLoopTime - readerEndLoopTime > 1000)
// System.out.println("r " + (readerLoopTime - readerEndLoopTime));
// try {
+ // Perform reads from the tailer and sample latencies
runInner(transportTime, readTime, tailer);
runInner(transportTime, readTime, tailer);
runInner(transportTime, readTime, tailer);
@@ -124,6 +154,7 @@ static void benchmark(int messageSize) {
// System.out.println(time);
writeTime.sample(time);
+ // Ensure the reader is keeping up with the writer
long diff = writeTime.totalCount() - readTime.totalCount();
Thread.yield();
if (diff >= 200) {
@@ -136,30 +167,43 @@ static void benchmark(int messageSize) {
System.out.println(sb);
}
+ // Control the write throughput to match the target throughput
next += (long) (messageSize * 1e9 / (throughput * 1e6));
long delay = next - System.nanoTime();
if (delay > 0)
LockSupport.parkNanos(delay);
}
+ // Wait for the reader to catch up before shutting down
while (readTime.totalCount() < writeTime.totalCount())
Jvm.pause(50);
+ // Interrupt the pretoucher and reader threads and clean up
pretoucher.interrupt();
reader.interrupt();
running = false;
// monitor.interrupt();
+ // Output benchmark results
System.out.println("Loop times " + loopTime.toMicrosFormat());
System.out.println("messageSize " + messageSize);
System.out.println("messages " + writeTime.totalCount());
System.out.println("write histogram: " + writeTime.toMicrosFormat());
System.out.println("transport histogram: " + transportTime.toMicrosFormat());
System.out.println("read histogram: " + readTime.toMicrosFormat());
+
+ // Clean up the queue files
IOTools.deleteDirWithFiles(path, 2);
Jvm.pause(1000);
}
+ /**
+ * Processes a single document from the queue using the provided tailer and samples transport and read times.
+ *
+ * @param transportTime The histogram for measuring transport times
+ * @param readTime The histogram for measuring read times
+ * @param tailer The ExcerptTailer used to read from the queue
+ */
private static void runInner(Histogram transportTime, Histogram readTime, ExcerptTailer tailer) {
Jvm.safepoint();
/*if (tailer.peekDocument()) {
@@ -190,6 +234,12 @@ private static void runInner(Histogram transportTime, Histogram readTime, Excerp
Jvm.safepoint();
}
+ /**
+ * Creates a new ChronicleQueue with the given path.
+ *
+ * @param path The path for the Chronicle Queue
+ * @return A new ChronicleQueue instance
+ */
@NotNull
private static ChronicleQueue createQueue(String path) {
return ChronicleQueue.singleBuilder(path)
@@ -198,6 +248,12 @@ private static ChronicleQueue createQueue(String path) {
.build();
}
+ /**
+ * Reads a message from the provided bytes and returns the start time of the message.
+ *
+ * @param bytes The bytes containing the message
+ * @return The start time of the message
+ */
private static long readMessage(Bytes> bytes) {
Jvm.safepoint();
long start = bytes.readLong();
@@ -212,6 +268,12 @@ private static long readMessage(Bytes> bytes) {
return start;
}
+ /**
+ * Writes a message of the specified size to the given wire.
+ *
+ * @param wire The wire to write to
+ * @param messageSize The size of the message to write
+ */
private static void writeMessage(Wire wire, int messageSize) {
Bytes> bytes = wire.bytes();
long wp = bytes.writePosition();
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalDumpMain.java b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalDumpMain.java
index 94cfd2eb49..cd11cd29c1 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalDumpMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalDumpMain.java
@@ -32,6 +32,11 @@
import static java.lang.System.err;
+/**
+ * The InternalDumpMain class provides methods to dump the content of Chronicle Queue and Table Store files.
+ * It outputs the content either to the standard output or to a specified file.
+ * The dump includes detailed binary structure of the files in a human-readable format.
+ */
public class InternalDumpMain {
private static final String FILE = System.getProperty("file");
private static final boolean SKIP_TABLE_STORE = Jvm.getBoolean("skipTableStoreDump");
@@ -42,10 +47,23 @@ public class InternalDumpMain {
SingleChronicleQueueBuilder.addAliases();
}
+ /**
+ * Main entry point for dumping Chronicle Queue or Table Store files.
+ *
+ * @param args Command-line arguments where the first argument is the path of the directory or file to be dumped
+ * @throws FileNotFoundException if the provided file path is invalid
+ */
public static void main(String[] args) throws FileNotFoundException {
dump(args[0]);
}
+ /**
+ * Dumps the content of a Chronicle Queue or Table Store file at the given path.
+ * Outputs to the file specified by the "file" system property, or to standard output if not specified.
+ *
+ * @param path Path to the file or directory to be dumped
+ * @throws FileNotFoundException if the specified file or directory is not found
+ */
public static void dump(@NotNull String path) throws FileNotFoundException {
File path2 = new File(path);
PrintStream out = FILE == null ? System.out : new PrintStream(FILE);
@@ -53,8 +71,17 @@ public static void dump(@NotNull String path) throws FileNotFoundException {
dump(path2, out, upperLimit);
}
+ /**
+ * Dumps the content of files in the provided directory or dumps a single file.
+ * Files are printed in order if it's a directory.
+ *
+ * @param path Path to the directory or file to dump
+ * @param out PrintStream to output the dump results
+ * @param upperLimit Maximum number of bytes to read and dump
+ */
public static void dump(@NotNull File path, @NotNull PrintStream out, long upperLimit) {
if (path.isDirectory()) {
+ // Filter files to dump: based on file suffix (".cq4" for queue, ".tq4" for table store)
final FilenameFilter filter =
SKIP_TABLE_STORE
? (d, n) -> n.endsWith(SingleChronicleQueue.SUFFIX)
@@ -76,6 +103,14 @@ public static void dump(@NotNull File path, @NotNull PrintStream out, long upper
}
}
+ /**
+ * Dumps the content of a single Chronicle Queue or Table Store file.
+ * Outputs each entry in a human-readable format.
+ *
+ * @param file The file to dump
+ * @param out The PrintStream to output the file content to
+ * @param upperLimit Maximum number of bytes to dump
+ */
private static void dumpFile(@NotNull File file, @NotNull PrintStream out, long upperLimit) {
Bytes buffer = Bytes.elasticByteBuffer();
try (MappedBytes bytes = MappedBytes.mappedBytes(file, 4 << 20, OS.pageSize(), !OS.isWindows())) {
@@ -86,7 +121,7 @@ private static void dumpFile(@NotNull File file, @NotNull PrintStream out, long
sb.setLength(0);
boolean last = dumper.dumpOne(sb, buffer);
if (sb.indexOf("\nindex2index:") != -1 || sb.indexOf("\nindex:") != -1) {
- // truncate trailing zeros
+ // Truncate trailing zeros for readability
if (sb.indexOf(", 0\n]\n") == sb.length() - 6) {
int i = indexOfLastZero(sb);
if (i < sb.length())
@@ -111,6 +146,13 @@ private static void dumpFile(@NotNull File file, @NotNull PrintStream out, long
}
}
+ /**
+ * Finds the index of the last zero-value entry in the given character sequence.
+ * Used to truncate trailing zeros for better readability in the dump.
+ *
+ * @param str The CharSequence to search for trailing zeros
+ * @return The index where the last trailing zero was found
+ */
private static int indexOfLastZero(@NotNull CharSequence str) {
int i = str.length() - 3;
do {
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalPingPongMain.java b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalPingPongMain.java
index 47439167da..ecb5db7303 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalPingPongMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalPingPongMain.java
@@ -34,8 +34,12 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+/**
+ * The InternalPingPongMain class demonstrates a ping-pong benchmark using Chronicle Queue.
+ * It writes messages to the queue and reads them back in another thread, recording the latencies
+ * for both operations. The benchmark runs for a defined duration, and results are printed at the end.
+ */
public final class InternalPingPongMain {
- // static int throughput = Integer.getInteger("throughput", 250); // MB/s
static int runtime = Integer.getInteger("runtime", 30); // seconds
static String basePath = System.getProperty("path", OS.TMP);
static AtomicLong writeTime = new AtomicLong();
@@ -47,26 +51,36 @@ public final class InternalPingPongMain {
System.setProperty("jvm.safepoint.enabled", "true");
}
+ /**
+ * Main method to run the ping-pong benchmark.
+ *
+ * @param args Command-line arguments (not used in this benchmark)
+ */
public static void main(String[] args) {
- System.out.println(
-// "-Dthroughput=" + throughput
- " -Druntime=" + runtime
- + " -Dpath=" + basePath);
- MappedFile.warmup();
+ System.out.println(" -Druntime=" + runtime + " -Dpath=" + basePath);
+ MappedFile.warmup(); // Warm up the MappedFile system for consistent performance
+ // Run the ping-pong benchmark with message size of 64 bytes
pingPong(64);
}
+ /**
+ * Executes the ping-pong benchmark, where one thread writes to a Chronicle Queue
+ * and another thread reads from it, measuring the time between writes and reads.
+ *
+ * @param size The size of the message to write in bytes
+ */
static void pingPong(int size) {
String path = InternalPingPongMain.basePath + "/test-q-" + Time.uniqueId();
Histogram readDelay = new Histogram();
Histogram readDelay2 = new Histogram();
try (ChronicleQueue queue = createQueue(path)) {
+ // Thread responsible for reading from the queue
Thread reader = new Thread(() -> {
ExcerptTailer tailer = queue.createTailer();
while (running.get()) {
- //noinspection StatementWithEmptyBody
+ // Wait until there's a message to read
while (readCount.get() == writeCount.get()) ;
long wakeTime = System.nanoTime();
@@ -77,6 +91,7 @@ static void pingPong(int size) {
}
break;
}
+ // Measure the time between when the write happened and the read started
final long delay = wakeTime - writeTime.get();
final long time = System.nanoTime() - wakeTime;
readDelay2.sample(time);
@@ -95,6 +110,7 @@ static void pingPong(int size) {
reader.start();
Jvm.pause(100);
+ // Calculate the finish time based on the runtime property
final long finish = System.currentTimeMillis() + runtime * 1000L;
final ExcerptAppender appender = queue.createAppender();
while (System.currentTimeMillis() < finish) {
@@ -110,11 +126,21 @@ static void pingPong(int size) {
}
running.set(false);
}
+
+ // Output the histograms for the read delays
System.out.println("read delay: " + readDelay.toMicrosFormat());
System.out.println("read delay2: " + readDelay2.toMicrosFormat());
+
+ // Clean up the queue files after the benchmark is finished
IOTools.deleteDirWithFiles(path, 2);
}
+ /**
+ * Creates a Chronicle Queue at the given path.
+ *
+ * @param path The path where the queue will be created
+ * @return The created ChronicleQueue instance
+ */
@NotNull
private static ChronicleQueue createQueue(String path) {
return ChronicleQueue.single(path);
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalRemovableRollFileCandidatesMain.java b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalRemovableRollFileCandidatesMain.java
index b4a337a426..462033050f 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalRemovableRollFileCandidatesMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalRemovableRollFileCandidatesMain.java
@@ -22,6 +22,11 @@
import java.io.File;
+/**
+ * The InternalRemovableRollFileCandidatesMain class is responsible for finding removable
+ * roll file candidates in a specified directory and printing their absolute paths.
+ * If no directory is provided as an argument, the current directory is used by default.
+ */
public final class InternalRemovableRollFileCandidatesMain {
/**
* Produces a list of removable roll file candidates and prints
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalUnlockMain.java b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalUnlockMain.java
index 18b0f6095f..2a4fe239f7 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/main/InternalUnlockMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/main/InternalUnlockMain.java
@@ -29,15 +29,32 @@
import static net.openhft.chronicle.queue.impl.single.SingleChronicleQueue.QUEUE_METADATA_FILE;
+/**
+ * The InternalUnlockMain class is responsible for unlocking a Chronicle Queue's table store write locks.
+ * This is useful in cases where a queue's lock has been left in an inconsistent state and needs to be manually unlocked.
+ * The class requires a queue directory as input and operates on the queue metadata file located within that directory.
+ */
public final class InternalUnlockMain {
static {
SingleChronicleQueueBuilder.addAliases();
}
+ /**
+ * Main method to execute the unlocking process.
+ *
+ * @param args Arguments provided, where the first argument should be the path to the queue directory.
+ */
public static void main(String[] args) {
unlock(args[0]);
}
+ /**
+ * Unlocks the queue's metadata file locks located within the provided directory.
+ * It forcefully unlocks both the appender lock and the main write lock.
+ *
+ * @param dir The directory path containing the queue metadata file.
+ * Must be a valid queue directory with a metadata file.
+ */
private static void unlock(@NotNull String dir) {
File path = new File(dir);
if (!path.isDirectory()) {
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalDummyMethodReaderQueueEntryHandler.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalDummyMethodReaderQueueEntryHandler.java
index ba914523ad..9263af1319 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalDummyMethodReaderQueueEntryHandler.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalDummyMethodReaderQueueEntryHandler.java
@@ -29,21 +29,45 @@
import static net.openhft.chronicle.core.util.ObjectUtils.requireNonNull;
+/**
+ * The {@code InternalDummyMethodReaderQueueEntryHandler} is a dummy implementation of the {@link QueueEntryHandler} interface
+ * for processing method reader entries from a queue.
+ *
+ * It converts binary wire entries into a specified wire type (e.g., text) and passes the result to the message handler.
+ * This implementation is particularly useful when you need to process queue entries as a text representation.
+ */
public final class InternalDummyMethodReaderQueueEntryHandler implements QueueEntryHandler {
private final Bytes> textConversionTarget = Bytes.allocateElasticOnHeap();
private final WireType wireType;
+ /**
+ * Constructs an {@code InternalDummyMethodReaderQueueEntryHandler} with the specified {@link WireType}.
+ *
+ * @param wireType The wire type to be used for converting entries, must not be null
+ */
public InternalDummyMethodReaderQueueEntryHandler(@NotNull WireType wireType) {
this.wireType = requireNonNull(wireType);
}
+ /**
+ * Processes entries from the given {@link WireIn}, converting them to the specified wire type and passing
+ * the result to the provided {@code messageHandler}.
+ *
+ * This method reads the binary wire entries, converts them to the target format, and passes the result to the
+ * message handler every two entries (i.e., after every second entry).
+ *
+ * @param wireIn The wire input to process
+ * @param messageHandler The handler that processes the converted message
+ */
@Override
public void accept(final WireIn wireIn, final Consumer messageHandler) {
long elementCount = 0;
while (wireIn.hasMore()) {
+ // Convert binary wire entries into the specified wire type and store in textConversionTarget
new BinaryWire(wireIn.bytes()).copyOne(wireType.apply(textConversionTarget));
elementCount++;
+ // Every two elements, pass the converted text to the message handler and clear the buffer
if ((elementCount & 1) == 0) {
messageHandler.accept(textConversionTarget.toString());
textConversionTarget.clear();
@@ -51,6 +75,9 @@ public void accept(final WireIn wireIn, final Consumer messageHandler) {
}
}
+ /**
+ * Releases the resources used by this entry handler, particularly the {@code textConversionTarget} buffer.
+ */
@Override
public void close() {
textConversionTarget.releaseLast();
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalMessageToTextQueueEntryHandler.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalMessageToTextQueueEntryHandler.java
index 67d57e3496..b984cf13db 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalMessageToTextQueueEntryHandler.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/InternalMessageToTextQueueEntryHandler.java
@@ -28,18 +28,47 @@
import static net.openhft.chronicle.core.util.ObjectUtils.requireNonNull;
+/**
+ * {@code InternalMessageToTextQueueEntryHandler} is responsible for converting queue entries into text format.
+ *
+ * It handles both binary and text formats by inspecting the data format indicator, converting binary data
+ * using the provided {@link WireType}, and passing the resulting text to the {@code messageHandler}.
+ *
+ * This handler can be used to transform queue entries into a human-readable format such as JSON or YAML.
+ */
public final class InternalMessageToTextQueueEntryHandler implements QueueEntryHandler {
private final Bytes> textConversionTarget = Bytes.allocateElasticOnHeap();
private final WireType wireType;
+ /**
+ * Constructs an {@code InternalMessageToTextQueueEntryHandler} with the specified {@link WireType}.
+ *
+ * @param wireType The wire type used for converting binary data, must not be null
+ */
public InternalMessageToTextQueueEntryHandler(WireType wireType) {
this.wireType = requireNonNull(wireType);
}
+ /**
+ * Determines if the given data is in binary format based on the data format indicator byte.
+ *
+ * @param dataFormatIndicator The byte representing the data format
+ * @return {@code true} if the data is in binary format, {@code false} otherwise
+ */
private static boolean isBinaryFormat(final byte dataFormatIndicator) {
return dataFormatIndicator < 0;
}
+ /**
+ * Processes entries from the provided {@link WireIn}, converts them to text if in binary format,
+ * and passes the result to the provided {@code messageHandler}.
+ *
+ * If the entry is binary, it will be converted using the specified {@link WireType}. Otherwise,
+ * the raw text will be passed through directly.
+ *
+ * @param wireIn The wire input to process
+ * @param messageHandler The handler that processes the converted or raw message text
+ */
@Override
public void accept(final WireIn wireIn, final Consumer messageHandler) {
final Bytes> serialisedMessage = wireIn.bytes();
@@ -47,17 +76,22 @@ public void accept(final WireIn wireIn, final Consumer messageHandler) {
String text;
if (isBinaryFormat(dataFormatIndicator)) {
+ // Convert binary data to text using the specified WireType
textConversionTarget.clear();
final BinaryWire binaryWire = new BinaryWire(serialisedMessage);
binaryWire.copyTo(wireType.apply(textConversionTarget));
text = textConversionTarget.toString();
} else {
+ // Directly use the serialized message if it's not in binary format
text = serialisedMessage.toString();
}
messageHandler.accept(text);
}
+ /**
+ * Releases resources used by this handler, particularly the {@code textConversionTarget} buffer.
+ */
@Override
public void close() {
textConversionTarget.releaseLast();
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/MessageCountingMessageConsumer.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/MessageCountingMessageConsumer.java
index d3754e1d7f..9c3c8fd9e1 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/MessageCountingMessageConsumer.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/MessageCountingMessageConsumer.java
@@ -21,8 +21,10 @@
import net.openhft.chronicle.queue.reader.MessageConsumer;
/**
- * A message consumer that counts the messages passed to a wrapped
- * consumer that made it to the sink
+ * {@code MessageCountingMessageConsumer} is a wrapper for a {@link MessageConsumer} that counts
+ * how many messages have been passed to the consumer and checks whether a defined match limit has been reached.
+ *
+ * This class is useful for scenarios where processing should stop after a certain number of messages have been consumed.
*/
public final class MessageCountingMessageConsumer implements MessageConsumer {
private final long matchLimit;
@@ -30,25 +32,38 @@ public final class MessageCountingMessageConsumer implements MessageConsumer {
private long matches = 0;
/**
- * Constructor
+ * Constructs a {@code MessageCountingMessageConsumer} with the specified match limit and wrapped consumer.
*
- * @param matchLimit The limit used to determine {@link #matchLimitReached()}
- * @param wrappedConsumer The downstream consumer to pass messages to
+ * @param matchLimit The maximum number of messages to consume before stopping. A value of 0 means no limit.
+ * @param wrappedConsumer The downstream consumer that processes the messages
*/
public MessageCountingMessageConsumer(long matchLimit, MessageConsumer wrappedConsumer) {
this.matchLimit = matchLimit;
this.wrappedConsumer = wrappedConsumer;
}
+ /**
+ * Consumes a message by passing it to the wrapped consumer. If the wrapped consumer processes the message,
+ * the match counter is incremented.
+ *
+ * @param index The index of the message
+ * @param message The message content
+ * @return {@code true} if the message was consumed, {@code false} otherwise
+ */
@Override
public boolean consume(long index, String message) {
final boolean consume = wrappedConsumer.consume(index, message);
if (consume) {
- matches++;
+ matches++; // Increment match count if the message was consumed
}
return consume;
}
+ /**
+ * Checks if the match limit has been reached.
+ *
+ * @return {@code true} if the number of consumed messages equals or exceeds the match limit, {@code false} otherwise
+ */
public boolean matchLimitReached() {
return matchLimit > 0 && matches >= matchLimit;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/PatternFilterMessageConsumer.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/PatternFilterMessageConsumer.java
index 5a6c150ef6..4c734ba859 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/PatternFilterMessageConsumer.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/PatternFilterMessageConsumer.java
@@ -24,8 +24,12 @@
import java.util.regex.Pattern;
/**
- * A MessageConsumer that only passes messages through that do or
- * do not match a list of patterns
+ * {@code PatternFilterMessageConsumer} is a {@link MessageConsumer} that filters messages based on a list of patterns.
+ *
+ * It either passes messages that match all the patterns (or none, depending on configuration) to the next consumer
+ * in the chain, or filters them out if the pattern conditions are not met.
+ *
+ * This class can be used for inclusion or exclusion filtering based on regular expressions.
*/
public final class PatternFilterMessageConsumer implements MessageConsumer {
@@ -34,11 +38,12 @@ public final class PatternFilterMessageConsumer implements MessageConsumer {
private final MessageConsumer nextMessageConsumer;
/**
- * Constructor
+ * Constructs a {@code PatternFilterMessageConsumer} with the specified patterns, matching condition,
+ * and next consumer.
*
* @param patterns The list of patterns to match against
- * @param shouldBePresent true if we require all the patterns to match, false if we require none of the patterns to match
- * @param nextMessageConsumer The next message consumer in line, messages that pass the filter will be passed to it
+ * @param shouldBePresent If {@code true}, all patterns must match; if {@code false}, none of the patterns should match
+ * @param nextMessageConsumer The next message consumer in the chain that receives messages passing the filter
*/
public PatternFilterMessageConsumer(List patterns, boolean shouldBePresent, MessageConsumer nextMessageConsumer) {
this.patterns = patterns;
@@ -46,13 +51,23 @@ public PatternFilterMessageConsumer(List patterns, boolean shouldBePres
this.nextMessageConsumer = nextMessageConsumer;
}
+ /**
+ * Consumes a message by checking it against the list of patterns. If the message matches (or doesn't match,
+ * depending on {@code shouldBePresent}), it is passed to the next consumer.
+ *
+ * @param index The index of the message
+ * @param message The message content
+ * @return {@code true} if the message was consumed by the next consumer, {@code false} otherwise
+ */
@Override
public boolean consume(long index, String message) {
for (Pattern pattern : patterns) {
+ // Check if the message matches the pattern, based on the shouldBePresent flag
if (shouldBePresent != pattern.matcher(message).find()) {
return false;
}
}
+ // Pass the message to the next consumer if all patterns matched (or didn't, depending on the flag)
return nextMessageConsumer.consume(index, message);
}
}
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/AbstractTailerPollingQueueEntryReader.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/AbstractTailerPollingQueueEntryReader.java
index 8c514cc468..695220fddb 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/AbstractTailerPollingQueueEntryReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/AbstractTailerPollingQueueEntryReader.java
@@ -24,16 +24,37 @@
import java.util.function.Function;
+/**
+ * {@code AbstractTailerPollingQueueEntryReader} is an abstract base class for implementing
+ * queue entry readers that poll entries from a {@link ExcerptTailer}.
+ *
+ * It provides a template method pattern where the actual reading logic is handled by
+ * subclasses via the {@link #doRead(DocumentContext)} method.
+ * The polling method is determined by a function provided during construction.
+ */
public abstract class AbstractTailerPollingQueueEntryReader implements QueueEntryReader {
private final ExcerptTailer tailer;
private final Function pollMethod;
+ /**
+ * Constructs an {@code AbstractTailerPollingQueueEntryReader} with the given tailer and polling method.
+ *
+ * @param tailer The {@link ExcerptTailer} to read entries from
+ * @param pollMethod A function that specifies how to poll the {@link ExcerptTailer} for entries
+ */
protected AbstractTailerPollingQueueEntryReader(ExcerptTailer tailer, Function pollMethod) {
this.tailer = tailer;
this.pollMethod = pollMethod;
}
+ /**
+ * Reads a single entry from the tailer using the specified polling method.
+ *
+ * If an entry is present, the {@link #doRead(DocumentContext)} method is called to process the entry.
+ *
+ * @return {@code true} if an entry was read, {@code false} if no entry was available
+ */
@Override
public final boolean read() {
try (DocumentContext dc = pollMethod.apply(tailer)) {
@@ -45,5 +66,10 @@ public final boolean read() {
}
}
+ /**
+ * Subclasses must implement this method to define how a document context should be processed.
+ *
+ * @param documentContext The {@link DocumentContext} to process
+ */
protected abstract void doRead(DocumentContext documentContext);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/CustomPluginQueueEntryReader.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/CustomPluginQueueEntryReader.java
index ebe269dd12..4e75148b5b 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/CustomPluginQueueEntryReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/CustomPluginQueueEntryReader.java
@@ -25,18 +25,42 @@
import java.util.function.Function;
+/**
+ * {@code CustomPluginQueueEntryReader} is a specialized queue entry reader that integrates
+ * with a custom {@link ChronicleReaderPlugin} for processing queue entries.
+ *
+ * It uses a {@link MessageConsumer} to handle the processed messages and delegates
+ * entry reading to the custom plugin. This is useful when custom behavior is needed
+ * for processing each entry in the queue.
+ */
public final class CustomPluginQueueEntryReader extends AbstractTailerPollingQueueEntryReader {
private final ChronicleReaderPlugin plugin;
private final MessageConsumer consumer;
- public CustomPluginQueueEntryReader(ExcerptTailer tailer, Function pollMethod, ChronicleReaderPlugin plugin,
- MessageConsumer consumer) {
+ /**
+ * Constructs a {@code CustomPluginQueueEntryReader} with the specified {@link ExcerptTailer},
+ * polling method, plugin, and message consumer.
+ *
+ * @param tailer The {@link ExcerptTailer} used to read entries from the queue
+ * @param pollMethod The function that determines how to poll for entries from the tailer
+ * @param plugin The custom plugin that processes each queue entry
+ * @param consumer The message consumer that handles the processed messages
+ */
+ public CustomPluginQueueEntryReader(ExcerptTailer tailer, Function pollMethod,
+ ChronicleReaderPlugin plugin, MessageConsumer consumer) {
super(tailer, pollMethod);
this.plugin = plugin;
this.consumer = consumer;
}
+ /**
+ * Reads a document from the queue and processes it using the custom plugin. The plugin
+ * is responsible for handling the document content, and it passes the result to the message
+ * consumer if applicable.
+ *
+ * @param documentContext The context of the document being read
+ */
@Override
protected void doRead(DocumentContext documentContext) {
plugin.onReadDocument(documentContext, value -> consumer.consume(documentContext.index(), value));
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/MethodReaderQueueEntryReader.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/MethodReaderQueueEntryReader.java
index 7a455a397f..afab01f2b0 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/MethodReaderQueueEntryReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/MethodReaderQueueEntryReader.java
@@ -28,6 +28,12 @@
import java.nio.ByteBuffer;
+/**
+ * {@code MethodReaderQueueEntryReader} is a queue entry reader that processes method calls from a queue using a {@link MethodReader}.
+ *
+ * It reads and decodes method calls from a queue using a {@link WireType}, and forwards them to a {@link MessageConsumer}.
+ * This class supports optional message history logging.
+ */
public final class MethodReaderQueueEntryReader implements QueueEntryReader {
private final ExcerptTailer tailer;
@@ -35,7 +41,18 @@ public final class MethodReaderQueueEntryReader implements QueueEntryReader {
private final MethodReader methodReader;
private final Bytes bytes;
- public MethodReaderQueueEntryReader(ExcerptTailer tailer, MessageConsumer messageConsumer, WireType wireType, Class> methodReaderInterface, boolean showMessageHistory) {
+ /**
+ * Constructs a {@code MethodReaderQueueEntryReader} with the provided tailer, message consumer, wire type, and method reader interface.
+ * Optionally logs message history if specified.
+ *
+ * @param tailer The {@link ExcerptTailer} used to read entries from the queue
+ * @param messageConsumer The {@link MessageConsumer} that handles the consumed messages
+ * @param wireType The {@link WireType} used to serialize/deserialize the method calls
+ * @param methodReaderInterface The interface used to define the methods to be read from the queue
+ * @param showMessageHistory Whether to include message history in the output
+ */
+ public MethodReaderQueueEntryReader(ExcerptTailer tailer, MessageConsumer messageConsumer, WireType wireType,
+ Class> methodReaderInterface, boolean showMessageHistory) {
this.tailer = tailer;
this.messageConsumer = messageConsumer;
bytes = Bytes.elasticHeapByteBuffer(256);
@@ -54,6 +71,13 @@ public MethodReaderQueueEntryReader(ExcerptTailer tailer, MessageConsumer messag
methodReader = tailer.methodReader(mwb.build());
}
+ /**
+ * Reads and processes one method call from the queue.
+ *
+ * If a method call is successfully read, it is passed to the {@link MessageConsumer} along with the last read index.
+ *
+ * @return {@code true} if a method call was read and processed, {@code false} otherwise
+ */
@Override
public boolean read() {
if (!methodReader.readOne()) {
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/VanillaQueueEntryReader.java b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/VanillaQueueEntryReader.java
index b79e86ee1a..5c48b2a36a 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/VanillaQueueEntryReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/reader/queueentryreaders/VanillaQueueEntryReader.java
@@ -27,6 +27,13 @@
import java.util.function.Function;
+/**
+ * {@code VanillaQueueEntryReader} is a basic implementation of the {@link QueueEntryReader} interface,
+ * responsible for reading entries from a Chronicle queue using a tailer.
+ *
+ * It converts the read entries using a provided {@link QueueEntryHandler} and forwards the converted
+ * message to a {@link MessageConsumer}.
+ */
public final class VanillaQueueEntryReader implements QueueEntryReader {
private final ExcerptTailer tailer;
@@ -34,6 +41,14 @@ public final class VanillaQueueEntryReader implements QueueEntryReader {
private final QueueEntryHandler messageConverter;
private final MessageConsumer messageConsumer;
+ /**
+ * Constructs a {@code VanillaQueueEntryReader} with the given tailer, polling method, message converter, and message consumer.
+ *
+ * @param tailer The {@link ExcerptTailer} used to read from the queue
+ * @param pollMethod A function that polls the {@link ExcerptTailer} for entries
+ * @param messageConverter The {@link QueueEntryHandler} that converts the wire format into a message
+ * @param messageConsumer The {@link MessageConsumer} that consumes the processed message
+ */
public VanillaQueueEntryReader(@NotNull ExcerptTailer tailer, @NotNull Function pollMethod,
@NotNull QueueEntryHandler messageConverter, @NotNull MessageConsumer messageConsumer) {
this.tailer = tailer;
@@ -42,6 +57,13 @@ public VanillaQueueEntryReader(@NotNull ExcerptTailer tailer, @NotNull Function<
this.messageConsumer = messageConsumer;
}
+ /**
+ * Reads a single entry from the queue using the specified polling method and processes it.
+ *
+ * The entry is converted using the {@link QueueEntryHandler} and passed to the {@link MessageConsumer}.
+ *
+ * @return {@code true} if an entry was successfully read and processed, {@code false} if no entry was present
+ */
@Override
public boolean read() {
try (DocumentContext dc = pollMethod.apply(tailer)) {
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/util/InternalFileUtil.java b/src/main/java/net/openhft/chronicle/queue/internal/util/InternalFileUtil.java
index 9923da3895..87861a2db6 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/util/InternalFileUtil.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/util/InternalFileUtil.java
@@ -37,7 +37,10 @@
import static java.util.stream.Collectors.toList;
/**
- * Utility methods for handling Files in connection with ChronicleQueue.
+ * InternalFileUtil provides utility methods for handling files related to Chronicle Queue operations.
+ * It offers functionality for determining which files are safe to remove, checking file states, and fetching open files on the system.
+ *
+ * These methods are used internally for managing file system interactions that are specific to queue operations, especially on Unix-like systems.
*
* @author Per Minborg
* @since 5.17.34
@@ -189,6 +192,11 @@ private static FileState stateWindows(@NotNull File file) {
return FileState.UNDETERMINED;
}
+ /**
+ * Verifies if the current OS supports retrieving open files via {@link #getAllOpenFiles()}.
+ *
+ * @throws UnsupportedOperationException if the OS does not support file querying
+ */
private static void assertOsSupported() {
if (!getAllOpenFilesIsSupportedOnOS()) {
throw new UnsupportedOperationException("This operation is not supported on your operating system");
@@ -218,6 +226,9 @@ public static Map getAllOpenFiles() throws IOException {
return visitor.openFiles;
}
+ /**
+ * Helper class to walk through the "/proc" directory and collect information about open files on Unix-like systems.
+ */
private static class ProcFdWalker extends SimpleFileVisitor {
private final static int PID_PATH_INDEX = 1; // where is the pid for process holding file open represented in path?
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriter.java b/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriter.java
index e4e4f25a1e..7c87707886 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriter.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriter.java
@@ -29,12 +29,24 @@
import java.nio.file.Path;
import java.util.List;
+/**
+ * The {@code ChronicleWriter} class is responsible for writing objects to a Chronicle Queue.
+ *
+ * It reads data from a list of files and writes the contents to the queue, optionally using a method writer
+ * if an interface class is provided.
+ *
+ */
public class ChronicleWriter {
private Path basePath;
private String methodName;
private List files;
private Class> writeTo;
+ /**
+ * Executes the process of reading from files and writing their contents to the Chronicle Queue.
+ *
+ * @throws IOException if an error occurs during file reading or queue writing
+ */
public void execute() throws IOException {
try (final ChronicleQueue queue = ChronicleQueue.singleBuilder(this.basePath).build();
final ExcerptAppender appender = queue.createAppender()) {
@@ -52,9 +64,10 @@ public void execute() throws IOException {
}
/**
- * Chronicle queue base path
- * @param path path of queue to write to
- * @return this
+ * Sets the base path of the Chronicle Queue to write to.
+ *
+ * @param path The path of the Chronicle Queue
+ * @return This {@code ChronicleWriter} instance for method chaining
*/
public ChronicleWriter withBasePath(final Path path) {
this.basePath = path;
@@ -62,9 +75,13 @@ public ChronicleWriter withBasePath(final Path path) {
}
/**
- * Interface class to use to write via
- * @param interfaceName interface
- * @return this
+ * Sets the interface class to use for writing through method calls.
+ *
+ * This method allows writing through a method writer by specifying the name of an interface class.
+ *
+ *
+ * @param interfaceName The fully qualified name of the interface class
+ * @return This {@code ChronicleWriter} instance for method chaining
*/
public ChronicleWriter asMethodWriter(String interfaceName) {
try {
@@ -76,9 +93,10 @@ public ChronicleWriter asMethodWriter(String interfaceName) {
}
/**
- * Specify method name to write each message out as
- * @param methodName method name
- * @return this
+ * Sets the method name to use when writing each message.
+ *
+ * @param methodName The method name
+ * @return This {@code ChronicleWriter} instance for method chaining
*/
public ChronicleWriter withMethodName(String methodName) {
this.methodName = methodName;
@@ -86,9 +104,10 @@ public ChronicleWriter withMethodName(String methodName) {
}
/**
- * List of files to read and, for each, write out a message preceded by {@link #methodName}
- * @param files files
- * @return this
+ * Sets the list of files to read from and write as messages to the queue.
+ *
+ * @param files The list of file paths
+ * @return This {@code ChronicleWriter} instance for method chaining
*/
public ChronicleWriter withFiles(List files) {
this.files = files;
diff --git a/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriterMain.java b/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriterMain.java
index 0709621f50..5a4ca3f6b4 100644
--- a/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriterMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/internal/writer/ChronicleWriterMain.java
@@ -26,8 +26,20 @@
import static net.openhft.chronicle.queue.ChronicleReaderMain.addOption;
+/**
+ * {@code ChronicleWriterMain} is the main class responsible for configuring and running the {@link ChronicleWriter}
+ * from the command line. It processes command-line arguments to determine how data should be written to the Chronicle Queue.
+ */
public class ChronicleWriterMain {
+ private static final int HELP_OUTPUT_LINE_WIDTH = 180;
+
+ /**
+ * Runs the ChronicleWriter based on the provided command-line arguments.
+ *
+ * @param args Command-line arguments
+ * @throws Exception If an error occurs during the writing process
+ */
public void run(@NotNull String[] args) throws Exception {
final Options options = options();
final CommandLine commandLine = parseCommandLine(args, options);
@@ -39,6 +51,14 @@ public void run(@NotNull String[] args) throws Exception {
writer.execute();
}
+ /**
+ * Parses the command-line arguments using Apache Commons CLI.
+ * If there are issues with parsing or required arguments are missing, it prints help and exits the program.
+ *
+ * @param args Command-line arguments
+ * @param options The defined options for command-line parsing
+ * @return The parsed {@link CommandLine} object
+ */
private CommandLine parseCommandLine(final @NotNull String[] args, final Options options) {
final CommandLineParser parser = new DefaultParser();
CommandLine commandLine = null;
@@ -59,11 +79,18 @@ private CommandLine parseCommandLine(final @NotNull String[] args, final Options
return commandLine;
}
+ /**
+ * Prints the help message and exits the application.
+ *
+ * @param options Command-line options
+ * @param status Exit status code
+ * @param message Optional message to display before the help
+ */
private void printHelpAndExit(final Options options, int status, String message) {
final PrintWriter writer = new PrintWriter(System.out);
new HelpFormatter().printHelp(
writer,
- 180,
+ HELP_OUTPUT_LINE_WIDTH,
this.getClass().getSimpleName() + " files..",
message,
options,
@@ -76,6 +103,12 @@ private void printHelpAndExit(final Options options, int status, String message)
System.exit(status);
}
+ /**
+ * Configures the {@link ChronicleWriter} based on the parsed command-line options.
+ *
+ * @param writer The {@link ChronicleWriter} instance to configure
+ * @param commandLine The parsed command-line options
+ */
private void configure(final ChronicleWriter writer, final CommandLine commandLine) {
writer.withBasePath(Paths.get(commandLine.getOptionValue('d')));
writer.withMethodName(commandLine.getOptionValue('m'));
@@ -88,6 +121,11 @@ private void configure(final ChronicleWriter writer, final CommandLine commandLi
writer.withFiles(commandLine.getArgList());
}
+ /**
+ * Defines the available command-line options for configuring the {@link ChronicleWriter}.
+ *
+ * @return A configured {@link Options} object with all available options
+ */
@NotNull
private Options options() {
final Options options = new Options();
diff --git a/src/main/java/net/openhft/chronicle/queue/main/BenchmarkMain.java b/src/main/java/net/openhft/chronicle/queue/main/BenchmarkMain.java
index e237f92c11..ad0b7dfc65 100644
--- a/src/main/java/net/openhft/chronicle/queue/main/BenchmarkMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/main/BenchmarkMain.java
@@ -21,14 +21,24 @@
import net.openhft.chronicle.queue.internal.main.InternalBenchmarkMain;
/**
- * This class is using the following System Properties:
+ * BenchmarkMain is an entry point for running benchmarking tools for Chronicle Queue.
*
- * static int throughput = Integer.getInteger("throughput", 250); // MB/s
- * static int runtime = Integer.getInteger("runtime", 300); // seconds
- * static String basePath = System.getProperty("path", OS.TMP);
+ * This class makes use of several system properties for configuration:
+ *
+ * - throughput: Specifies the throughput in MB/s (default: 250)
+ * - runtime: Specifies the runtime duration in seconds (default: 300)
+ * - path: Specifies the base path for the benchmark (default: OS temporary directory)
+ *
+ * The system properties can be set using JVM arguments.
*/
public final class BenchmarkMain {
+ /**
+ * The main method that triggers the benchmarking process.
+ * Delegates the execution to {@link InternalBenchmarkMain#main(String[])}.
+ *
+ * @param args Command-line arguments
+ */
public static void main(String[] args) {
InternalBenchmarkMain.main(args);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/main/DumpMain.java b/src/main/java/net/openhft/chronicle/queue/main/DumpMain.java
index 670d362968..57d13dac3d 100644
--- a/src/main/java/net/openhft/chronicle/queue/main/DumpMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/main/DumpMain.java
@@ -1,6 +1,8 @@
/*
* Copyright 2016-2020 http://chronicle.software
*
+ * 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
@@ -24,24 +26,46 @@
import java.io.PrintStream;
/**
- * Parameters to the methods in this class can be set using any of the
- * following system properties:
- *
- * private static final String FILE = System.getProperty("file");
- * private static final boolean SKIP_TABLE_STORE = Jvm.getBoolean("skipTableStoreDump");
- * private static final boolean UNALIGNED = Jvm.getBoolean("dumpUnaligned");
- * private static final int LENGTH = ", 0".length();
+ * DumpMain is an entry point for dumping the contents of a Chronicle Queue file.
+ *
This class uses several system properties to configure the dumping process:
+ *
+ * - file: Specifies the file to be dumped
+ * - skipTableStoreDump: Set to true to skip dumping the TableStore
+ * - dumpUnaligned: Set to true to dump unaligned data
+ *
+ * These properties can be set using JVM system properties when running the application.
*/
public final class DumpMain {
+ /**
+ * The main method that triggers the dump process.
+ * Delegates the execution to {@link InternalDumpMain#main(String[])}.
+ *
+ * @param args Command-line arguments
+ * @throws FileNotFoundException if the specified file is not found
+ */
public static void main(String[] args) throws FileNotFoundException {
InternalDumpMain.main(args);
}
+ /**
+ * Dumps the contents of a Chronicle Queue file located at the specified path.
+ *
+ * @param path The path to the Chronicle Queue file
+ * @throws FileNotFoundException if the specified file is not found
+ */
public static void dump(@NotNull String path) throws FileNotFoundException {
InternalDumpMain.dump(path);
}
+ /**
+ * Dumps the contents of a Chronicle Queue file to the specified {@link PrintStream}.
+ * This method provides more fine-grained control over the output, including setting an upper limit for the dump.
+ *
+ * @param path The Chronicle Queue file to be dumped
+ * @param out The {@link PrintStream} to which the dump will be written
+ * @param upperLimit The upper limit for the dump, controlling how much of the file is dumped
+ */
public static void dump(@NotNull File path, @NotNull PrintStream out, long upperLimit) {
InternalDumpMain.dump(path, out, upperLimit);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/main/HistoryMain.java b/src/main/java/net/openhft/chronicle/queue/main/HistoryMain.java
index 6753d53627..c180a42c3c 100644
--- a/src/main/java/net/openhft/chronicle/queue/main/HistoryMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/main/HistoryMain.java
@@ -22,10 +22,13 @@
import org.jetbrains.annotations.NotNull;
/**
- * Reads @see MessageHistory from a queue and outputs histograms for
+ * HistoryMain is an entry point for reading {@link net.openhft.chronicle.wire.MessageHistory} from a Chronicle Queue
+ * and outputting histograms related to message latencies.
+ *
+ * The histograms provide insights into:
*
- * - latencies for each component that has processed a message
- * - latencies between each component that has processed a message
+ * - Latencies for each component that has processed a message
+ * - Latencies between each component that has processed a message
*
*
* @author Jerry Shea
@@ -33,6 +36,12 @@
*/
public final class HistoryMain {
+ /**
+ * The main method that triggers the history reading process.
+ * Delegates execution to {@link ChronicleHistoryReaderMain#main(String[])}.
+ *
+ * @param args Command-line arguments
+ */
public static void main(@NotNull String[] args) {
ChronicleHistoryReaderMain.main(args);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/main/PingPongMain.java b/src/main/java/net/openhft/chronicle/queue/main/PingPongMain.java
index ed9f81eec7..9d990a3baf 100644
--- a/src/main/java/net/openhft/chronicle/queue/main/PingPongMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/main/PingPongMain.java
@@ -21,13 +21,23 @@
import net.openhft.chronicle.queue.internal.main.InternalPingPongMain;
/**
- * System Properties:
+ * PingPongMain is an entry point for running a ping-pong style benchmark using Chronicle Queue.
*
- * static int runtime = Integer.getInteger("runtime", 30); // seconds
- * static String basePath = System.getProperty("path", OS.TMP);
+ * This class uses the following system properties for configuration:
+ *
+ * - runtime: Specifies the duration of the benchmark in seconds (default: 30)
+ * - path: Specifies the base path for the benchmark files (default: OS temporary directory)
+ *
+ * These properties can be set as JVM arguments.
*/
public final class PingPongMain {
+ /**
+ * The main method that triggers the ping-pong benchmark.
+ * Delegates execution to {@link InternalPingPongMain#main(String[])}.
+ *
+ * @param args Command-line arguments
+ */
public static void main(String[] args) {
InternalPingPongMain.main(args);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/main/ReaderMain.java b/src/main/java/net/openhft/chronicle/queue/main/ReaderMain.java
index f02553f6d2..fb440c7738 100644
--- a/src/main/java/net/openhft/chronicle/queue/main/ReaderMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/main/ReaderMain.java
@@ -22,11 +22,17 @@
import org.jetbrains.annotations.NotNull;
/**
- * Display records in a queue in a text form.
- *
+ * ReaderMain is an entry point for displaying records from a Chronicle Queue in text format.
+ * This class delegates the reading and display of queue records to {@link ChronicleReaderMain}.
*/
public final class ReaderMain {
+ /**
+ * The main method that triggers the reading and display of queue records.
+ * Delegates execution to {@link ChronicleReaderMain#main(String[])}.
+ *
+ * @param args Command-line arguments
+ */
public static void main(@NotNull String[] args) {
ChronicleReaderMain.main(args);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/main/RemovableRollFileCandidatesMain.java b/src/main/java/net/openhft/chronicle/queue/main/RemovableRollFileCandidatesMain.java
index 98efbcfefa..1a77695346 100644
--- a/src/main/java/net/openhft/chronicle/queue/main/RemovableRollFileCandidatesMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/main/RemovableRollFileCandidatesMain.java
@@ -20,13 +20,17 @@
import net.openhft.chronicle.queue.internal.main.InternalRemovableRollFileCandidatesMain;
+/**
+ * RemovableRollFileCandidatesMain is an entry point for producing a list of removable roll file candidates from a given directory.
+ *
This utility prints the absolute path of each removable file to the standard output, one file per row.
+ */
public final class RemovableRollFileCandidatesMain {
/**
- * Produces a list of removable roll file candidates and prints
- * their absolute path to standard out row-by-row.
+ * The main method that generates and prints the list of removable roll file candidates.
+ * Delegates execution to {@link InternalRemovableRollFileCandidatesMain#main(String[])}.
*
- * @param args the directory. If no directory is given, "." is assumed
+ * @param args The directory path to search for removable roll files. If no directory is provided, the current directory ("." ) is assumed.
*/
public static void main(String[] args) {
InternalRemovableRollFileCandidatesMain.main(args);
diff --git a/src/main/java/net/openhft/chronicle/queue/main/UnlockMain.java b/src/main/java/net/openhft/chronicle/queue/main/UnlockMain.java
index ec2ff1ba9c..35090539ca 100644
--- a/src/main/java/net/openhft/chronicle/queue/main/UnlockMain.java
+++ b/src/main/java/net/openhft/chronicle/queue/main/UnlockMain.java
@@ -19,8 +19,18 @@
import net.openhft.chronicle.queue.internal.main.InternalUnlockMain;
+/**
+ * UnlockMain is an entry point for unlocking resources or files used by a Chronicle Queue.
+ *
This utility handles the unlocking of locked resources, such as files, that may be in use by the queue.
+ */
public final class UnlockMain {
+ /**
+ * The main method that triggers the unlocking process.
+ * Delegates execution to {@link InternalUnlockMain#main(String[])}.
+ *
+ * @param args Command-line arguments for unlocking operations
+ */
public static void main(String[] args) {
InternalUnlockMain.main(args);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/ChronicleHistoryReader.java b/src/main/java/net/openhft/chronicle/queue/reader/ChronicleHistoryReader.java
index 677aa97b1e..7cf55ae1d4 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/ChronicleHistoryReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/ChronicleHistoryReader.java
@@ -39,6 +39,14 @@
import java.util.function.Supplier;
import java.util.stream.Collectors;
+/**
+ * Implementation of the {@link HistoryReader} interface, providing functionality for reading and processing
+ * historical Chronicle Queue data with timing and histogram-based metrics.
+ *
+ * This class allows the user to read messages from a Chronicle Queue and process them with the help of
+ * histograms and timing windows. Various options such as progress reporting, time unit settings, and
+ * histogram management are available for customization.
+ */
public class ChronicleHistoryReader implements HistoryReader, Closeable {
private static final int SUMMARY_OUTPUT_UNSET = -999;
@@ -64,42 +72,84 @@ public class ChronicleHistoryReader implements HistoryReader, Closeable {
ToolsUtil.warnIfResourceTracing();
}
+ /**
+ * Sets the message sink, which is a consumer to handle each processed message.
+ *
+ * @param messageSink The consumer to handle processed messages
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
@Override
public ChronicleHistoryReader withMessageSink(final Consumer messageSink) {
this.messageSink = messageSink;
return this;
}
+ /**
+ * Sets the base path for the Chronicle Queue.
+ *
+ * @param path The path to the Chronicle Queue directory
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
@Override
public ChronicleHistoryReader withBasePath(final Path path) {
this.basePath = path;
return this;
}
+ /**
+ * Enables or disables progress reporting.
+ *
+ * @param p True to enable progress reporting, false otherwise
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
@Override
public ChronicleHistoryReader withProgress(boolean p) {
this.progress = p;
return this;
}
+ /**
+ * Sets the time unit for measurements.
+ *
+ * @param p The {@link TimeUnit} to be used for time-based measurements
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
@Override
public ChronicleHistoryReader withTimeUnit(TimeUnit p) {
this.timeUnit = p;
return this;
}
+ /**
+ * Enables or disables method-specific histograms.
+ *
+ * @param b True to enable histograms by method, false otherwise
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
@Override
public ChronicleHistoryReader withHistosByMethod(boolean b) {
this.histosByMethod = b;
return this;
}
+ /**
+ * Sets the number of messages to ignore at the start.
+ *
+ * @param ignore The number of messages to ignore
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
@Override
public ChronicleHistoryReader withIgnore(long ignore) {
this.ignore = ignore;
return this;
}
+ /**
+ * Sets the measurement window size in the configured time unit.
+ *
+ * @param measurementWindow The size of the measurement window
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
@Override
public ChronicleHistoryReader withMeasurementWindow(long measurementWindow) {
this.measurementWindowNanos = timeUnit.toNanos(measurementWindow);
@@ -124,7 +174,13 @@ public ChronicleHistoryReader withHistoSupplier(Supplier histoSupplie
return this;
}
- // TODO: rename as queue is now cached
+ /**
+ * Creates and returns a new {@link ChronicleQueue} instance if the current tailer is null or closed.
+ * Otherwise, returns the current tailer's queue. This method throws an exception if the base path does not exist.
+ *
+ * @return A new or cached {@link ChronicleQueue} instance
+ * @throws IllegalArgumentException if the base path does not exist or if the start index could not be moved to
+ */
@NotNull
protected ChronicleQueue createQueue() {
if (tailer != null && ! tailer.queue().isClosed()) {
@@ -144,6 +200,9 @@ protected ChronicleQueue createQueue() {
return queue;
}
+ /**
+ * Executes the reading of the Chronicle Queue and outputs data if no measurement window is set.
+ */
@Override
public void execute() {
readChronicle();
@@ -151,6 +210,12 @@ public void execute() {
outputData();
}
+ /**
+ * Reads messages from the Chronicle Queue, processing each one and updating histograms.
+ * Progress is reported if enabled, and histograms are returned after reading.
+ *
+ * @return A map of histograms representing message processing metrics
+ */
@Override
public Map readChronicle() {
createQueue();
@@ -175,11 +240,20 @@ public Map readChronicle() {
return histos;
}
+ /**
+ * Converts a method ID to its corresponding name.
+ *
+ * @param methodId The method ID
+ * @return The method name as a string
+ */
@NotNull
protected String methodIdToName(long methodId) {
return Long.toString(methodId);
}
+ /**
+ * Outputs the data, either as a summary or as percentile-based timings depending on the configuration.
+ */
@Override
public void outputData() {
if (summaryOutputOffset != SUMMARY_OUTPUT_UNSET)
@@ -188,6 +262,9 @@ public void outputData() {
printPercentilesSummary();
}
+ /**
+ * Prints a percentile-based summary of the histograms.
+ */
private void printPercentilesSummary() {
// we should also consider the case where >1 output messages are from 1 incoming
@@ -211,6 +288,9 @@ private void printPercentilesSummary() {
messageSink.accept("worst: " + percentiles(-1));
}
+ /**
+ * Prints a summary of the histograms.
+ */
private void printSummary() {
if (histos.size() > lastHistosSize) {
messageSink.accept("relative_ts," + String.join(",", histos.keySet()));
@@ -224,16 +304,34 @@ private void printSummary() {
collect(Collectors.joining(",")));
}
+ /**
+ * Calculates the value at a specified percentile offset.
+ *
+ * @param percentiles The array of percentile values
+ * @param offset The percentile offset
+ * @return The value at the specified percentile
+ */
private double offset(double[] percentiles, int offset) {
return offset >= 0 ? percentiles[offset] : percentiles[percentiles.length + offset];
}
+ /**
+ * Returns a formatted string representing the count of messages processed by each histogram.
+ *
+ * @return A string representing the message counts
+ */
private String count() {
final StringBuilder sb = new StringBuilder(" ");
histos.forEach((id, histogram) -> sb.append(String.format("%12d ", histogram.totalCount())));
return sb.toString();
}
+ /**
+ * Returns a formatted string representing the percentile values for the histograms.
+ *
+ * @param index The index of the percentile to retrieve
+ * @return A string representing the percentile values
+ */
private String percentiles(final int index) {
final StringBuilder sb = new StringBuilder(" ");
histos.forEach((id, histogram) -> {
@@ -250,6 +348,11 @@ private String percentiles(final int index) {
return sb.toString();
}
+ /**
+ * Creates a {@link WireParselet} for processing Chronicle Queue messages.
+ *
+ * @return A new {@link WireParselet} instance
+ */
protected WireParselet parselet() {
return (methodName, v) -> {
v.skipValue();
@@ -276,6 +379,12 @@ protected WireParselet parselet() {
};
}
+ /**
+ * Processes a message and updates the histograms based on timing data.
+ *
+ * @param methodName The name of the method being processed
+ * @param history The {@link MessageHistory} for the message
+ */
protected void processMessage(CharSequence methodName, MessageHistory history) {
CharSequence extraHistoId = histosByMethod ? (SEPARATOR + methodName) : "";
long lastTime = 0;
@@ -308,20 +417,34 @@ protected void processMessage(CharSequence methodName, MessageHistory history) {
}
}
+ /**
+ * Handles when a measurement window has passed, outputting the data and resetting histograms.
+ */
protected void windowPassed() {
outputData();
resetHistos();
}
+ /**
+ * Resets all histograms to their initial state.
+ */
private void resetHistos() {
histos.values().forEach(Histogram::reset);
}
+ /**
+ * Creates a new {@link Histogram} instance using the configured supplier.
+ *
+ * @return A new {@link Histogram} instance
+ */
@NotNull
protected Histogram histogram() {
return histoSupplier.get();
}
+ /**
+ * Closes the tailer and the associated queue.
+ */
@Override
public void close() {
if (tailer != null)
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReader.java b/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReader.java
index a811a3c167..ddb1e05865 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReader.java
@@ -51,6 +51,14 @@
import static net.openhft.chronicle.queue.TailerDirection.FORWARD;
import static net.openhft.chronicle.queue.impl.StoreFileListener.NO_OP;
+/**
+ * Implementation of the {@link Reader} interface, providing functionality for reading messages from a
+ * Chronicle Queue with various configurations such as filtering, tailing, and binary search support.
+ *
+ * The {@link ChronicleReader} class is designed to handle different types of queue reading patterns,
+ * including tailing (continuous reading of new entries), and allows users to specify inclusion/exclusion
+ * filters, start indices, and message processing through customizable plugins.
+ */
public class ChronicleReader implements Reader {
private static final long UNSET_VALUE = Long.MIN_VALUE;
@@ -87,6 +95,10 @@ private static boolean isSet(final long configValue) {
return configValue != UNSET_VALUE;
}
+ /**
+ * Executes the reader logic by creating the necessary queue, tailers, and entry readers,
+ * and processing messages until the stop condition is met.
+ */
public void execute() {
configureContentBasedLimiter();
validateArgs();
@@ -131,9 +143,11 @@ public void execute() {
}
/**
- * Ignore {@link DateTimeParseException} error - due to a race condition between the reader creating a Queue
- * (with default roll-cycle due to no files on disk) and the writer appending to the Queue with a non-default
- * roll-cycle.
+ * Handles runtime exceptions, particularly {@link DateTimeParseException} caused by race conditions
+ * between different roll cycles. It retries the operation if this specific exception is encountered.
+ *
+ * @param e The caught runtime exception
+ * @return {@code true} if the operation should be retried, {@code false} otherwise
*/
private static boolean handleRuntimeException(RuntimeException e) {
if (e.getCause() instanceof DateTimeParseException) {
@@ -143,6 +157,14 @@ private static boolean handleRuntimeException(RuntimeException e) {
}
}
+ /**
+ * Reads from the queue while the thread is not interrupted, pausing or halting as needed based on
+ * the tail input source and content-based limits.
+ *
+ * @param tailer The tailer used for reading messages
+ * @param messageConsumer The consumer for processed messages
+ * @param queueEntryReader The entry reader for the queue
+ */
private void readWhileNotInterrupted(ExcerptTailer tailer, MessageCountingMessageConsumer messageConsumer, QueueEntryReader queueEntryReader) {
while (!Thread.currentThread().isInterrupted()) {
if (shouldHaltReadingDueToContentBasedLimit(tailer)) {
@@ -164,13 +186,18 @@ private void readWhileNotInterrupted(ExcerptTailer tailer, MessageCountingMessag
}
}
+ /**
+ * Validates the arguments for the {@link ChronicleReader}.
+ *
Throws an {@link IllegalArgumentException} if a named tailer is used with a read-only queue.
+ */
private void validateArgs() {
if (tailerId != null && readOnly)
throw new IllegalArgumentException("Named tailers only work on writable queues");
}
/**
- * Configure the content-based limiter if it was specified
+ * Configures the content-based limiter if specified.
+ *
This method ensures that the content-based limiter is properly initialized before queue processing.
*/
private void configureContentBasedLimiter() {
if (contentBasedLimiter != null) {
@@ -179,10 +206,10 @@ private void configureContentBasedLimiter() {
}
/**
- * Check if the content-based limit has been reached
+ * Checks whether reading should be halted due to the content-based limit being reached.
*
- * @param tailer The Tailer we're using to read the queue
- * @return true if we should halt reading, false otherwise
+ * @param tailer The {@link ExcerptTailer} used for reading the queue
+ * @return {@code true} if reading should be halted, {@code false} otherwise
*/
private boolean shouldHaltReadingDueToContentBasedLimit(ExcerptTailer tailer) {
if (contentBasedLimiter == null) {
@@ -199,6 +226,14 @@ private boolean shouldHaltReadingDueToContentBasedLimit(ExcerptTailer tailer) {
}
}
+ /**
+ * Creates a {@link QueueEntryReader} for processing entries in the queue.
+ *
This method chooses between a vanilla reader, a plugin-based reader, or a method reader based on the configuration.
+ *
+ * @param tailer The {@link ExcerptTailer} for reading queue entries
+ * @param messageConsumer The {@link MessageConsumer} for processing queue entries
+ * @return A configured {@link QueueEntryReader} instance
+ */
private QueueEntryReader createQueueEntryReader(ExcerptTailer tailer, MessageConsumer messageConsumer) {
if (methodReaderInterface == null) {
if (customPlugin == null) {
@@ -212,7 +247,7 @@ private QueueEntryReader createQueueEntryReader(ExcerptTailer tailer, MessageCon
}
/**
- * Create the chain of message consumers according to config
+ * Creates a chain of message consumers according to the configured inclusion and exclusion patterns.
*
* @return The head of the chain of message consumers
*/
@@ -227,6 +262,13 @@ private MessageConsumer createMessageConsumers() {
return tail;
}
+ /**
+ * Writes the index and text of a queue entry to the message sink.
+ *
+ * @param index The index of the entry being processed
+ * @param text The content of the entry being processed
+ * @return {@code true} after writing to the sink
+ */
private boolean writeToSink(long index, String text) {
if (displayIndex)
messageSink.accept("0x" + Long.toHexString(index) + ": ");
@@ -235,64 +277,135 @@ private boolean writeToSink(long index, String text) {
return true;
}
+ /**
+ * Sets whether the {@link ChronicleReader} operates in read-only mode.
+ *
+ * @param readOnly {@code true} to enable read-only mode, {@code false} otherwise
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
+ /**
+ * Sets the maximum number of matching records to read.
+ *
+ * @param matchLimit The maximum number of records to match
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withMatchLimit(long matchLimit) {
this.matchLimit = matchLimit;
return this;
}
+ /**
+ * Sets the consumer for handling messages processed by the {@link ChronicleReader}.
+ *
+ * @param messageSink The consumer for processing message strings
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withMessageSink(final @NotNull Consumer messageSink) {
this.messageSink = messageSink;
return this;
}
+ /**
+ * Gets the current message sink for handling processed messages.
+ *
+ * @return The current message sink
+ */
public Consumer messageSink() {
return messageSink;
}
+ /**
+ * Sets the base path for the {@link ChronicleQueue} that the reader will operate on.
+ *
+ * @param path The base directory path for the Chronicle Queue
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withBasePath(final @NotNull Path path) {
this.basePath = path;
return this;
}
+ /**
+ * Adds an inclusion regex for filtering messages.
+ *
+ * @param regex The regex pattern for inclusion
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withInclusionRegex(final @NotNull String regex) {
this.inclusionRegex.add(Pattern.compile(regex));
return this;
}
+ /**
+ * Adds an exclusion regex for filtering messages.
+ *
+ * @param regex The regex pattern for exclusion
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withExclusionRegex(final @NotNull String regex) {
this.exclusionRegex.add(Pattern.compile(regex));
return this;
}
+ /**
+ * Sets a custom plugin to handle queue entries.
+ *
+ * @param customPlugin The {@link ChronicleReaderPlugin} to use
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withCustomPlugin(final @NotNull ChronicleReaderPlugin customPlugin) {
this.customPlugin = customPlugin;
return this;
}
+ /**
+ * Sets the start index for reading the queue.
+ *
+ * @param index The start index to begin reading from
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withStartIndex(final long index) {
this.startIndex = index;
return this;
}
+ /**
+ * Enables tailing mode, allowing the reader to continuously read new entries as they are added to the queue.
+ *
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader tail() {
this.tailInputSource = true;
return this;
}
+ /**
+ * Sets the maximum number of history records to read from the queue.
+ *
+ * @param maxHistoryRecords The maximum number of history records to process
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader historyRecords(final long maxHistoryRecords) {
this.maxHistoryRecords = maxHistoryRecords;
return this;
}
+ /**
+ * Sets the method reader interface for reading queue entries.
+ * If the provided interface name is empty, it uses a dummy handler; otherwise, it loads the class specified by the methodReaderInterface parameter.
+ *
+ * @param methodReaderInterface The fully qualified class name of the method reader interface
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader asMethodReader(@NotNull String methodReaderInterface) {
- if (methodReaderInterface.isEmpty())
+ if (methodReaderInterface.isEmpty()) {
entryHandlerFactory = () -> new InternalDummyMethodReaderQueueEntryHandler(wireType);
- else try {
+ } else try {
this.methodReaderInterface = Class.forName(methodReaderInterface);
} catch (ClassNotFoundException e) {
throw Jvm.rethrow(e);
@@ -300,12 +413,25 @@ public ChronicleReader asMethodReader(@NotNull String methodReaderInterface) {
return this;
}
+ /**
+ * Enables or disables showing message history in the reader.
+ *
+ * @param showMessageHistory {@code true} to show message history, {@code false} otherwise
+ * @return The current instance of {@link ChronicleReader}
+ */
@Override
public ChronicleReader showMessageHistory(boolean showMessageHistory) {
this.showMessageHistory = showMessageHistory;
return this;
}
+ /**
+ * Configures a binary search comparator for the reader.
+ *
This method dynamically loads a binary search class and allows it to configure itself by passing the current {@link ChronicleReader} instance.
+ *
+ * @param binarySearchClass The fully qualified class name of the binary search comparator
+ * @return The current instance of {@link ChronicleReader}
+ */
@Override
public ChronicleReader withBinarySearch(@NotNull String binarySearchClass) {
try {
@@ -319,70 +445,144 @@ public ChronicleReader withBinarySearch(@NotNull String binarySearchClass) {
return this;
}
+ /**
+ * Sets a content-based limiter for the reader to control how many entries can be read based on their content.
+ *
+ * @param contentBasedLimiter The {@link ContentBasedLimiter} to be used
+ * @return The current instance of {@link ChronicleReader}
+ */
@Override
public ChronicleReader withContentBasedLimiter(ContentBasedLimiter contentBasedLimiter) {
this.contentBasedLimiter = contentBasedLimiter;
return this;
}
+ /**
+ * Sets an argument to be passed to the reader, typically used for custom plugin configurations.
+ *
+ * @param arg The argument as a string
+ * @return The current instance of {@link ChronicleReader}
+ */
@Override
public ChronicleReader withArg(@NotNull String arg) {
this.arg = arg;
return this;
}
+ /**
+ * Sets an argument for the content-based limiter, allowing further customization of the limiter's behavior.
+ *
+ * @param limiterArg The argument for the limiter
+ * @return The current instance of {@link ChronicleReader}
+ */
@Override
public ChronicleReader withLimiterArg(@NotNull String limiterArg) {
this.limiterArg = limiterArg;
return this;
}
+ /**
+ * Configures the wire type for the reader, determining how entries are serialized/deserialized.
+ *
+ * @param wireType The {@link WireType} to be used
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withWireType(@NotNull WireType wireType) {
this.wireType = wireType;
return this;
}
+ /**
+ * Sets the reader to operate in reverse order, allowing it to read entries from the end of the queue backwards.
+ *
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader inReverseOrder() {
this.tailerDirection = TailerDirection.BACKWARD;
return this;
}
+ /**
+ * Disables displaying the index of each queue entry during reading.
+ *
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader suppressDisplayIndex() {
this.displayIndex = false;
return this;
}
+ /**
+ * Returns the argument passed to the reader.
+ *
+ * @return The current argument as a string
+ */
@Override
public String arg() {
return arg;
}
+ /**
+ * Returns the argument passed to the content-based limiter.
+ *
+ * @return The limiter argument as a string
+ */
@Override
public String limiterArg() {
return limiterArg;
}
+ /**
+ * Returns the class used for the method reader interface.
+ *
+ * @return The method reader interface class
+ */
@Override
public Class> methodReaderInterface() {
return methodReaderInterface;
}
- // visible for testing
+ /**
+ * Sets the polling method used to retrieve documents from the tailer. This is mainly used for testing purposes.
+ *
+ * @param pollMethod The polling function to use
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withDocumentPollMethod(final Function pollMethod) {
this.pollMethod = pollMethod;
return this;
}
+ /**
+ * Sets the ID for the tailer to use when reading from the queue. This ID can be used to read from a specific named tailer.
+ *
+ * @param tailerId The tailer ID
+ * @return The current instance of {@link ChronicleReader}
+ */
public ChronicleReader withTailerId(String tailerId) {
this.tailerId = tailerId;
return this;
}
+ /**
+ * Determines whether the queue has been modified since the last check by comparing the current tail index with the last observed index.
+ *
+ * @param lastObservedTailIndex The index of the last observed tail
+ * @param tailer The {@link ExcerptTailer} used to read from the queue
+ * @return {@code true} if the queue has been modified, {@code false} otherwise
+ */
private boolean queueHasBeenModifiedSinceLastCheck(final long lastObservedTailIndex, ExcerptTailer tailer) {
long currentTailIndex = indexOfEnd(tailer);
return currentTailIndex > lastObservedTailIndex;
}
+ /**
+ * Moves the tailer to the specified position, taking into account whether it is the first iteration or if binary search is being used.
+ *
+ * @param ic The {@link ChronicleQueue} instance
+ * @param tailer The {@link ExcerptTailer} used for reading
+ * @param isFirstIteration Whether this is the first iteration of reading
+ */
private void moveToSpecifiedPosition(final ChronicleQueue ic, final ExcerptTailer tailer, final boolean isFirstIteration) {
if (isFirstIteration) {
@@ -404,6 +604,13 @@ private void moveToSpecifiedPosition(final ChronicleQueue ic, final ExcerptTaile
}
}
+ /**
+ * Moves the {@link ExcerptTailer} to the end of the queue.
+ * If {@code maxHistoryRecords} is set, it moves the tailer to a specific number of entries from the end.
+ * Otherwise, if tailing is enabled, it simply moves the tailer to the end.
+ *
+ * @param tailer The {@link ExcerptTailer} to move
+ */
private void moveTailerToEnd(ExcerptTailer tailer) {
if (isSet(maxHistoryRecords)) {
tailer.toEnd();
@@ -413,6 +620,12 @@ private void moveTailerToEnd(ExcerptTailer tailer) {
}
}
+ /**
+ * Attempts to move the {@link ExcerptTailer} to the specified start index, throwing an exception if the index is out of bounds.
+ *
+ * @param ic The {@link ChronicleQueue} instance
+ * @param tailer The {@link ExcerptTailer} to move
+ */
private void tryMoveToIndex(ChronicleQueue ic, ExcerptTailer tailer) {
if (startIndex < ic.firstIndex()) {
throw new IllegalArgumentException(String.format("startIndex 0x%xd is less than first index 0x%xd",
@@ -434,6 +647,11 @@ private void tryMoveToIndex(ChronicleQueue ic, ExcerptTailer tailer) {
}
}
+ /**
+ * Performs a binary search using the {@link BinarySearchComparator} to find the desired entry, adjusting the tailer based on the search result.
+ *
+ * @param tailer The {@link ExcerptTailer} to move
+ */
private void seekBinarySearch(ExcerptTailer tailer) {
TailerDirection originalDirection = tailer.direction();
tailer.direction(FORWARD);
@@ -450,11 +668,10 @@ private void seekBinarySearch(ExcerptTailer tailer) {
}
/**
- * In the event the matched value is repeated, move to the first instance of it, taking into account traversal
- * direction
+ * Moves to the first matching entry in the queue, adjusting for traversal direction.
*
- * @param tailer The {@link net.openhft.chronicle.queue.ExcerptTailer} to move
- * @param key The value we searched for
+ * @param tailer The {@link ExcerptTailer} to move
+ * @param key The search key
* @param matchingIndex The index of a matching entry
*/
private void scanToFirstMatchingEntry(ExcerptTailer tailer, Wire key, long matchingIndex) {
@@ -471,7 +688,7 @@ private void scanToFirstMatchingEntry(ExcerptTailer tailer, Wire key, long match
else
break;
} catch (NotComparableException e) {
- // continue
+ // Continue if not comparable
}
}
}
@@ -510,6 +727,12 @@ private void scanToFirstEntryFollowingMatch(ExcerptTailer tailer, Wire key, long
}
}
+ /**
+ * Moves the {@link ExcerptTailer} a specific number of entries from the end of the queue.
+ *
+ * @param tailer The {@link ExcerptTailer} to move
+ * @param numberOfEntriesFromEnd The number of entries from the end to move to
+ */
private void moveToIndexNFromTheEnd(ExcerptTailer tailer, long numberOfEntriesFromEnd) {
tailer.direction(TailerDirection.BACKWARD).toEnd();
for (int i = 0; i < numberOfEntriesFromEnd - 1; i++) {
@@ -522,10 +745,22 @@ private void moveToIndexNFromTheEnd(ExcerptTailer tailer, long numberOfEntriesFr
tailer.direction(FORWARD);
}
+ /**
+ * Returns the index of the last entry in the queue.
+ *
+ * @param excerptTailer The {@link ExcerptTailer} used to read the queue
+ * @return The index of the last entry
+ */
private long indexOfEnd(ExcerptTailer excerptTailer) {
return excerptTailer.toEnd().index();
}
+ /**
+ * Creates a {@link ChronicleQueue} based on the configuration, throwing an exception if the base path does not exist.
+ *
+ * @return A configured {@link ChronicleQueue} instance
+ * @throws IllegalArgumentException if the base path does not exist
+ */
@NotNull
private ChronicleQueue createQueue() {
if (!Files.exists(basePath)) {
@@ -538,6 +773,9 @@ private ChronicleQueue createQueue() {
.build();
}
+ /**
+ * Stops the reader, halting any further processing of the queue.
+ */
public void stop() {
running = false;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReaderPlugin.java b/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReaderPlugin.java
index 5cd3c2b0ba..ccea23028e 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReaderPlugin.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/ChronicleReaderPlugin.java
@@ -23,18 +23,29 @@
import java.util.function.Consumer;
/**
- * Handle the document from the queue that is read in {@code ChronicleReader}.
- * Particularly useful when you need more than the text representation e.g.
- * when your queue is written in binary.
+ * Plugin interface for handling documents read from the queue in {@code ChronicleReader}.
+ *
This interface allows for custom handling of the documents, which can be particularly useful when working with non-textual
+ * queues, such as those written in binary format. Implementing this plugin provides a way to process the raw {@link DocumentContext}
+ * from the queue.
*/
public interface ChronicleReaderPlugin {
+
+ /**
+ * Handle the document from the queue that is read in {@code ChronicleReader}.
+ *
Implement this method to define how a document should be processed when read from the queue.
+ * This method provides access to the raw {@link DocumentContext}.
+ *
+ * @param dc The document context representing the queue entry
+ */
void onReadDocument(DocumentContext dc);
/**
- * Consume dc and allow it to be given back to ChronicleReader so it could e.g. apply inclusion filters
+ * Handle the document and optionally pass it back to the {@code ChronicleReader} as a text representation.
+ *
This method allows for additional processing of the document and the ability to convert it to a string form using the
+ * provided {@link Consumer}. This is useful when inclusion filters or other processing steps need to be applied.
*
- * @param dc doc context
- * @param messageConsumer use this to pass back text representation
+ * @param dc The document context representing the queue entry
+ * @param messageConsumer A consumer used to pass the text representation back to the {@code ChronicleReader}
*/
default void onReadDocument(DocumentContext dc, Consumer messageConsumer) {
onReadDocument(dc);
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/ContentBasedLimiter.java b/src/main/java/net/openhft/chronicle/queue/reader/ContentBasedLimiter.java
index 5cbc8a591f..a5e4aeadac 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/ContentBasedLimiter.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/ContentBasedLimiter.java
@@ -21,25 +21,26 @@
import net.openhft.chronicle.wire.DocumentContext;
/**
- * Implement this to signal when to halt reading a queue based on the content
- * of a message.
+ * Interface for signaling when to halt reading from a queue based on the content of a message.
+ * This can be used to limit processing within the {@link ChronicleReader} based on specific conditions found in the messages.
*/
public interface ContentBasedLimiter {
/**
- * Should the ChronicleReader stop processing messages?
+ * Determines whether the {@link ChronicleReader} should stop processing further messages.
+ *
This method examines the content of the next message and decides if reading should halt before processing it.
*
- * @param documentContext The next message to be processed
- * @return true to halt processing before processing the passed message, false otherwise
+ * @param documentContext The document context representing the next message to be processed
+ * @return {@code true} to halt processing, {@code false} to continue processing the message
*/
boolean shouldHaltReading(DocumentContext documentContext);
/**
- * Passed to the limiter before processing begins, can be used to provide parameters to it
+ * Configures the limiter with parameters before the reader begins processing.
*
- * The limiter should make use of {@link Reader#limiterArg()} to convey parameters
+ * This method allows the limiter to be customized using arguments provided via {@link Reader#limiterArg()}.
*
- * @param reader The Reader about to be executed
+ * @param reader The reader that is about to be executed, providing context and parameters for the limiter
*/
void configure(Reader reader);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/HistoryReader.java b/src/main/java/net/openhft/chronicle/queue/reader/HistoryReader.java
index e2d99b1acf..f8c323639b 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/HistoryReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/HistoryReader.java
@@ -28,42 +28,111 @@
import java.util.function.Consumer;
import java.util.function.Supplier;
+/**
+ * Interface representing a history reader, designed to read from a {@link ChronicleQueue}
+ * and collect latency histograms from the queue entries over time.
+ *
This interface provides methods for configuring the reader, managing message sinks,
+ * and accumulating histograms for performance analysis. It supports flexible options such as
+ * setting the base path, start index, measurement windows, and other performance metrics.
+ */
public interface HistoryReader {
+ /**
+ * Sets the message sink to handle output messages processed by the reader.
+ *
+ * @param messageSink The consumer for processing message strings
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withMessageSink(final Consumer messageSink);
+ /**
+ * Sets the base path for the {@link ChronicleQueue} that the history reader will operate on.
+ *
+ * @param path The base directory path for the Chronicle Queue
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withBasePath(final Path path);
+ /**
+ * Enables or disables progress reporting.
+ *
+ * @param p {@code true} to enable progress reporting, {@code false} otherwise
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withProgress(boolean p);
+ /**
+ * Sets the time unit for measurements.
+ *
+ * @param p The {@link TimeUnit} to be used for time-based measurements
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withTimeUnit(TimeUnit p);
+ /**
+ * Enables or disables histograms by method.
+ *
+ * @param b {@code true} to enable histograms by method, {@code false} otherwise
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withHistosByMethod(boolean b);
+ /**
+ * Sets the number of initial messages to ignore at the start.
+ *
+ * @param ignore The number of messages to ignore
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withIgnore(long ignore);
+ /**
+ * Sets the measurement window size in the configured time unit.
+ *
+ * @param measurementWindow The size of the measurement window
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withMeasurementWindow(long measurementWindow);
+ /**
+ * Configures the offset for summary output.
+ *
+ * @param offset The offset for summary output
+ * @return The current instance of {@link HistoryReader}
+ */
HistoryReader withSummaryOutput(int offset);
/**
- * set the index to start at
- * @param startIndex start index
- * @return this
+ * Sets the start index for reading the queue.
+ *
+ * @param startIndex The start index to begin reading from
+ * @return The current instance of {@link HistoryReader}
*/
HistoryReader withStartIndex(long startIndex);
+ /**
+ * Sets the supplier for histograms.
+ *
+ * @param histoSupplier The supplier for providing histograms
+ * @return The current instance of {@link ChronicleHistoryReader}
+ */
ChronicleHistoryReader withHistoSupplier(Supplier histoSupplier);
+ /**
+ * Executes the history reader to process messages from the queue.
+ */
void execute();
/**
- * Read until the end of the queue, accumulating latency histograms.
- * Can be called repeatedly and will start where last finished
- * @return histograms
+ * Reads messages from the queue until the end, accumulating latency histograms.
+ * This method can be called repeatedly and will continue from where the last call finished.
+ *
+ * @return A map of histograms representing message processing metrics
*/
Map readChronicle();
+ /**
+ * Outputs the collected data from the histograms.
+ */
void outputData();
/**
@@ -78,13 +147,13 @@ static HistoryReader create() {
}
/**
- * Creates and returns a new history reader that will use
- * the provided {@code queueSupplier } to provide the queue.
+ * Creates and returns a new history reader that will use the provided queue supplier
+ * to obtain the {@link ChronicleQueue}.
*
- * @return a new history reader that will use
- * the provided {@code queueSupplier } to provide the queue.
+ * @param queueSupplier A supplier providing the {@link ChronicleQueue} to be used
+ * @return A new instance of {@link HistoryReader}
*/
static HistoryReader create(@NotNull final Supplier extends ChronicleQueue> queueSupplier) {
- throw new UnsupportedOperationException("TODO");
+ throw new UnsupportedOperationException("TODO"); // Implementation pending
}
}
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/MessageConsumer.java b/src/main/java/net/openhft/chronicle/queue/reader/MessageConsumer.java
index 370abc98d7..cd0c60dc8e 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/MessageConsumer.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/MessageConsumer.java
@@ -26,11 +26,11 @@
public interface MessageConsumer {
/**
- * Consume the message
+ * Consumes the given message, performing any filtering or transformation as necessary.
*
- * @param index the index of the message
- * @param message the message as a string
- * @return True if the message was sent through to the sink, false if it was filtered
+ * @param index The index of the message within the queue
+ * @param message The message content as a string
+ * @return {@code true} if the message was passed through to the sink, {@code false} if it was filtered out
*/
boolean consume(long index, String message);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryHandler.java b/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryHandler.java
index 8efe5de205..d8947a0d7f 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryHandler.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryHandler.java
@@ -26,11 +26,26 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+/**
+ * Handles the processing of queue entries, converting them to text or other forms for consumption.
+ * Implements the {@link BiConsumer} interface to consume a {@link WireIn} object, which represents the serialized data,
+ * and a {@link Consumer} that processes the resulting string.
+ */
public interface QueueEntryHandler extends BiConsumer>, AutoCloseable {
+ /**
+ * Closes the handler, releasing any resources held.
+ */
@Override
void close();
+ /**
+ * Creates a {@link QueueEntryHandler} that converts messages to text based on the provided {@link WireType}.
+ * This is useful when reading queues written in different formats such as binary, JSON, or text.
+ *
+ * @param wireType The {@link WireType} used to interpret the data
+ * @return A {@link QueueEntryHandler} that converts messages to text
+ */
@NotNull
static QueueEntryHandler messageToText(@NotNull final WireType wireType) {
return new InternalMessageToTextQueueEntryHandler(wireType);
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryReader.java b/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryReader.java
index 3b248a14ce..393c050a2a 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryReader.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/QueueEntryReader.java
@@ -18,12 +18,17 @@
package net.openhft.chronicle.queue.reader;
+/**
+ * Interface for reading and processing entries from a queue.
+ *
Implementations of this interface are responsible for reading the next available entry
+ * from the queue and processing it as necessary.
+ */
public interface QueueEntryReader {
/**
- * Read/process the next entry from the queue
+ * Reads and processes the next entry from the queue.
*
- * @return true if there was an entry to read, false if we are at the end of the queue
+ * @return {@code true} if there was an entry to read and process, {@code false} if the end of the queue has been reached
*/
boolean read();
}
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/Reader.java b/src/main/java/net/openhft/chronicle/queue/reader/Reader.java
index 9aeff62ec7..132a34bd80 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/Reader.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/Reader.java
@@ -26,18 +26,20 @@
/**
* The Reader interface provides methods for reading messages from a Chronicle Queue.
- * It allows for customization of the reading process through various configuration methods, and
- * creates a Reader with the {@link #create()} method
+ *
+ * It allows for extensive customization of the reading process through various configuration methods,
+ * including setting the base path, inclusion/exclusion filters, content-based limiters, and method reader interfaces.
+ * A new Reader can be created using the {@link #create()} method.
*/
public interface Reader {
/**
- * Executes the Reader.
+ * Executes the Reader to begin processing messages from the queue.
*/
void execute();
/**
- * Stops the Reader.
+ * Stops the Reader, halting further processing.
*/
void stop();
@@ -45,151 +47,154 @@ public interface Reader {
* Sets the message sink for this Reader. If not set, messages are output to stdout.
*
* @param messageSink A Consumer function that will handle the messages read by this Reader.
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader withMessageSink(@NotNull Consumer messageSink);
/**
- * Sets the base path for this Reader.
+ * Sets the base path for this Reader, which indicates the location of the Chronicle Queue.
*
- * @param path The base path.
- * @return this
+ * @param path The base path of the Chronicle Queue.
+ * @return The current instance of {@link Reader}
*/
Reader withBasePath(@NotNull Path path);
/**
- * Adds an inclusion regex for this Reader. These are anded together.
+ * Adds an inclusion regex for filtering messages.
+ * Messages that match the inclusion regex will be processed.
*
* @param regex The inclusion regex.
- * @return The Reader instance with the inclusion regex set.
+ * @return The current instance of {@link Reader}
*/
Reader withInclusionRegex(@NotNull String regex);
/**
- * Adds exclusion regex for this Reader. These are anded together.
+ * Adds an exclusion regex for filtering messages.
+ *
Messages that match the exclusion regex will be filtered out.
*
* @param regex The exclusion regex.
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader withExclusionRegex(@NotNull String regex);
/**
- * Sets the custom plugin for this Reader. Allows more flexibility than {@link #withMessageSink(Consumer)}
+ * Sets a custom plugin for this Reader, allowing more flexibility than {@link #withMessageSink(Consumer)}.
*
- * @param customPlugin The custom plugin.
- * @return this
+ * @param customPlugin The custom plugin to use.
+ * @return The current instance of {@link Reader}
*/
Reader withCustomPlugin(@NotNull ChronicleReaderPlugin customPlugin);
/**
- * Sets the start index for this Reader.
+ * Sets the start index for reading messages from the queue.
*
* @param index The start index.
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader withStartIndex(final long index);
/**
- * Sets the content-based limiter for this Reader.
+ * Sets the content-based limiter for this Reader to control the processing of messages based on their content.
*
* @param contentBasedLimiter The content-based limiter.
- * @return this
+ * @return The current instance of {@link Reader}
*/
ChronicleReader withContentBasedLimiter(ContentBasedLimiter contentBasedLimiter);
/**
- * Sets the argument for this Reader. Used in conjunction with {@link #withBinarySearch(String)}
+ * Sets an argument for this Reader, typically used in conjunction with {@link #withBinarySearch(String)}.
*
- * @param arg The argument.
- * @return this
+ * @param arg The argument to pass.
+ * @return The current instance of {@link Reader}
*/
Reader withArg(@NotNull String arg);
/**
- * Sets the limiter argument for this Reader. Used with {@link #withContentBasedLimiter(ContentBasedLimiter)}
+ * Sets an argument for the content-based limiter in this Reader.
*
* @param limiterArg The limiter argument.
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader withLimiterArg(@NotNull String limiterArg);
/**
- * Sets the Reader to tail mode.
+ * Configures the Reader to operate in tail mode, continuously reading new messages as they arrive.
*
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader tail();
/**
- * Sets the maximum number of history records for this Reader.
+ * Sets the maximum number of history records to read from the queue.
*
* @param maxHistoryRecords The maximum number of history records.
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader historyRecords(final long maxHistoryRecords);
/**
* Sets the method reader interface for this Reader.
+ *
If the provided interface name is empty, a dummy method reader will be created.
*
- * @param methodReaderInterface The method reader interface class name. If empty, a dummy reader is created.
- * @return this
+ * @param methodReaderInterface The fully qualified class name of the method reader interface.
+ * @return The current instance of {@link Reader}
*/
Reader asMethodReader(@NotNull String methodReaderInterface);
/**
- * Sets the wire type for this Reader.
+ * Sets the wire type for this Reader, determining how messages are serialized and deserialized.
*
* @param wireType The wire type.
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader withWireType(@NotNull WireType wireType);
/**
- * Suppresses the display index for this Reader.
+ * Suppresses the display of the index in the output for this Reader.
*
- * @return this
+ * @return The current instance of {@link Reader}
*/
Reader suppressDisplayIndex();
/**
- * Sets the binary search for this Reader.
+ * Sets the binary search functionality for this Reader, allowing it to search for specific entries.
*
- * @param binarySearch The binary search.
- * @return this
+ * @param binarySearch The fully qualified class name of the binary search implementation.
+ * @return The current instance of {@link Reader}
*/
Reader withBinarySearch(@NotNull String binarySearch);
/**
- * Sets whether to show message history for this Reader.
+ * Enables or disables the display of message history for this Reader.
*
- * @param showMessageHistory Whether to show message history.
- * @return this
+ * @param showMessageHistory {@code true} to show message history, {@code false} otherwise.
+ * @return The current instance of {@link Reader}
*/
Reader showMessageHistory(boolean showMessageHistory);
/**
- * Retrieves the argument for this Reader.
+ * Retrieves the argument set for this Reader.
*
- * @return The argument.
+ * @return The argument as a string.
*/
String arg();
/**
- * Retrieves the limiter argument for this Reader.
+ * Retrieves the argument set for the content-based limiter in this Reader.
*
- * @return The limiter argument.
+ * @return The limiter argument as a string.
*/
String limiterArg();
/**
* Retrieves the method reader interface for this Reader.
*
- * @return The method reader interface.
+ * @return The method reader interface class.
*/
Class> methodReaderInterface();
/**
- * Creates a new Reader instance.
+ * Creates a new instance of {@link Reader}.
*
* @return A new Reader instance.
*/
diff --git a/src/main/java/net/openhft/chronicle/queue/reader/comparator/BinarySearchComparator.java b/src/main/java/net/openhft/chronicle/queue/reader/comparator/BinarySearchComparator.java
index c6e19c7624..f185c4a8b3 100644
--- a/src/main/java/net/openhft/chronicle/queue/reader/comparator/BinarySearchComparator.java
+++ b/src/main/java/net/openhft/chronicle/queue/reader/comparator/BinarySearchComparator.java
@@ -24,6 +24,17 @@
import java.util.Comparator;
import java.util.function.Consumer;
+/**
+ * Interface for implementing a comparator used in binary search operations within the {@link Reader}.
+ *
This interface extends {@link Comparator} to compare {@link Wire} objects, and {@link Consumer} to allow the comparator
+ * to configure itself using a {@link Reader} instance.
+ */
public interface BinarySearchComparator extends Comparator, Consumer {
+
+ /**
+ * Provides the key used in the binary search, represented as a {@link Wire}.
+ *
+ * @return The {@link Wire} object representing the search key
+ */
Wire wireKey();
}
diff --git a/src/main/java/net/openhft/chronicle/queue/rollcycles/LargeRollCycles.java b/src/main/java/net/openhft/chronicle/queue/rollcycles/LargeRollCycles.java
index 6d76d4f142..1ef4064688 100644
--- a/src/main/java/net/openhft/chronicle/queue/rollcycles/LargeRollCycles.java
+++ b/src/main/java/net/openhft/chronicle/queue/rollcycles/LargeRollCycles.java
@@ -1,9 +1,29 @@
+/*
+ * Copyright 2022 Higher Frequency Trading
+ *
+ * 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.rollcycles;
import net.openhft.chronicle.queue.RollCycle;
/**
- * These are used to minimise rolls but do create very large files, possibly too large.
+ * Enum representing large roll cycles, designed to minimize file rolls but resulting in very large files.
+ * These roll cycles are typically used in scenarios where fewer rollovers are preferred, but the file sizes
+ * can grow quite large and may exceed typical limits.
*/
public enum LargeRollCycles implements RollCycle {
/**
@@ -28,12 +48,25 @@ public enum LargeRollCycles implements RollCycle {
private final int lengthInMillis;
private final RollCycleArithmetic arithmetic;
+ /**
+ * Constructs a LargeRollCycle with the given parameters.
+ *
+ * @param format The format string used for rolling files
+ * @param lengthInMillis The duration of each cycle in milliseconds
+ * @param indexCount The number of index entries
+ * @param indexSpacing The spacing between indexed entries
+ */
LargeRollCycles(String format, int lengthInMillis, int indexCount, int indexSpacing) {
this.format = format;
this.lengthInMillis = lengthInMillis;
this.arithmetic = RollCycleArithmetic.of(indexCount, indexSpacing);
}
+ /**
+ * Returns the maximum number of messages allowed per cycle.
+ *
+ * @return The maximum number of messages allowed per cycle
+ */
public long maxMessagesPerCycle() {
return arithmetic.maxMessagesPerCycle();
}
@@ -49,7 +82,10 @@ public int lengthInMillis() {
}
/**
- * @return this is the size of each index array, note: indexCount^2 is the maximum number of index queue entries.
+ * Returns the default size of the index array.
+ *
Note: {@code indexCount^2} is the maximum number of index queue entries.
+ *
+ * @return The default index count
*/
@Override
public int defaultIndexCount() {
diff --git a/src/main/java/net/openhft/chronicle/queue/rollcycles/LegacyRollCycles.java b/src/main/java/net/openhft/chronicle/queue/rollcycles/LegacyRollCycles.java
index 277a3fcb20..41d030d73e 100644
--- a/src/main/java/net/openhft/chronicle/queue/rollcycles/LegacyRollCycles.java
+++ b/src/main/java/net/openhft/chronicle/queue/rollcycles/LegacyRollCycles.java
@@ -1,9 +1,29 @@
+/*
+ * Copyright 2022 Higher Frequency Trading
+ *
+ * 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.rollcycles;
import net.openhft.chronicle.queue.RollCycle;
/**
- * Old RollCycle definitions kept for historical reasons
+ * Enum representing legacy roll cycles, kept for historical reasons.
+ *
These roll cycles were used in older versions of Chronicle Queue and retain their original
+ * configurations for backward compatibility.
*/
public enum LegacyRollCycles implements RollCycle {
/**
@@ -24,12 +44,25 @@ public enum LegacyRollCycles implements RollCycle {
private final int lengthInMillis;
private final RollCycleArithmetic arithmetic;
+ /**
+ * Constructs a LegacyRollCycle with the given parameters.
+ *
+ * @param format The format string used for rolling files
+ * @param lengthInMillis The duration of each cycle in milliseconds
+ * @param indexCount The number of index entries
+ * @param indexSpacing The spacing between indexed entries
+ */
LegacyRollCycles(String format, int lengthInMillis, int indexCount, int indexSpacing) {
this.format = format;
this.lengthInMillis = lengthInMillis;
this.arithmetic = RollCycleArithmetic.of(indexCount, indexSpacing);
}
+ /**
+ * Returns the maximum number of messages allowed per cycle.
+ *
+ * @return The maximum number of messages allowed per cycle
+ */
public long maxMessagesPerCycle() {
return arithmetic.maxMessagesPerCycle();
}
@@ -45,7 +78,10 @@ public int lengthInMillis() {
}
/**
- * @return this is the size of each index array, note: indexCount^2 is the maximum number of index queue entries.
+ * Returns the default size of the index array.
+ *
Note: {@code indexCount^2} is the maximum number of index queue entries.
+ *
+ * @return The default index count
*/
@Override
public int defaultIndexCount() {
diff --git a/src/main/java/net/openhft/chronicle/queue/rollcycles/RollCycleArithmetic.java b/src/main/java/net/openhft/chronicle/queue/rollcycles/RollCycleArithmetic.java
index 26e8e565be..f15a651498 100644
--- a/src/main/java/net/openhft/chronicle/queue/rollcycles/RollCycleArithmetic.java
+++ b/src/main/java/net/openhft/chronicle/queue/rollcycles/RollCycleArithmetic.java
@@ -1,3 +1,21 @@
+/*
+ * Copyright 2022 Higher Frequency Trading
+ *
+ * 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.rollcycles;
import net.openhft.chronicle.core.Maths;
@@ -5,22 +23,39 @@
import static net.openhft.chronicle.queue.RollCycle.MAX_INDEX_COUNT;
/**
- * Reusable optimised arithmetic for converting between cycles/indices/sequenceNumbers
+ * Reusable and optimized arithmetic for converting between cycles, indices, and sequence numbers
+ * in a Chronicle Queue. This class encapsulates logic for indexing and sequencing based on
+ * configurable parameters such as {@code indexCount} and {@code indexSpacing}.
*/
public final class RollCycleArithmetic {
+
/**
- * Sunday 1970 Jan 4th 00:00:00 UTC
+ * Constant representing Sunday, January 4th, 1970 at 00:00:00 UTC, expressed in milliseconds.
*/
public static final int SUNDAY_00_00 = 259_200_000;
+
private final int cycleShift;
private final int indexCount;
private final int indexSpacing;
private final long sequenceMask;
+ /**
+ * Factory method to create an instance of {@link RollCycleArithmetic} with the given index count and spacing.
+ *
+ * @param indexCount The number of index entries allowed
+ * @param indexSpacing The spacing between indexed entries
+ * @return A new instance of {@link RollCycleArithmetic}
+ */
public static RollCycleArithmetic of(int indexCount, int indexSpacing) {
return new RollCycleArithmetic(indexCount, indexSpacing);
}
+ /**
+ * Private constructor to initialize a {@link RollCycleArithmetic} instance.
+ *
+ * @param indexCount The number of index entries (power of 2)
+ * @param indexSpacing The spacing between indexed entries (power of 2)
+ */
private RollCycleArithmetic(int indexCount, int indexSpacing) {
this.indexCount = Maths.nextPower2(indexCount, 8);
assert this.indexCount <= MAX_INDEX_COUNT : "indexCount: " + indexCount;
@@ -30,26 +65,60 @@ private RollCycleArithmetic(int indexCount, int indexSpacing) {
sequenceMask = (1L << cycleShift) - 1;
}
+ /**
+ * Returns the maximum number of messages that can be stored in a single cycle.
+ *
+ * @return The maximum number of messages per cycle
+ */
public long maxMessagesPerCycle() {
return Math.min(sequenceMask, ((long) indexCount * indexCount * indexSpacing));
}
+ /**
+ * Converts a cycle and sequence number to a unique index.
+ *
+ * @param cycle The cycle number
+ * @param sequenceNumber The sequence number within the cycle
+ * @return The calculated index
+ */
public long toIndex(int cycle, long sequenceNumber) {
return ((long) cycle << cycleShift) + (sequenceNumber & sequenceMask);
}
+ /**
+ * Extracts the sequence number from a given index.
+ *
+ * @param index The index to extract from
+ * @return The sequence number
+ */
public long toSequenceNumber(long index) {
return index & sequenceMask;
}
+ /**
+ * Extracts the cycle number from a given index.
+ *
+ * @param index The index to extract from
+ * @return The cycle number
+ */
public int toCycle(long index) {
return Maths.toUInt31(index >> cycleShift);
}
+ /**
+ * Returns the configured index spacing.
+ *
+ * @return The index spacing
+ */
public int indexSpacing() {
return indexSpacing;
}
+ /**
+ * Returns the configured index count.
+ *
+ * @return The index count
+ */
public int indexCount() {
return indexCount;
}
diff --git a/src/main/java/net/openhft/chronicle/queue/rollcycles/SparseRollCycles.java b/src/main/java/net/openhft/chronicle/queue/rollcycles/SparseRollCycles.java
index 404e609cb8..47bd66086c 100644
--- a/src/main/java/net/openhft/chronicle/queue/rollcycles/SparseRollCycles.java
+++ b/src/main/java/net/openhft/chronicle/queue/rollcycles/SparseRollCycles.java
@@ -1,9 +1,29 @@
+/*
+ * Copyright 2022 Higher Frequency Trading
+ *
+ * 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.rollcycles;
import net.openhft.chronicle.queue.RollCycle;
/**
- * These are largely used for testing and benchmarks to almost turn off indexing.
+ * Enum representing sparse roll cycles, primarily used for testing and benchmarking purposes.
+ *
These roll cycles are designed to minimize indexing, making them useful for scenarios where
+ * indexing is either unnecessary or should be kept minimal to reduce overhead.
*/
public enum SparseRollCycles implements RollCycle {
/**
@@ -28,12 +48,25 @@ public enum SparseRollCycles implements RollCycle {
private final int lengthInMillis;
private final RollCycleArithmetic arithmetic;
+ /**
+ * Constructs a SparseRollCycle with the given parameters.
+ *
+ * @param format The format string used for rolling files
+ * @param lengthInMillis The duration of each cycle in milliseconds
+ * @param indexCount The number of index entries
+ * @param indexSpacing The spacing between indexed entries
+ */
SparseRollCycles(String format, int lengthInMillis, int indexCount, int indexSpacing) {
this.format = format;
this.lengthInMillis = lengthInMillis;
this.arithmetic = RollCycleArithmetic.of(indexCount, indexSpacing);
}
+ /**
+ * Returns the maximum number of messages allowed per cycle.
+ *
+ * @return The maximum number of messages allowed per cycle
+ */
public long maxMessagesPerCycle() {
return arithmetic.maxMessagesPerCycle();
}
@@ -49,7 +82,10 @@ public int lengthInMillis() {
}
/**
- * @return this is the size of each index array, note: indexCount^2 is the maximum number of index queue entries.
+ * Returns the default size of the index array.
+ *
Note: {@code indexCount^2} is the maximum number of index queue entries.
+ *
+ * @return The default index count
*/
@Override
public int defaultIndexCount() {
diff --git a/src/main/java/net/openhft/chronicle/queue/rollcycles/TestRollCycles.java b/src/main/java/net/openhft/chronicle/queue/rollcycles/TestRollCycles.java
index f6149bce69..e69ee3f476 100644
--- a/src/main/java/net/openhft/chronicle/queue/rollcycles/TestRollCycles.java
+++ b/src/main/java/net/openhft/chronicle/queue/rollcycles/TestRollCycles.java
@@ -1,9 +1,30 @@
+/*
+ * Copyright 2022 Higher Frequency Trading
+ *
+ * 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.rollcycles;
import net.openhft.chronicle.queue.RollCycle;
/**
- * These are used for test to reduce the size of a queue dump when doing a small test.
+ * Enum representing various test roll cycles, designed to reduce the size of a queue dump
+ * when performing small tests.
+ *
These roll cycles are intended for testing purposes only and are not suited for production use
+ * due to their limited capacity and reduced indexing granularity.
*/
public enum TestRollCycles implements RollCycle {
/**
@@ -40,12 +61,25 @@ public enum TestRollCycles implements RollCycle {
private final int lengthInMillis;
private final RollCycleArithmetic arithmetic;
+ /**
+ * Constructs a TestRollCycle with the given parameters.
+ *
+ * @param format The format string used for rolling files
+ * @param lengthInMillis The duration of each cycle in milliseconds
+ * @param indexCount The number of index entries
+ * @param indexSpacing The spacing between indexed entries
+ */
TestRollCycles(String format, int lengthInMillis, int indexCount, int indexSpacing) {
this.format = format;
this.lengthInMillis = lengthInMillis;
this.arithmetic = RollCycleArithmetic.of(indexCount, indexSpacing);
}
+ /**
+ * Returns the maximum number of messages allowed per cycle.
+ *
+ * @return The maximum number of messages allowed per cycle
+ */
public long maxMessagesPerCycle() {
return arithmetic.maxMessagesPerCycle();
}
@@ -61,7 +95,10 @@ public int lengthInMillis() {
}
/**
- * @return this is the size of each index array, note: indexCount^2 is the maximum number of index queue entries.
+ * Returns the default size of the index array.
+ *
Note: {@code indexCount^2} is the maximum number of index queue entries.
+ *
+ * @return The default index count
*/
@Override
public int defaultIndexCount() {
diff --git a/src/main/java/net/openhft/chronicle/queue/util/FileState.java b/src/main/java/net/openhft/chronicle/queue/util/FileState.java
index 39350a697d..2f19b68304 100644
--- a/src/main/java/net/openhft/chronicle/queue/util/FileState.java
+++ b/src/main/java/net/openhft/chronicle/queue/util/FileState.java
@@ -18,6 +18,18 @@
package net.openhft.chronicle.queue.util;
+/**
+ * Enum representing the state of a file in relation to its usage or existence.
+ *
+ * - {@link #OPEN} - The file is currently open and being used.
+ * - {@link #CLOSED} - The file is closed and not in use.
+ * - {@link #NON_EXISTENT} - The file does not exist in the file system.
+ * - {@link #UNDETERMINED} - The state of the file cannot be determined.
+ *
+ */
public enum FileState {
- OPEN, CLOSED, NON_EXISTENT, UNDETERMINED
+ OPEN, // The file is open and being used
+ CLOSED, // The file is closed and not in use
+ NON_EXISTENT, // The file does not exist
+ UNDETERMINED // The state of the file cannot be determined
}
diff --git a/src/main/java/net/openhft/chronicle/queue/util/FileUtil.java b/src/main/java/net/openhft/chronicle/queue/util/FileUtil.java
index 070e89ea2b..0657e1ad75 100644
--- a/src/main/java/net/openhft/chronicle/queue/util/FileUtil.java
+++ b/src/main/java/net/openhft/chronicle/queue/util/FileUtil.java
@@ -27,7 +27,8 @@
import java.util.stream.Stream;
/**
- * Utility methods for handling Files in connection with ChronicleQueue.
+ * Utility methods for handling files in connection with ChronicleQueue.
+ * Provides functions for identifying removable files, checking open file states, and determining file suffixes.
*
* @author Per Minborg
* @since 5.17.34
diff --git a/src/main/java/net/openhft/chronicle/queue/util/MicroTouched.java b/src/main/java/net/openhft/chronicle/queue/util/MicroTouched.java
index d3b56771b6..9029f40129 100644
--- a/src/main/java/net/openhft/chronicle/queue/util/MicroTouched.java
+++ b/src/main/java/net/openhft/chronicle/queue/util/MicroTouched.java
@@ -18,14 +18,22 @@
package net.openhft.chronicle.queue.util;
+/**
+ * Interface representing small operations that can be used to reduce jitter in threads.
+ *
Provides methods to perform tiny operations either on the current thread or in a background thread to improve performance consistency.
+ */
public interface MicroTouched {
/**
- * perform a tiny operation to improve jitter in the current thread.
+ * Performs a tiny operation to improve jitter in the current thread.
+ *
This method should be called in contexts where reducing jitter or improving performance consistency is desired.
+ *
+ * @return {@code true} if the operation was successful, otherwise {@code false}
*/
boolean microTouch();
/**
- * perofmr a small operation to improve jitter in a background thread.
+ * Performs a small operation to improve jitter in a background thread.
+ *
This method is designed to be executed in a background thread to smooth out performance fluctuations.
*/
void bgMicroTouch();
}
diff --git a/src/main/java/net/openhft/chronicle/queue/util/PretouchUtil.java b/src/main/java/net/openhft/chronicle/queue/util/PretouchUtil.java
index ee4a918741..cc8c81a99a 100644
--- a/src/main/java/net/openhft/chronicle/queue/util/PretouchUtil.java
+++ b/src/main/java/net/openhft/chronicle/queue/util/PretouchUtil.java
@@ -26,37 +26,78 @@
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder;
import org.jetbrains.annotations.NotNull;
+/**
+ * Utility class for creating {@link Pretoucher} and {@link EventHandler} instances.
+ *
+ * This class provides a factory-based mechanism to support both enterprise and non-enterprise pretoucher functionality.
+ * If enterprise features are available, it attempts to load the enterprise implementation, otherwise, it falls back
+ * to a basic implementation.
+ */
public final class PretouchUtil {
+
+ // Singleton instance of the PretoucherFactory
private static final PretoucherFactory INSTANCE;
static {
PretoucherFactory instance;
try {
+ // Attempt to load enterprise features
final Class> clazz = Class.forName("software.chronicle.enterprise.queue.pretoucher.EnterprisePretouchUtil");
instance = (PretoucherFactory) ObjectUtils.newInstance(clazz);
assert SingleChronicleQueueBuilder.areEnterpriseFeaturesAvailable();
} catch (Exception e) {
+ // Fallback to the non-enterprise implementation
instance = new PretouchFactoryEmpty();
SingleChronicleQueueBuilder.onlyAvailableInEnterprise("Pretoucher");
}
- INSTANCE = instance;
+ INSTANCE = instance; // Set the determined instance
}
+ /**
+ * Creates an {@link EventHandler} for the given {@link ChronicleQueue}.
+ *
+ * @param queue The {@link ChronicleQueue} instance
+ * @return A new {@link EventHandler} for the specified queue
+ */
public static EventHandler createEventHandler(@NotNull final ChronicleQueue queue) {
return INSTANCE.createEventHandler((SingleChronicleQueue) queue);
}
+ /**
+ * Creates a {@link Pretoucher} for the given {@link ChronicleQueue}.
+ *
+ * @param queue The {@link ChronicleQueue} instance
+ * @return A new {@link Pretoucher} for the specified queue
+ */
public static Pretoucher createPretoucher(@NotNull final ChronicleQueue queue) {
return INSTANCE.createPretoucher((SingleChronicleQueue) queue);
}
+ /**
+ * Fallback factory class for non-enterprise environments.
+ *
+ * Provides basic implementations for creating {@link EventHandler} and {@link Pretoucher} when enterprise features
+ * are not available.
+ */
private static class PretouchFactoryEmpty implements PretoucherFactory {
+ /**
+ * Returns a no-op {@link EventHandler} that does nothing.
+ *
+ * @param queue The {@link SingleChronicleQueue} instance
+ * @return A no-op {@link EventHandler}
+ */
@Override
public EventHandler createEventHandler(@NotNull final SingleChronicleQueue queue) {
return () -> false;
}
+ /**
+ * Creates a basic {@link Pretoucher} for the specified {@link SingleChronicleQueue}.
+ *
+ * @param queue The {@link SingleChronicleQueue} instance
+ * @return A basic {@link Pretoucher} instance
+ */
@Override
public Pretoucher createPretoucher(@NotNull final SingleChronicleQueue queue) {
return queue.createPretoucher();
diff --git a/src/main/java/net/openhft/chronicle/queue/util/PretoucherFactory.java b/src/main/java/net/openhft/chronicle/queue/util/PretoucherFactory.java
index 34f64801c6..af402223b0 100644
--- a/src/main/java/net/openhft/chronicle/queue/util/PretoucherFactory.java
+++ b/src/main/java/net/openhft/chronicle/queue/util/PretoucherFactory.java
@@ -23,8 +23,30 @@
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueue;
import org.jetbrains.annotations.NotNull;
+/**
+ * Factory interface for creating {@link Pretoucher} and {@link EventHandler} instances.
+ *
+ * Pretouchers are used to preload and access resources in advance, reducing the likelihood of delays due to I/O or
+ * memory access. This interface provides methods to create both event handlers and pretouchers for a specific
+ * {@link SingleChronicleQueue}.
+ */
public interface PretoucherFactory {
+ /**
+ * Creates an {@link EventHandler} for the specified {@link SingleChronicleQueue}.
+ *
The event handler can be used to periodically pretouch or handle other events related to the queue.
+ *
+ * @param queue The {@link SingleChronicleQueue} instance for which the event handler is created
+ * @return A new {@link EventHandler} for the given queue
+ */
EventHandler createEventHandler(@NotNull final SingleChronicleQueue queue);
+ /**
+ * Creates a {@link Pretoucher} for the specified {@link SingleChronicleQueue}.
+ *
The pretoucher is used to access and load queue resources in advance, reducing potential I/O-related delays
+ * during critical operations.
+ *
+ * @param queue The {@link SingleChronicleQueue} instance for which the pretoucher is created
+ * @return A new {@link Pretoucher} for the given queue
+ */
Pretoucher createPretoucher(@NotNull final SingleChronicleQueue queue);
}
diff --git a/src/main/java/net/openhft/chronicle/queue/util/ToolsUtil.java b/src/main/java/net/openhft/chronicle/queue/util/ToolsUtil.java
index abcfa92bda..ac86164d5d 100644
--- a/src/main/java/net/openhft/chronicle/queue/util/ToolsUtil.java
+++ b/src/main/java/net/openhft/chronicle/queue/util/ToolsUtil.java
@@ -20,13 +20,20 @@
import net.openhft.chronicle.core.Jvm;
+/**
+ * Utility class for tools-related functions, such as resource tracing warnings.
+ *
This class is final and cannot be instantiated.
+ */
public final class ToolsUtil {
private ToolsUtil() {
}
/**
- * When running tools e.g. ChronicleReader, from the CQ source dir, resource tracing may be turned on
+ * Warns the user if resource tracing is enabled, which may eventually lead to an OutOfMemoryError (OOME).
+ *
When running tools like {@code ChronicleReader} from the Chronicle Queue source directory, this method checks
+ * if resource tracing is turned on. If it is, a warning is printed to {@code System.err}, as SLF4J may not be
+ * properly set up in certain tool environments (e.g., when running shell scripts like {@code queue_reader.sh}).
*/
public static void warnIfResourceTracing() {
// System.err (*not* logger as slf4j may not be set up e.g. when running queue_reader.sh)
diff --git a/src/test/java/net/openhft/chronicle/queue/ChronicleHistoryReaderMainTest.java b/src/test/java/net/openhft/chronicle/queue/ChronicleHistoryReaderMainTest.java
new file mode 100644
index 0000000000..c97312c0d0
--- /dev/null
+++ b/src/test/java/net/openhft/chronicle/queue/ChronicleHistoryReaderMainTest.java
@@ -0,0 +1,185 @@
+package net.openhft.chronicle.queue;
+
+import net.openhft.chronicle.core.Jvm;
+import net.openhft.chronicle.queue.reader.ChronicleHistoryReader;
+import org.apache.commons.cli.*;
+import org.junit.Test;
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.security.Permission;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+@SuppressWarnings({"deprecation", "removal"})
+public class ChronicleHistoryReaderMainTest {
+
+ private static class NoExitSecurityManager extends SecurityManager {
+ @Override
+ public void checkPermission(Permission perm) {
+ // allow anything
+ }
+
+ @Override
+ public void checkExit(int status) {
+ throw new SecurityException("System exit attempted with status: " + status);
+ }
+ }
+
+ @Before
+ public void setUp() {
+ assumeTrue(Jvm.majorVersion() < 17);
+ System.setSecurityManager(new NoExitSecurityManager());
+ }
+
+ @After
+ public void tearDown() {
+ System.setSecurityManager(null); // Restore the default security manager after each test
+ }
+
+ @Test
+ public void testRunExecutesChronicleHistoryReader() {
+ // Setup
+ ChronicleHistoryReaderMain main = new ChronicleHistoryReaderMain() {
+ @Override
+ protected ChronicleHistoryReader chronicleHistoryReader() {
+ return new ChronicleHistoryReader() {
+ @Override
+ public void execute() {
+ // Simulate execution
+ assertTrue(true); // Verify execution reached here
+ }
+ };
+ }
+ };
+
+ String[] args = {"-d", "test-directory"}; // Simulate passing a directory argument
+ main.run(args); // Expect that execute is called
+ }
+
+ @Test
+ public void testSetupChronicleHistoryReader() {
+ // Simulate command line arguments
+ String[] args = {"-d", "test-directory", "-p", "-m", "-t", "NANOSECONDS"};
+ ChronicleHistoryReaderMain main = new ChronicleHistoryReaderMain();
+ Options options = main.options();
+ CommandLine commandLine = main.parseCommandLine(args, options);
+
+ // Create a mock ChronicleHistoryReader
+ ChronicleHistoryReader historyReader = new ChronicleHistoryReader() {
+ private boolean progressEnabled = false;
+ private boolean histosByMethod = false;
+
+ @Override
+ public ChronicleHistoryReader withProgress(boolean progress) {
+ this.progressEnabled = progress;
+ return this;
+ }
+
+ @Override
+ public ChronicleHistoryReader withHistosByMethod(boolean histosByMethod) {
+ this.histosByMethod = histosByMethod;
+ return this;
+ }
+
+ @Override
+ public ChronicleHistoryReader withMessageSink(Consumer sink) {
+ return this;
+ }
+
+ @Override
+ public ChronicleHistoryReader withBasePath(Path basePath) {
+ assertEquals("test-directory", basePath.toString());
+ return this;
+ }
+
+ @Override
+ public ChronicleHistoryReader withTimeUnit(TimeUnit timeUnit) {
+ assertEquals(TimeUnit.NANOSECONDS, timeUnit);
+ return this;
+ }
+
+ @Override
+ public void execute() {
+ // Simulate execution
+ }
+ };
+
+ // Act
+ main.setup(commandLine, historyReader);
+
+ // Assert
+ assertTrue(historyReader.withProgress(true) != null);
+ assertTrue(historyReader.withHistosByMethod(true) != null);
+ }
+
+ @Test
+ public void testParseCommandLine() {
+ // Test that parseCommandLine correctly parses arguments
+ ChronicleHistoryReaderMain main = new ChronicleHistoryReaderMain();
+ Options options = main.options();
+ String[] args = {"-d", "test-directory", "-t", "SECONDS"};
+ CommandLine commandLine = main.parseCommandLine(args, options);
+
+ assertEquals("test-directory", commandLine.getOptionValue("d"));
+ assertEquals("SECONDS", commandLine.getOptionValue("t"));
+ }
+
+ @Test
+ public void testParseCommandLineHelpOption() {
+ ChronicleHistoryReaderMain main = new ChronicleHistoryReaderMain() {
+ @Override
+ protected void printHelpAndExit(Options options, int status, String message) {
+ assertEquals(0, status); // Ensure help is printed with status 0 (success)
+ throw new ThreadDeath(); // Exit without calling System.exit()
+ }
+ };
+ String[] args = {"-h"};
+
+ // Manually setting the security manager to catch System.exit() if needed
+ try {
+ main.run(args); // Should trigger the help message and exit with 0
+ fail("Expected ThreadDeath to be thrown");
+
+ } catch (ThreadDeath e) {
+ // Expected exception
+
+ } catch (SecurityException e) {
+ fail("System.exit was called unexpectedly.");
+ }
+ }
+
+ @Test
+ public void testOptionsConfiguration() {
+ ChronicleHistoryReaderMain main = new ChronicleHistoryReaderMain();
+ Options options = main.options();
+
+ // Verify that all expected options are present
+ assertNotNull(options.getOption("d"));
+ assertNotNull(options.getOption("h"));
+ assertNotNull(options.getOption("t"));
+ assertNotNull(options.getOption("i"));
+ assertNotNull(options.getOption("w"));
+ assertNotNull(options.getOption("u"));
+ assertNotNull(options.getOption("p"));
+ assertNotNull(options.getOption("m"));
+ }
+
+ @Test
+ public void testPrintHelpAndExit() {
+ ChronicleHistoryReaderMain main = new ChronicleHistoryReaderMain();
+ Options options = main.options();
+
+ try {
+ main.printHelpAndExit(options, 0, "Optional message");
+ } catch (SecurityException e) {
+ assertTrue(e.getMessage().contains("System exit attempted with status: 0"));
+ }
+ }
+}
diff --git a/src/test/java/net/openhft/chronicle/queue/ChronicleQueueTest.java b/src/test/java/net/openhft/chronicle/queue/ChronicleQueueTest.java
new file mode 100644
index 0000000000..43dbc3783a
--- /dev/null
+++ b/src/test/java/net/openhft/chronicle/queue/ChronicleQueueTest.java
@@ -0,0 +1,228 @@
+package net.openhft.chronicle.queue;
+
+import net.openhft.chronicle.core.time.TimeProvider;
+import net.openhft.chronicle.core.values.LongValue;
+import net.openhft.chronicle.queue.impl.single.Pretoucher;
+import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder;
+import net.openhft.chronicle.queue.ExcerptTailer;
+import net.openhft.chronicle.wire.WireType;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.OutputStream;
+import java.io.Writer;
+
+import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@SuppressWarnings("deprecation")
+public class ChronicleQueueTest extends QueueTestCommon {
+
+ @Test
+ public void testSingleBuilderCreatesNewInstance() {
+ // Test that the singleBuilder() method returns a valid SingleChronicleQueueBuilder instance
+ SingleChronicleQueueBuilder builder = ChronicleQueue.singleBuilder();
+ assertNotNull(builder);
+ }
+
+ @Test
+ public void testIndexForIdThrowsUnsupportedOperationException() {
+ // Test that indexForId(String id) throws an UnsupportedOperationException as expected
+ try (ChronicleQueue queue = new StubChronicleQueue()) {
+ assertThrows(UnsupportedOperationException.class, () -> queue.indexForId("someId"));
+ }
+ }
+
+ @Test
+ public void testCreateTailerThrowsUnsupportedOperationExceptionForNamedTailer() {
+ // Test that createTailer(String id) throws an UnsupportedOperationException for the default implementation
+ try (ChronicleQueue queue = new StubChronicleQueue()) {
+ assertThrows(UnsupportedOperationException .class, () ->queue.createTailer("namedTailer"));
+ }
+ }
+
+ @Test
+ public void testCreateTailerCreatesNewExcerptTailer() {
+ // Assuming createTailer() creates a valid ExcerptTailer when not overridden
+ try (ChronicleQueue queue = ChronicleQueue.single("test-path"); // Adjust with a proper path
+ ExcerptTailer tailer = queue.createTailer()) {
+ assertNotNull(tailer);
+ }
+ }
+
+ @Test
+ public void testFileAbsolutePath() {
+ // Assuming fileAbsolutePath() returns the correct absolute path of the Chronicle Queue
+ try (ChronicleQueue queue = ChronicleQueue.single("test-path")) { // Use a test path
+ String path = queue.fileAbsolutePath();
+ assertNotNull(path);
+ assertTrue(path.endsWith("test-path")); // Adjust based on actual path structure
+ }
+ }
+
+ @Test
+ public void testDumpCallsOutputStreamWriter() {
+ // Test that dump(OutputStream stream, long fromIndex, long toIndex) calls the writer version correctly
+ try (ChronicleQueue queue = new StubChronicleQueue() {
+
+ @Override
+ public void dump(Writer writer, long fromIndex, long toIndex) {
+ // Validate that this method is called with the correct parameters
+ assertNotNull(writer);
+ assertEquals(0, fromIndex);
+ assertEquals(10, toIndex);
+ }
+ }) {
+ OutputStream stream = new ByteArrayOutputStream();
+ queue.dump(stream, 0, 10); // Expect that this calls the other dump method
+ }
+ }
+
+ @Test
+ public void testLastIndexReplicatedReturnsMinusOne() {
+ // Test that lastIndexReplicated() returns -1
+ try (ChronicleQueue queue = new StubChronicleQueue()) {
+ assertEquals(-1, queue.lastIndexReplicated());
+ }
+ }
+
+ @Test
+ public void testLastAcknowledgedIndexReplicatedReturnsMinusOne() {
+ // Test that lastAcknowledgedIndexReplicated() returns -1
+ try (ChronicleQueue queue = new StubChronicleQueue()) {
+ assertEquals(-1, queue.lastAcknowledgedIndexReplicated());
+ }
+ }
+
+ @Test
+ public void testLastIndexMSyncedReturnsMinusOne() {
+ // Test that lastIndexMSynced() returns -1
+ try (ChronicleQueue queue = new StubChronicleQueue()) {
+ assertEquals(-1, queue.lastIndexMSynced());
+ }
+ }
+
+ @Test
+ public void testLastIndexMSyncedThrowsUnsupportedOperationException() {
+ // Test that lastIndexMSynced(long lastIndexMSynced) throws UnsupportedOperationException
+ try (ChronicleQueue queue = new StubChronicleQueue()) {
+ assertThrows(UnsupportedOperationException.class, () -> queue.lastIndexMSynced(100L));
+ }
+ }
+
+ @Test
+ public void testAwaitAsyncReturnsTrue() {
+ // Test that awaitAsync() always returns true
+ try (ChronicleQueue queue = new StubChronicleQueue()) {
+ assertTrue(queue.awaitAsync());
+ }
+ }
+
+ @Test
+ public void testNonAsyncTailerCallsCreateTailer() {
+ // Test that nonAsyncTailer() calls createTailer()
+ try (ChronicleQueue queue = ChronicleQueue.single("test-path")) { // Adjust with a proper path
+ assertNotNull(queue.nonAsyncTailer());
+ }
+ }
+
+ // A minimal stub of ChronicleQueue for testing UnsupportedOperationException
+ static class StubChronicleQueue implements ChronicleQueue {
+ @Override
+ public void close() {
+ // Implementation for abstract method close()
+ }
+
+ @Override
+ public boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ public @NotNull ExcerptTailer createTailer() {
+ return null;
+ }
+
+ @Override
+ public @NotNull ExcerptAppender acquireAppender() {
+ return null;
+ }
+
+ @Override
+ public @NotNull ExcerptAppender createAppender() {
+ return null;
+ }
+
+ @Override
+ public long firstIndex() {
+ return 0;
+ }
+
+ @Override
+ public long lastIndex() {
+ return 0;
+ }
+
+ @Override
+ public @NotNull WireType wireType() {
+ return null;
+ }
+
+ @Override
+ public void clear() {
+ }
+
+ @Override
+ public @NotNull File file() {
+ return new File("test-path");
+ }
+
+ @Override
+ public @NotNull String dump() {
+ return null;
+ }
+
+ @Override
+ public void dump(Writer writer, long fromIndex, long toIndex) {
+ }
+
+ @Override
+ public int sourceId() {
+ return 0;
+ }
+
+ @Override
+ public @NotNull RollCycle rollCycle() {
+ return null;
+ }
+
+ @Override
+ public TimeProvider time() {
+ return null;
+ }
+
+ @Override
+ public int deltaCheckpointInterval() {
+ return 0;
+ }
+
+ @Override
+ public void lastIndexReplicated(long lastIndex) {
+ }
+
+ @Override
+ public void lastAcknowledgedIndexReplicated(long lastAcknowledgedIndexReplicated) {
+ }
+
+ @Override
+ public void refreshDirectoryListing() {
+ }
+
+ @Override
+ public @NotNull String dumpLastHeader() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/net/openhft/chronicle/queue/ChronicleReaderMainTest.java b/src/test/java/net/openhft/chronicle/queue/ChronicleReaderMainTest.java
new file mode 100644
index 0000000000..94cf041b2f
--- /dev/null
+++ b/src/test/java/net/openhft/chronicle/queue/ChronicleReaderMainTest.java
@@ -0,0 +1,77 @@
+package net.openhft.chronicle.queue;
+
+import org.junit.Test;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.commons.cli.Options;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for ChronicleReaderMain class.
+ */
+public class ChronicleReaderMainTest extends QueueTestCommon {
+
+ @Test
+ public void testMainWithValidArguments() {
+ ignoreException("Metadata file not found in readOnly mode");
+ try {
+ // Create a temporary directory for the test
+ Path tempDir = Files.createTempDirectory("testDirectory");
+
+ String[] args = {"-d", tempDir.toString()};
+
+ // Capture System.out and System.err
+ ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(outContent));
+ System.setErr(new PrintStream(errContent));
+
+ ChronicleReaderMain.main(args); // Run the main method with valid args
+
+ assertTrue("Expected valid arguments to run without issues.", true);
+
+ // Clean up: delete the temporary directory
+ File dir = tempDir.toFile();
+ if (dir.exists()) {
+ dir.delete();
+ }
+
+ } catch (Exception e) {
+ fail("No exception should be thrown with valid arguments: " + e.getMessage());
+ } finally {
+ // Reset System.out and System.err
+ System.setOut(System.out);
+ System.setErr(System.err);
+ }
+ }
+
+ @Test
+ public void testOptionsConfiguration() {
+ ChronicleReaderMain main = new ChronicleReaderMain();
+ Options options = main.options();
+
+ // Verify options are set correctly
+ assertNotNull(options.getOption("d")); // Directory option
+ assertNotNull(options.getOption("i")); // Include regex
+ assertNotNull(options.getOption("e")); // Exclude regex
+ assertNotNull(options.getOption("f")); // Follow (tail) option
+ assertNotNull(options.getOption("m")); // Max history
+ assertNotNull(options.getOption("n")); // Start index
+ assertNotNull(options.getOption("b")); // Binary search
+ assertNotNull(options.getOption("a")); // Binary argument
+ assertNotNull(options.getOption("r")); // As method reader
+ assertNotNull(options.getOption("g")); // Message history
+ assertNotNull(options.getOption("w")); // Wire type
+ assertNotNull(options.getOption("s")); // Suppress index
+ assertNotNull(options.getOption("l")); // Single line squash
+ assertNotNull(options.getOption("z")); // Use local timezone
+ assertNotNull(options.getOption("k")); // Reverse order
+ assertNotNull(options.getOption("x")); // Max results
+ assertNotNull(options.getOption("cbl")); // Content-based limiter
+ assertNotNull(options.getOption("named")); // Named tailer ID
+ }
+}
diff --git a/src/test/java/net/openhft/chronicle/queue/ExcerptAppenderTest.java b/src/test/java/net/openhft/chronicle/queue/ExcerptAppenderTest.java
new file mode 100644
index 0000000000..08a2c9ef91
--- /dev/null
+++ b/src/test/java/net/openhft/chronicle/queue/ExcerptAppenderTest.java
@@ -0,0 +1,149 @@
+package net.openhft.chronicle.queue;
+
+import net.openhft.chronicle.bytes.Bytes;
+import net.openhft.chronicle.bytes.BytesStore;
+import net.openhft.chronicle.core.OS;
+import net.openhft.chronicle.core.io.IOTools;
+import net.openhft.chronicle.wire.DocumentContext;
+import net.openhft.chronicle.wire.UnrecoverableTimeoutException;
+import net.openhft.chronicle.wire.Wire;
+import org.junit.AfterClass;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit tests for ExcerptAppender interface implementations.
+ */
+public class ExcerptAppenderTest extends QueueTestCommon {
+
+ static final String TEST_QUEUE = OS.getTarget() + "/ExcerptAppenderTest";
+
+ class ExcerptAppenderImpl implements ExcerptAppender {
+
+ private long lastIndexAppended = 0;
+ private int currentCycle = 1;
+ private Wire wire;
+
+ @Override
+ public void writeBytes(BytesStore, ?> bytes) {
+ // Assume the bytes are written successfully
+ lastIndexAppended++;
+ }
+
+ @Override
+ public long lastIndexAppended() {
+ return lastIndexAppended;
+ }
+
+ @Override
+ public int cycle() {
+ return currentCycle;
+ }
+
+ @Override
+ public Wire wire() {
+ return wire;
+ }
+
+ @Override
+ public int sourceId() {
+ return 0;
+ }
+
+ @Override
+ public ChronicleQueue queue() {
+ return ChronicleQueue.single(TEST_QUEUE);
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ public void singleThreadedCheckReset() {
+
+ }
+
+ @Override
+ public void singleThreadedCheckDisabled(boolean singleThreadedCheckDisabled) {
+
+ }
+
+ @Override
+ public DocumentContext writingDocument(boolean metaData) throws UnrecoverableTimeoutException {
+ return null;
+ }
+
+ @Override
+ public DocumentContext acquireWritingDocument(boolean metaData) throws UnrecoverableTimeoutException {
+ return null;
+ }
+ }
+
+ @AfterClass
+ public static void cleanup() {
+ IOTools.deleteDirWithFiles(TEST_QUEUE);
+ }
+
+ @Test
+ public void testWriteBytes() {
+ ExcerptAppenderImpl appender = new ExcerptAppenderImpl();
+ Bytes bytes = Bytes.wrapForRead("Test data".getBytes(StandardCharsets.UTF_8));
+
+ appender.writeBytes(bytes);
+
+ assertEquals(1, appender.lastIndexAppended());
+ }
+
+ @Test
+ public void testLastIndexAppended() {
+ ExcerptAppenderImpl appender = new ExcerptAppenderImpl();
+
+ assertEquals(0, appender.lastIndexAppended());
+
+ Bytes bytes = Bytes.wrapForRead("Test data".getBytes(StandardCharsets.UTF_8));
+ appender.writeBytes(bytes);
+
+ assertEquals(1, appender.lastIndexAppended());
+ }
+
+ @Test
+ public void testCycle() {
+ ExcerptAppenderImpl appender = new ExcerptAppenderImpl();
+
+ assertEquals(1, appender.cycle());
+ }
+
+ @Test
+ public void testWire() {
+ ExcerptAppenderImpl appender = new ExcerptAppenderImpl();
+
+ assertNull(appender.wire()); // As wire is not set in this example
+ }
+
+ @Test
+ public void testPretouch() {
+ ExcerptAppenderImpl appender = new ExcerptAppenderImpl();
+ appender.pretouch();
+
+ // No assertion needed as pretouch() is a no-op in the default implementation
+ }
+
+ @Test
+ public void testNormaliseEOFs() {
+ ExcerptAppenderImpl appender = new ExcerptAppenderImpl();
+ appender.normaliseEOFs();
+
+ // No assertion needed as normaliseEOFs() is a no-op in the default implementation
+ }
+}
diff --git a/src/test/java/net/openhft/chronicle/queue/ExcerptCommonTest.java b/src/test/java/net/openhft/chronicle/queue/ExcerptCommonTest.java
new file mode 100644
index 0000000000..50e5137c49
--- /dev/null
+++ b/src/test/java/net/openhft/chronicle/queue/ExcerptCommonTest.java
@@ -0,0 +1,106 @@
+package net.openhft.chronicle.queue;
+
+import net.openhft.chronicle.core.OS;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit tests for ExcerptCommon interface implementations.
+ */
+public class ExcerptCommonTest extends QueueTestCommon {
+
+ static final String TEST_QUEUE = OS.getTarget() + "/ExcerptCommonTest";
+
+ class ExcerptCommonImpl implements ExcerptCommon {
+ private final int sourceId;
+ private final ChronicleQueue queue;
+ private final File currentFile;
+
+ public ExcerptCommonImpl(int sourceId, ChronicleQueue queue, File currentFile) {
+ this.sourceId = sourceId;
+ this.queue = queue;
+ this.currentFile = currentFile;
+ }
+
+ @Override
+ public int sourceId() {
+ return sourceId;
+ }
+
+ @Override
+ public ChronicleQueue queue() {
+ return queue;
+ }
+
+ @Override
+ public File currentFile() {
+ return currentFile;
+ }
+
+ @Override
+ public void sync() {
+ // Sync implementation
+ }
+
+ @Override
+ public void close() {
+ // Close resources if necessary
+ }
+
+ @Override
+ public boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ public void singleThreadedCheckReset() {
+
+ }
+
+ @Override
+ public void singleThreadedCheckDisabled(boolean singleThreadedCheckDisabled) {
+
+ }
+ }
+
+ @Test
+ public void testSourceId() {
+ try (ChronicleQueue queue = ChronicleQueue.single(TEST_QUEUE)) {
+ ExcerptCommonImpl excerpt = new ExcerptCommonImpl(123, queue, null);
+ assertEquals(123, excerpt.sourceId());
+ }
+ }
+
+ @Test
+ public void testQueue() {
+ try (ChronicleQueue queue = ChronicleQueue.single(TEST_QUEUE)) {
+ ExcerptCommonImpl excerpt = new ExcerptCommonImpl(123, queue, null);
+ assertEquals(queue, excerpt.queue());
+ }
+ }
+
+ @Test
+ public void testCurrentFile() {
+ File file = new File("testfile.txt");
+ try (ChronicleQueue queue = ChronicleQueue.single(TEST_QUEUE)) {
+ ExcerptCommonImpl excerpt = new ExcerptCommonImpl(123, queue, file);
+ assertEquals(file, excerpt.currentFile());
+
+ ExcerptCommonImpl excerptWithNullFile = new ExcerptCommonImpl(123, queue, null);
+ assertNull(excerptWithNullFile.currentFile());
+ }
+ }
+
+ @Test
+ public void testSync() {
+ try (ChronicleQueue queue = ChronicleQueue.single(TEST_QUEUE)) {
+ ExcerptCommonImpl excerpt = new ExcerptCommonImpl(123, queue, null);
+ excerpt.sync(); // Would test actual sync if implemented
+ // No assertion needed for this default method
+ }
+ }
+}
diff --git a/src/test/java/net/openhft/chronicle/queue/ExcerptTailerTest.java b/src/test/java/net/openhft/chronicle/queue/ExcerptTailerTest.java
new file mode 100644
index 0000000000..1dc19674e3
--- /dev/null
+++ b/src/test/java/net/openhft/chronicle/queue/ExcerptTailerTest.java
@@ -0,0 +1,63 @@
+package net.openhft.chronicle.queue;
+
+import net.openhft.chronicle.wire.DocumentContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.*;
+
+public class ExcerptTailerTest extends QueueTestCommon {
+
+ private ExcerptTailer excerptTailer;
+ private ChronicleQueue queue;
+
+ @Before
+ public void setUp() {
+ File dir = new File(System.getProperty("java.io.tmpdir"), "queue-test");
+ queue = ChronicleQueue.single(dir.getPath());
+ excerptTailer = queue.createTailer();
+ }
+
+ @After
+ public void tearDown() {
+ excerptTailer.close();
+ queue.close();
+ }
+
+ @Test
+ public void testReadingDocumentWithMetaData() {
+ try (DocumentContext dc = excerptTailer.readingDocument(true)) {
+ assertNotNull(dc);
+ }
+ }
+
+ @Test
+ public void testReadingDocumentWithoutMetaData() {
+ try (DocumentContext dc = excerptTailer.readingDocument(false)) {
+ assertNotNull(dc);
+ }
+ }
+
+ @Test
+ public void testIndex() {
+ long index = excerptTailer.index();
+ assertTrue(index >= 0);
+ }
+
+ @Test
+ public void testLastReadIndex() {
+ // The last read index may return -1 or 0 depending on the state
+ long lastReadIndex = excerptTailer.lastReadIndex();
+ assertTrue(lastReadIndex == -1 || lastReadIndex == 0);
+ }
+
+ @Test
+ public void testCycle() {
+ // Since no data has been read, cycle should be Integer.MIN_VALUE as no cycle has been loaded
+ int cycle = excerptTailer.cycle();
+ assertEquals(Integer.MIN_VALUE,cycle);
+ }
+}
diff --git a/src/test/java/net/openhft/chronicle/queue/NoMessageHistoryTest.java b/src/test/java/net/openhft/chronicle/queue/NoMessageHistoryTest.java
new file mode 100644
index 0000000000..d82eac21e6
--- /dev/null
+++ b/src/test/java/net/openhft/chronicle/queue/NoMessageHistoryTest.java
@@ -0,0 +1,83 @@
+package net.openhft.chronicle.queue;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class NoMessageHistoryTest extends QueueTestCommon {
+
+ @Test
+ public void testSingletonInstance() {
+ // Test that the singleton instance is available and not null
+ assertNotNull(NoMessageHistory.INSTANCE);
+ }
+
+ @Test
+ public void testTimings() {
+ // Test that timings() always returns 0
+ assertEquals(0, NoMessageHistory.INSTANCE.timings());
+ }
+
+ @Test
+ public void testTimingForIndex() {
+ // Test that timing(int n) always returns -1
+ assertEquals(-1, NoMessageHistory.INSTANCE.timing(0));
+ assertEquals(-1, NoMessageHistory.INSTANCE.timing(1));
+ }
+
+ @Test
+ public void testSources() {
+ // Test that sources() always returns 0
+ assertEquals(0, NoMessageHistory.INSTANCE.sources());
+ }
+
+ @Test
+ public void testSourceIdForIndex() {
+ // Test that sourceId(int n) always returns -1
+ assertEquals(-1, NoMessageHistory.INSTANCE.sourceId(0));
+ assertEquals(-1, NoMessageHistory.INSTANCE.sourceId(1));
+ }
+
+ @Test
+ public void testSourceIdsEndsWith() {
+ // Test that sourceIdsEndsWith(int[] sourceIds) always returns false
+ assertFalse(NoMessageHistory.INSTANCE.sourceIdsEndsWith(new int[] {1, 2, 3}));
+ }
+
+ @Test
+ public void testSourceIndexForIndex() {
+ // Test that sourceIndex(int n) always returns -1
+ assertEquals(-1, NoMessageHistory.INSTANCE.sourceIndex(0));
+ assertEquals(-1, NoMessageHistory.INSTANCE.sourceIndex(1));
+ }
+
+ @Test
+ public void testResetWithParameters() {
+ // Test that reset(int sourceId, long sourceIndex) performs no action (no exceptions thrown)
+ NoMessageHistory.INSTANCE.reset(1, 100L);
+ }
+
+ @Test
+ public void testResetWithoutParameters() {
+ // Test that reset() performs no action (no exceptions thrown)
+ NoMessageHistory.INSTANCE.reset();
+ }
+
+ @Test
+ public void testLastSourceId() {
+ // Test that lastSourceId() always returns -1
+ assertEquals(-1, NoMessageHistory.INSTANCE.lastSourceId());
+ }
+
+ @Test
+ public void testLastSourceIndex() {
+ // Test that lastSourceIndex() always returns -1
+ assertEquals(-1, NoMessageHistory.INSTANCE.lastSourceIndex());
+ }
+
+ @Test
+ public void testIsDirty() {
+ // Test that isDirty() always returns false
+ assertFalse(NoMessageHistory.INSTANCE.isDirty());
+ }
+}