All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.crankuptheamps.client.HybridPublishStore Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2022 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();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy