Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2024 60East Technologies Inc., All Rights Reserved.
//
// This computer software is owned by 60East Technologies Inc. and is
// protected by U.S. copyright laws and other laws and by international
// treaties. This computer software is furnished by 60East Technologies
// Inc. pursuant to a written license agreement and may be used, copied,
// transmitted, and stored only in accordance with the terms of such
// license agreement and with the inclusion of the above copyright notice.
// This computer software or any other copies thereof may not be provided
// or otherwise made available to any other person.
//
// U.S. Government Restricted Rights. This computer software: (a) was
// developed at private expense and is in all respects the proprietary
// information of 60East Technologies Inc.; (b) was not developed with
// government funds; (c) is a trade secret of 60East Technologies Inc.
// for all purposes of the Freedom of Information Act; and (d) is a
// commercial item and thus, pursuant to Section 12.212 of the Federal
// Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
// Government's use, duplication or disclosure of the computer software
// is subject to the restrictions set forth by 60East Technologies Inc..
//
////////////////////////////////////////////////////////////////////////////
package com.crankuptheamps.client;
import java.io.IOException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
import com.crankuptheamps.client.exception.DisconnectedException;
import com.crankuptheamps.client.exception.StoreException;
import com.crankuptheamps.client.exception.TimedOutException;
/**
* PublishStore that stores first in memory, and swaps excess out to disk.
*
*/
public class HybridPublishStore implements Store {
// Two storage components: one in-memory, one on disk
protected PublishStore _fileStore;
protected MemoryPublishStore _memoryStore;
// Maximum capacity for in-memory storage
protected int _cap;
// File path for disk storage
protected String _path;
// Threshold for swapping data to disk
protected int _lowWatermark = 0;
// Flag to control swapping behavior
private boolean _holdSwapping = false;
// Lock for synchronization
private final Lock _lock = new ReentrantLock();
// Condition variable for signaling when swapping occurs
private final Condition _swapping = _lock.newCondition();
// Inner class for replaying messages during swapping
private static class SwappingOutReplayer implements Store.StoreReplayer {
HybridPublishStore _store;
int _entries, _errorCount;
long _lastIndex;
StoreException _exception = null;
private SwappingOutReplayer(HybridPublishStore store_, int entries_)
{
_store = store_;
_entries = entries_;
}
public int getErrors() throws StoreException
{
if (_exception != null) {
throw _exception;
}
return _errorCount;
}
public long lastIndex() {
return _lastIndex;
}
public void execute(Message message)
{
if(_entries > 0 && _errorCount == 0)
{
try {
// Sequence numbers are assigned by memory store
_store._fileStore.store(message, false);
_lastIndex = message.getSequence();
} catch (StoreException e) {
++_errorCount;
_exception = e;
}
--_entries;
}
}
}
/**
* Constructor for the HybridPublishStore.
*
* @param path The path for the disk-based storage.
* @param cap The maximum number of messages that can be stored in memory. It
* is an integer that defines how many messages the in-memory storage
* component of the HybridPublishStore can hold before swapping excess
* data out to disk.
* @throws StoreException Thrown when an operation fails with details of the
* failure.
*/
public HybridPublishStore(String path, int cap) throws StoreException {
// Initialize the file store with the provided path
_fileStore = new PublishStore(path);
// Need to create with room for 1 extra to hold metadata
_memoryStore = new MemoryPublishStore(cap+1);
_cap = cap;
_lowWatermark = (int) (_cap * 0.5);
// Store the provided path
_path = path;
// Synchronize the last persisted values if file store is empty
if (_fileStore.getLastPersisted() % 1000000 == 0) {
_fileStore.discardUpTo(_memoryStore.getLastPersisted());
}
}
/**
* Sets the low watermark; once we start swapping out to disk, we
* keep going until the number of entries in memory is lower than this.
* This method allows changing the low watermark value while ensuring it
* doesn't exceed the maximum capacity.
*
* @param lowWatermark_ The number of entries to serve as a low watermark.
*/
public void setLowWatermark(int lowWatermark_) {
_lock.lock();
try {
// Update the low watermark if it's less than the maximum capacity
if (lowWatermark_ < _cap) {
_lowWatermark = lowWatermark_;
}
} finally {
_lock.unlock();
}
}
/**
* Returns the lowest sequence number currently in the store.
*/
public long getLowestUnpersisted() {
// Get the lowest unpersisted sequence number from the disk store
long count = _fileStore.getLowestUnpersisted();
// If no unpersisted sequence number is found in the disk store,
// get it from the memory store
if (count == -1)
return _memoryStore.getLowestUnpersisted();
// Return the lowest unpersisted sequence number found
return count;
}
/**
* Discards data from both the disk store and memory store up to the specified
* index.
*
* @see com.crankuptheamps.client.Store#discardUpTo(long)
*
* @param index The index up to which data should be discarded.
*
* @throws StoreException Thrown when an operation fails with details of the
* failure.
*/
public void discardUpTo(long index) throws StoreException {
_lock.lock();
try {
// Wait while swapping is in progress to ensure consistency
while (_holdSwapping) {
try {
_swapping.await(10, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
}
// Set a flag to prevent further swapping while discarding
_holdSwapping = true;
} finally {
_lock.unlock();
}
try {
// If the specified index is greater than or equal to the lowest unpersisted
// index in memory, discard data from the memory store
if (index >= _memoryStore.getLowestUnpersisted())
_memoryStore.discardUpTo(index);
// Discard data from the disk store up to the specified index
_fileStore.discardUpTo(index);
} finally {
// Signal that the discarding process is complete
_signalLock();
}
}
/**
* Private method to signal that swapping or discarding is complete.
*/
private void _signalLock() {
_lock.lock();
try {
// Reset the swapping flag and signal waiting threads
_holdSwapping = false;
_swapping.signalAll();
} finally {
_lock.unlock();
}
}
/**
* Replays messages using the provided replayer, ensuring no swapping occurs
* during the replay process.
*
* @see com.crankuptheamps.client.Store#replay(com.crankuptheamps.client.Store.StoreReplayer)
*
* @param replayer The replayer used to replay messages.
* @throws StoreException Thrown when an operation fails with details of
* the failure.
* @throws DisconnectedException Thrown when the store is disconnected.
*/
public void replay(StoreReplayer replayer) throws StoreException, DisconnectedException {
// Have to make sure that no swapping happens or messages could get moved
// from memory to file between file replay and memory replay
_lock.lock();
try {
// Wait while swapping is in progress to ensure consistency
while (_holdSwapping) {
try {
_swapping.await(10, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
}
// Set a flag to prevent further swapping during replay
_holdSwapping = true;
} finally {
_lock.unlock();
}
try {
// First replay everything on disk, then replay everything in memory.
_fileStore.replay(replayer);
_memoryStore.replay(replayer);
} finally {
// Signal that the replay process is complete
_signalLock();
}
}
/**
* Replays a single message using the provided replayer and index.
*
* @see com.crankuptheamps.client.Store#replaySingle(com.crankuptheamps.client.Store.StoreReplayer,
* long)
* @param replayer The replayer used to replay the message.
* @param index The index of the message to replay.
* @return true if the message was successfully replayed, false otherwise.
* @throws StoreException Thrown when an operation fails with details of
* the failure.
* @throws DisconnectedException Thrown when the store is disconnected.
*/
public boolean replaySingle(StoreReplayer replayer, long index)
throws StoreException, DisconnectedException {
// Determine whether to replay from memory or disk based on the index
if (index < _memoryStore.getLowestUnpersisted()) {
return _fileStore.replaySingle(replayer, index);
} else {
return _memoryStore.replaySingle(replayer, index);
}
}
/**
* Gets the count of unpersisted messages in both the disk store and memory
* store
*
* @see com.crankuptheamps.client.Store#unpersistedCount()
*
* @return The total count of unpersisted messages.
*/
public long unpersistedCount() {
// Sum the counts of unpersisted messages from both stores
return _fileStore.unpersistedCount() + _memoryStore.unpersistedCount();
}
/**
* Stores a message in the hybrid store, swapping to disk if memory usage
* exceeds capacity.
*
* @param message The message to store.
* @throws StoreException Thrown when an operation fails with details of the
* failure.
*/
public void store(Message message) throws StoreException {
SwappingOutReplayer replayer = null;
_lock.lock();
try {
// Wait while swapping is in progress to ensure consistency
while (_holdSwapping) {
try {
_swapping.await(10, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
}
long memUnpersistedCount = _memoryStore.unpersistedCount();
if (memUnpersistedCount >= _cap) {
_holdSwapping = true;
// Swap some messages from memory to disk
replayer = new SwappingOutReplayer(this, (int) memUnpersistedCount - _lowWatermark);
}
} finally {
_lock.unlock();
}
try {
if (replayer != null) {
_memoryStore.replay(replayer);
// If there are no errors during replay, discard the replayed messages from
// memory
if (replayer.getErrors() == 0) {
_memoryStore.discardUpTo(replayer.lastIndex());
}
}
// Store the incoming message in memory
_memoryStore.store(message);
} catch (DisconnectedException e) {
// This "cannot" happen, as our replayer does not throw DisconnectedException
assert false;
} finally {
_lock.lock();
if (replayer != null)
_holdSwapping = false;
_lock.unlock();
}
}
/**
* Sets a message in both the memory store and the disk store.
*
* @param m The message to set.
*/
public void setMessage(Message m) {
_memoryStore.setMessage(m);
_fileStore.setMessage(m.copy());
}
/**
* Gets the last persisted sequence number from the file store.
* The file store value is always considered correct.
*
* @return The last persisted sequence number.
* @throws StoreException Thrown when an operation fails with details of the
* failure.
*/
public long getLastPersisted() throws StoreException {
// File store value will always be correct. It is updated on every
// call to discardUpTo and initialized with the same values as the
// memory store. The memory store value could be too high because
// it has discardUpTo called after swapping to file store.
return _fileStore.getLastPersisted();
}
/**
* Flushes the memory store and, if necessary, the file store.
*
* @throws TimedOutException Thrown if the flush operation times out.
*/
public void flush() throws TimedOutException {
// Flush memory first
_memoryStore.flush();
// Flush file store in case it needs flushing after swapping
_fileStore.flush();
}
/**
* Flushes the memory store and, if necessary, the file store with a specified
* timeout.
*
* @param timeout The maximum time to wait for the flush operation to complete.
* @throws TimedOutException Thrown if the flush operation times out.
*/
public void flush(long timeout) throws TimedOutException {
long start = System.currentTimeMillis();
// Flush memory store first
_memoryStore.flush(timeout);
// Calculate remaining time for file store flushing, if needed
long remaining = timeout - (System.currentTimeMillis() - start);
if (remaining > 0)
_fileStore.flush(remaining);
else
throw new TimedOutException("Timed out waiting to flush publish store.");
}
/**
* Sets a resize handler for both the file store and the memory store.
*
* @param handler The resize handler to set.
*/
public void setResizeHandler(PublishStoreResizeHandler handler) {
_fileStore.setResizeHandler(handler);
_memoryStore.setResizeHandler(handler);
}
/**
* Closes both the memory store and the file store.
*
* @throws Exception Thrown if an error occurs during closure.
*/
public void close() throws Exception {
_memoryStore.close();
_fileStore.close();
}
/**
* Get whether the Store will throw a PublishGapException from discardUpTo if the
* sequence number being discarded is less then the current last persisted.
* @return If true, an exception will be thrown during logon if a gap could be
* created. If false, logon is allowed to proceed.
*/
public boolean getErrorOnPublishGap()
{
return _fileStore.getErrorOnPublishGap();
}
/**
* Set whether the Store should throw a PublishGapException from discardUpTo if the
* sequence number being discarded is less then the current last persisted. This can
* occur if the client fails over to a server that has not been synchronously
* replicated to by its previous server, which lead to the new server having a gap
* in messages from this client.
* @param errorOnGap If true, an exception will be thrown during logon if a gap
* could be created. If false, allow the logon to proceed.
*/
public void setErrorOnPublishGap(boolean errorOnGap)
{
// Don't set this on the memory store, as it always has only the most recent items
// _memoryStore.setErrorOnPublishGap(errorOnGap);
_fileStore.setErrorOnPublishGap(errorOnGap);
}
}