com.crankuptheamps.client.HybridPublishStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amps-client Show documentation
Show all versions of amps-client Show documentation
AMPS Java client by 60East Technologies, Inc.
////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2021 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
{
protected PublishStore _fileStore;
protected MemoryPublishStore _memoryStore;
protected int _cap;
protected String _path;
protected int _lowWatermark = 0;
private boolean _holdSwapping = false;
private final Lock _lock = new ReentrantLock();
private final Condition _swapping = _lock.newCondition();
private static class SwappingOutReplayer implements Store.StoreReplayer
{
HybridPublishStore _store;
int _entries, _errorCount;
long _lastIndex;
private SwappingOutReplayer(HybridPublishStore store_, int entries_)
{
_store = store_;
_entries = entries_;
}
public int getErrors()
{
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;
}
--_entries;
}
}
}
public HybridPublishStore(String path, int cap) throws StoreException
{
_fileStore = new PublishStore(path);
// Need to create with room for 1 extra to hold metadata
_memoryStore = new MemoryPublishStore(cap+1);
// Synchronize the last persisted values if file store is empty
if (_fileStore.getLastPersisted() % 1000000 == 0)
_fileStore.discardUpTo(_memoryStore.getLastPersisted());
_cap = cap;
_lowWatermark = (int)(_cap * 0.5);
_path = path;
}
/**
* 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.
* @param lowWatermark_ The number of entries to serve as a low watermark.
*/
public void setLowWatermark(int lowWatermark_)
{
_lock.lock();
try {
if(lowWatermark_ < _cap)
{
_lowWatermark = lowWatermark_;
}
}
finally {
_lock.unlock();
}
}
/** Returns the lowest sequence number currently in the store.
*/
public long getLowestUnpersisted()
{
long count = _fileStore.getLowestUnpersisted();
if (count == -1)
return _memoryStore.getLowestUnpersisted();
return count;
}
/** Discards from both the disk store and memory store.
* @see com.crankuptheamps.client.Store#discardUpTo(long)
*/
public void discardUpTo(long index) throws StoreException
{
_lock.lock();
try {
while (_holdSwapping)
{
try { _swapping.await(10, TimeUnit.MILLISECONDS); }
catch(InterruptedException e){}
}
_holdSwapping = true;
}
finally {
_lock.unlock();
}
try
{
if (index >= _memoryStore.getLowestUnpersisted())
_memoryStore.discardUpTo(index);
_fileStore.discardUpTo(index);
}
finally
{
_signalLock();
}
}
private void _signalLock()
{
_lock.lock();
try {
_holdSwapping = false;
_swapping.signalAll();
}
finally {
_lock.unlock();
}
}
/**
* @see com.crankuptheamps.client.Store#replay(com.crankuptheamps.client.Store.StoreReplayer)
*/
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 {
while (_holdSwapping)
{
try { _swapping.await(10, TimeUnit.MILLISECONDS); }
catch(InterruptedException e){}
}
_holdSwapping = true;
}
finally {
_lock.unlock();
}
try
{
// First replay everything on disk, then replay everything in memory.
_fileStore.replay(replayer);
_memoryStore.replay(replayer);
}
finally
{
_signalLock();
}
}
/**
* @see com.crankuptheamps.client.Store#replaySingle(com.crankuptheamps.client.Store.StoreReplayer, long)
*/
public boolean replaySingle(StoreReplayer replayer, long index)
throws StoreException, DisconnectedException
{
if(index < _memoryStore.getLowestUnpersisted())
{
return _fileStore.replaySingle(replayer, index);
}
else
{
return _memoryStore.replaySingle(replayer, index);
}
}
/**
* @see com.crankuptheamps.client.Store#unpersistedCount()
*/
public long unpersistedCount()
{
return _fileStore.unpersistedCount() + _memoryStore.unpersistedCount();
}
public void store(Message message) throws StoreException
{
SwappingOutReplayer replayer = null;
_lock.lock();
try {
while (_holdSwapping)
{
try { _swapping.await(10, TimeUnit.MILLISECONDS); }
catch(InterruptedException e){}
}
long memUnpersistedCount = _memoryStore.unpersistedCount();
if(memUnpersistedCount >= _cap)
{
_holdSwapping = true;
// ok, swap some out to disk now.
replayer = new SwappingOutReplayer(this, (int)memUnpersistedCount - _lowWatermark);
}
}
finally {
_lock.unlock();
}
try
{
if (replayer != null)
{
_memoryStore.replay(replayer);
if(replayer.getErrors() == 0)
{
_memoryStore.discardUpTo(replayer.lastIndex());
}
}
_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();
}
}
public void setMessage(Message m)
{
_memoryStore.setMessage(m);
_fileStore.setMessage(m.copy());
}
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();
}
public void flush() throws TimedOutException
{
// Flush memory first
_memoryStore.flush();
// In case flush only passed from swapping to file, flush file
_fileStore.flush();
}
public void flush(long timeout) throws TimedOutException
{
long start = System.currentTimeMillis();
// Flush memory first
_memoryStore.flush(timeout);
// In case flush only passed from swapping to file, flush file
long remaining = timeout - (System.currentTimeMillis() - start);
if (remaining > 0) _fileStore.flush(remaining);
else throw new TimedOutException("Timed out waiting to flush publish store.");
}
public void setResizeHandler(PublishStoreResizeHandler handler)
{
_fileStore.setResizeHandler(handler);
_memoryStore.setResizeHandler(handler);
}
public void close() throws Exception
{
_memoryStore.close();
_fileStore.close();
}
}