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

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

////////////////////////////////////////////////////////////////////////////
//
// 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.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.crankuptheamps.client.exception.AMPSException;
import com.crankuptheamps.client.exception.CommandException;
import com.crankuptheamps.client.fields.BookmarkField;
import com.crankuptheamps.client.fields.BookmarkRangeField;
import com.crankuptheamps.client.fields.Field;

/**
 * RingBookmarkStore is an implementation of BookmarkStore that stores state on
 * disk and in memory. This BookmarkStore implementation is designed for use
 * when messages will be strictly discarded in the order in which they are
 * received. Discarding messages out of order may result in duplicate or missed
 * messages on failover. For stores that support out of order discard, see
 * (links) LoggedBookmarkStore or MemoryBookmarkStore. ( For each subscription,
 * RingBookmarkStore keeps the most recent bookmark B for which all messages
 * prior to B have been discard()ed by the subscriber. As messages are logged
 * and discarded, an in-memory array is used to track when a new bookmark can be
 * logged.
 */
public class RingBookmarkStore implements BookmarkStore {
    // Manages the portion of the store for a single subscription.
    // RingBookmarkStore uses a memory mapped file for persistent storage.
    // Each subscription has four elements: one subId of size BYTES_SUBID,
    // and three bookmarks of size BYTES_BOOKMARK.
    // On disk, each bookmark entry begins with one byte designating the next
    // bookmark entry
    // to be written. The value '*', aka the Cursor, in this position indicates this
    // is the oldest bookmark
    // stored for the subscription, and is the next to be written.
    // Writing always follows a predictable sequence: the '*' for the next entry is
    // written,
    // the bookmark id is written to the current entry, and then the cursor is
    // erased for the current entry.
    // On recovery, the entry *before* the earliest one with the '*' is the most
    // recently
    // written: in case of crash during write, there may be two entries with a '*',
    // and the third one is the one to recover.

    // In memory, each Subscription has an array of entry's. Each entry holds a
    // bookmark and whether or not it has been discard()ed.
    // Whenever the 0th entry in the array is discarded, we know it's time to write
    // a bookmark to disk.
    // We truncate the front of the array up through the last one that is inactive,
    // and then write the next one to disk.

    /**
     * The Subscription object is used to represent internal bookmark state
     * for the messages received and discarded on a specific subscription
     * within the bookmark store.
     */
    protected static class Subscription implements com.crankuptheamps.client.Subscription {
        // The buffer holding the memory-mapped file of all subscriptions
        ByteBuffer _buffer;
        // The offset into _buffer where our subscription begins.
        int _offset;
        // The disk entry to be written next.
        short _currentDiskPosition = 0;

        BookmarkRingBuffer _ring = new BookmarkRingBuffer();

        // On-disk bytes for a bookmark entry.
        static final int BYTES_BOOKMARK = BookmarkField.MAX_BOOKMARK_LENGTH * 6 + 8;
        // On-disk bytes for each entry
        static final int BYTES_ENTRY = 1024;
        // Number of bookmarks stored per subscription. Must be at least 3 for recovery
        // guarantee.
        static final short POSITIONS = 3;
        // On-disk bytes for the subscription ID.
        static final int BYTES_SUBID = BYTES_ENTRY - (BYTES_BOOKMARK * POSITIONS);
        private BookmarkRangeField _range = new BookmarkRangeField();

        // The Subscription lock
        final Lock _lock = new ReentrantLock();

        public Subscription() {
        }

        public void init(ByteBuffer buffer, int offset) throws AMPSException {
            _buffer = buffer;
            _offset = offset;

            recover();
        }

        // A new bookmark has arrived for this subscription. Just remember it:
        // nothing to write about until the message is discarded.
        public long log(BookmarkField bookmark) throws CommandException {
            _lock.lock();
            try {
                if (!bookmark.isRange()) {
                    long seq = _ring.log(bookmark);
                    while (seq == 0) {
                        _lock.unlock();
                        try {
                            _ring.checkResize();
                        } finally {
                            _lock.lock();
                        }
                        seq = _ring.log(bookmark);
                    }
                    return seq;
                } else {
                    _range.copyFrom(bookmark);
                    if (!_range.isValid()) {
                        _range.reset();
                        throw new CommandException("Invalid bookmark range specified");
                    }
                    long seq = 0;
                    // Only exclusive start does log/discard
                    if (_range.isStartExclusive()) {
                        // Log/discard in ring directly
                        seq = _ring.log(_range.getStart());
                        _ring.discard(seq);
                    }
                    // Write the range in case we disconnect with no messages
                    write(_range);
                    return seq;
                }
            } finally {
                _lock.unlock();
            }
        }

        // This is never called but required by the interface
        public boolean isDiscarded(BookmarkField bookmark) {
            return false;
        }

        // User is finished with a bookmark. Mark the entry as inactive,
        // and if it's the 0th entry, forward-truncate and log the most recent.
        public void discard(long bookmark) {
            _lock.lock();
            try {
                // If discard changes most recent, save updated recent
                if (_ring.discard(bookmark)) {
                    // If we have a range, update that
                    if (_range.isValid()) {
                        Field recent = _ring.getLastDiscarded();
                        if (recent.length > 1
                                && (_range.isStartInclusive()
                                        || !recent.equals(_range.getStart()))) {
                            _range.replaceStart(recent, true);
                            write(_range);
                        }
                    } else {
                        write(_ring.getLastDiscarded());
                    }
                }
            } finally {
                _lock.unlock();
            }
        }

        public BookmarkRangeField getRange() {
            _lock.lock();
            try {
                return _range.copy();
            } finally {
                _lock.unlock();
            }
        }

        public Field getMostRecent() {
            _lock.lock();
            try {
                return _ring.getLastDiscarded();
            } finally {
                _lock.unlock();
            }
        }

        public Field getMostRecentList(boolean useList) {
            _lock.lock();
            try {
                if (_range.isValid()) {
                    return _range;
                } else {
                    return _ring.getLastDiscarded();
                }
            } finally {
                _lock.unlock();
            }
        }

        private void write(Field bookmark) {
            _lock.lock();
            try {
                // We want to write to _currentDiskPosition.
                short nextDiskPosition = (short) ((_currentDiskPosition + 1) % POSITIONS);

                // Mark the next position with the 'cursor'

                _buffer.put(_offset + BYTES_SUBID + (BYTES_BOOKMARK * nextDiskPosition), (byte) '*');

                // write the current position and validate it
                _buffer.position(_offset + BYTES_SUBID + (BYTES_BOOKMARK * _currentDiskPosition) + 1);
                for (int i = 0; i < bookmark.length; i++) {
                    _buffer.put(bookmark.byteAt(i));
                }
                for (int i = 0; i < BYTES_BOOKMARK - (bookmark.length + 2); i++) {
                    _buffer.put((byte) 0);
                }
                _buffer.put(_offset + BYTES_SUBID + (BYTES_BOOKMARK * _currentDiskPosition), (byte) '+');

                // advance _currentDiskPosition
                _currentDiskPosition = nextDiskPosition;
            } finally {
                _lock.unlock();
            }
        }

        private void recover() throws AMPSException {
            // find the first cursor
            short foundCursor = 0;
            for (; foundCursor < POSITIONS; foundCursor++) {
                byte b = _buffer.get(_offset + BYTES_SUBID + (BYTES_BOOKMARK * foundCursor));
                if (b == (byte) '*')
                    break;
            }
            if (foundCursor == 0) {
                byte b = _buffer.get(_offset + BYTES_SUBID + (BYTES_BOOKMARK * (POSITIONS - 1)));
                if (b == (byte) '*') {
                    foundCursor = POSITIONS - 1;
                }
            }

            if (foundCursor < POSITIONS) {
                // Found an existing "cursor": start the writing there.
                _currentDiskPosition = foundCursor;
                int mostRecentValid = _currentDiskPosition == 0 ? POSITIONS - 1 : _currentDiskPosition - 1;
                byte[] buf = new byte[BYTES_BOOKMARK - 1];
                _buffer.position(_offset + BYTES_SUBID + (BYTES_BOOKMARK * mostRecentValid) + 1);
                _buffer.get(buf);

                int bookmarkLength = 0;
                for (; bookmarkLength < buf.length && buf[bookmarkLength] != 0; bookmarkLength++)
                    ;

                try {
                    BookmarkField f = new BookmarkField();
                    f.set(buf, 0, bookmarkLength);
                    if (f.isRange()) {
                        log(f);
                    } else {
                        // discard and log to make this the
                        // "starting point" for the subscription.
                        _ring.discard(_ring.log(f));
                    }
                } catch (Exception e) {
                    throw new AMPSException("Error while recovering.", e);
                }
            } else {
                _currentDiskPosition = 0;
            }
        }

        @Deprecated
        public void setLastPersisted(long bookmark) {
            // no-op
        }

        public void setLastPersisted(BookmarkField bookmark) {
            // no-op
        }

        public long getOldestBookmarkSeq() {
            return _ring.getStartIndex();
        }

        public void setResizeHandler(BookmarkStoreResizeHandler handler, BookmarkStore store) {
            _ring.setResizeHandler(handler, store);
        }
    }

    // A memory-mapped buffer used for in-memory data storage.
    MappedByteBuffer _buffer;
    static final int ENTRIES = 16384;
    // This constant defines the size of each entry in the store. It is computed
    // based on the subscription ID size and the size of multiple bookmarks from
    // the `Subscription` class.
    static final int ENTRY_SIZE = Subscription.BYTES_SUBID + (Subscription.POSITIONS * Subscription.BYTES_BOOKMARK);
    // A hash map used to associate fields with subscription objects
    HashMap _map;
    // An integer that tracks the number of free entries in the log. It is
    // initialized to `ENTRIES` to ensure that there is no free space in the log
    // until the `recover()` method succeeds.
    int _free = ENTRIES;
    // A random access file that facilitates reading from and writing to a file on
    // disk
    RandomAccessFile _file;
    FileChannel _channel;
    // A string containing the file path where data is stored.
    String _path;
    // An optional handler that can be used to manage resizing of the bookmark
    // store.
    BookmarkStoreResizeHandler _resizeHandler = null;
    // A private attribute that holds the server version. Its default value is set
    // to `Message.MINIMUM_SERVER_VERSION`.
    private int _serverVersion = Message.MINIMUM_SERVER_VERSION;
    // The Store lock
    // A lock object used to synchronize access to the `RingBookmarkStore` instance,
    // ensuring thread safety.
    final Lock _lock = new ReentrantLock();

    // A pool used to manage subscription objects. The pool is designed to reduce
    // memory overhead by allowing the reuse of subscription instances.
    Pool _pool;

    /**
     * Initializes a `RingBookmarkStore` object with the provided file path. It
     * internally calls another constructor with a default target number of
     * subscriptions (`targetNumberOfSubscriptions` set to 1).
     *
     * @param path The path to the file.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public RingBookmarkStore(String path) throws AMPSException {
        this(path, 1);
    }

    /**
     * Initializes a `RingBookmarkStore` object with a specified file path and a
     * target number of subscriptions. It creates a random access file, initializes
     * the subscription pool (`_pool`), and calls the `init()` method to set up
     * the object. If any I/O errors occur during initialization, an `AMPSException`
     * is thrown with details of the error.
     *
     * @param path                        The path to the file.
     * @param targetNumberOfSubscriptions The target number of subscriptions.
     * @throws AMPSException Thrown when an operation on the store fails.
     *                       The exception will contain details of the failure.
     */
    public RingBookmarkStore(String path, int targetNumberOfSubscriptions) throws AMPSException {
        try {
            _path = path;
            _file = new RandomAccessFile(_path, "rw");
            _pool = new Pool(Subscription.class, targetNumberOfSubscriptions);
        } catch (IOException ioex) {
            throw new AMPSException("I/O Error initializing file " + path, ioex);
        }
        init();
    }

    /**
     * This method is called internally by the AMPS Client to log a bookmark to
     * the persistent log. It takes an `AMPS Message` as an argument and returns
     * a bookmark to be logged. If a subscription is not associated with the
     * message, it attempts to find or create one using the message's subscription
     * ID. The method logs the bookmark and assigns a sequence number to it. If any
     * operation on the store fails, it throws an `AMPSException` with details
     * of the failure.
     *
     * @param message The AMPS Message.
     * @return Returns bookmark to be logged.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public long log(Message message) throws AMPSException {
        BookmarkField bookmark = (BookmarkField) message.getBookmarkRaw();
        Subscription sub = (RingBookmarkStore.Subscription) message.getSubscription();
        if (sub == null) {
            Field subId = message.getSubIdRaw();
            if (subId == null || subId.isNull())
                subId = message.getSubIdsRaw();
            sub = find(subId);
            message.setSubscription(sub);
        }
        long seqNo = sub.log(bookmark);
        message.setBookmarkSeqNo(seqNo);
        return seqNo;
    }

    /**
     * Marks the message specified by the Subscription ID and bookmark sequence
     * number as discarded, indicating that the application has completed processing
     * the message.
     * Marking a message as discarded means that the message will not be replayed
     * when the subscription resumes. This method is intended for internal use by
     * the AMPS Client to manage message processing. Application code typically does
     * not need to call this method directly.
     *
     * @param subId         The subscription ID of the message.
     * @param bookmarkSeqNo The bookmark sequence number.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public void discard(Field subId, long bookmarkSeqNo) throws AMPSException {
        find(subId).discard(bookmarkSeqNo);
    }

    /**
     * Call this when you want to mark the provided message as discarded, indicating
     * that the application has completed processing the message. Marking a message
     * as discarded means that the message will not be replayed when the
     * subscription resumes.
     * 
     * @param message Message to be marked as discarded.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public void discard(Message message) throws AMPSException {
        long bookmark = message.getBookmarkSeqNo();
        Subscription sub = (RingBookmarkStore.Subscription) message.getSubscription();
        if (sub == null) {
            Field subId = message.getSubIdRaw();
            if (subId == null || subId.isNull())
                subId = message.getSubIdsRaw();
            sub = find(subId);
            message.setSubscription(sub);
        }
        sub.discard(bookmark);
    }

    /**
     * Call this when you want to return to the most recent bookmark from the log
     * that should be used for (re-)subscriptions based on the provided subscription
     * ID.
     * 
     * @param subId Subscription Id
     * @return Returns most recent bookmark.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public Field getMostRecent(Field subId) throws AMPSException {
        return find(subId).getMostRecentList(true).copy();
    }

    /**
     * Call this when you want to return to the most recent bookmark from the log
     * that should be used for (re-)subscriptions based on the provided subscription
     * ID.
     * 
     * @param subId   Subscription Id
     * @param useList Ignored by this type of store.
     * @return Returns most recent bookmark.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public Field getMostRecent(Field subId, boolean useList) throws AMPSException {
        return find(subId).getMostRecentList(useList).copy();
    }

    /**
     * Called for each arriving message to determine if the application has already
     * processed and discarded the message. Generally isDiscarded is called by the
     * Client however, if needed it can be called by the application as well.
     * 
     * @param message Message used to determine if the application has already
     * @return Returns 'true' if the bookmark is in the log and marked as discarded.
     *         Otherwise, returns 'false'.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public boolean isDiscarded(Message message) throws AMPSException {
        BookmarkField bookmark = (BookmarkField) message.getBookmarkRaw();
        Subscription sub = (RingBookmarkStore.Subscription) message.getSubscription();
        if (sub == null) {
            Field subId = message.getSubIdRaw();
            if (subId == null || subId.isNull())
                subId = message.getSubIdsRaw();
            sub = find(subId);
            message.setSubscription(sub);
        }
        return sub.isDiscarded(bookmark);
    }

    // Recovers subscription information from the persistent store during
    // initialization. This method iterates through the entries in the store,
    // checks if an entry is active, and initializes a `Subscription` object for
    // each active entry. The `Subscription` objects are added to a map for future
    // reference.
    private void recover() throws AMPSException {
        int currentEntry = 0;
        for (; currentEntry < ENTRIES; currentEntry++) {
            // Check if the entry is active
            byte firstByte = _buffer.get(currentEntry * ENTRY_SIZE);
            if (firstByte != 0) {
                // Read subscription information
                byte[] id = new byte[Subscription.BYTES_SUBID - 1];
                _buffer.position(currentEntry * ENTRY_SIZE);
                _buffer.get(id);
                int idLength = 0;
                for (; idLength < id.length && id[idLength] != 0; idLength++)
                    ;

                // Create and initialize a Subscription object
                Field f = new Field(id, 0, idLength);
                Subscription subscription = _pool.get();
                subscription.init(_buffer, currentEntry * ENTRY_SIZE);
                try {
                    _map.put(f, subscription);
                } catch (Exception e) {
                    throw new AMPSException("Bookmark store corrupted.", e);
                }
                subscription.recover();
            } else
                break;
        }
        if (currentEntry == ENTRIES) {
            // Handle case where no space is available. todo: resize here.
            throw new AMPSException("Unable to allocate space in this bookmark store.");
        }
        _free = currentEntry;
    }

    /**
     * Finds and returns the `Subscription` object associated with the specified
     * subscription ID (`subId`). If the `subId` exists in the store, it returns
     * the existing `Subscription` object. If the `subId` is not found, it checks
     * if there is space available to create a new `Subscription` object and adds
     * it to the store.
     *
     * @param subId The subscription ID for which to find or create a Subscription.
     * @return The Subscription associated with the specified subId.
     * @throws AMPSException Thrown when there is an error in the bookmark store or
     *                       when there is insufficient space to create a new
     *                       Subscription.
     */
    protected Subscription find(Field subId) throws AMPSException {
        _lock.lock();
        try {
            if (_map.containsKey(subId)) {
                return _map.get(subId);
            }

            if (_free >= ENTRIES) {
                throw new AMPSException("Unable to allocate space in this bookmark store.");
            }
            int pos = _free++;

            Subscription subscription = _pool.get();
            subscription.init(_buffer, pos * ENTRY_SIZE);
            subscription.setResizeHandler(_resizeHandler, this);
            _map.put(subId.copy(), subscription);
            _buffer.position(pos * ENTRY_SIZE);
            for (int i = 0; i < subId.length; i++) {
                _buffer.put(subId.buffer[subId.position + i]);
            }
            return subscription;
        } finally {
            _lock.unlock();
        }
    }

    /**
     * Called internally by the Client to mark the message as safely persisted by
     * AMPS to all of its sync replication destinations. On this store, this method
     * has no effect.
     * 
     * @param subId    The subscription ID for the message.
     * @param bookmark The bookmark containing the message.
     * @throws AMPSException Not thrown by this implementation.
     */
    public void persisted(Field subId, BookmarkField bookmark) throws AMPSException {
        // no-op
    }

    /**
     * Old style of setting a persisted bookmark no longer used.
     * destinations. On this store, this method has no effect.
     * 
     * @param subId    The subscription ID for the message.
     * @param bookmark The bookmark number.
     * @throws AMPSException Not thrown by this implementation.
     * @deprecated use {@link #persisted(Field, BookmarkField)} instead.
     */
    @Deprecated
    public void persisted(Field subId, long bookmark) throws AMPSException {
        // no-op
    }

    /**
     * Call this when you want to retrieve the sequence number for the oldest
     * bookmark in the store.
     * 
     * @param subId The subscription ID for the oldest bookmark in the store.
     * @return Returns the oldest bookmark sequence number in the store.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public long getOldestBookmarkSeq(Field subId) throws AMPSException {
        return find(subId).getOldestBookmarkSeq();
    }

    /**
     * Call this when you want to set a resize handler that is invoked when the
     * store needs to resize.
     * 
     * @param handler The handler to invoke for the resize.
     */
    public void setResizeHandler(BookmarkStoreResizeHandler handler) {
        _resizeHandler = handler;
        Iterator it = _map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pairs = (Map.Entry) it.next();
            ((Subscription) pairs.getValue()).setResizeHandler(handler, this);
        }
    }

    /**
     * Call this when you want to purge the contents of this store. Removes any
     * tracking history associated with publishers and received messages, and may
     * delete or truncate on-disk representations as well.
     * 
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public void purge() throws AMPSException {
        _lock.lock();
        try {
            // This is the quickest way I've found, to zero out the file.
            int longs = _buffer.capacity() / 8;
            _buffer.position(0);
            for (int i = 0; i < longs; i++) {
                _buffer.putLong(0);
            }
            _buffer.position(0);

            _buffer.force();
            _map = new HashMap();
            _free = 0;
            recover();
        } finally {
            _lock.unlock();
        }
    }

    /**
     * Call this when you want to purge the contents of this store for a given
     * Subscription Id. Removes any tracking history associated with publishers
     * and received messages, and may delete or truncate on-disk representations as
     * well.
     * 
     * @param subId_ The identifier of the subscription to purge.
     * @throws AMPSException Thrown when an operation on the store fails. The
     *                       exception will contain details of the failure.
     */
    public void purge(Field subId_) throws AMPSException {
        _lock.lock();
        try {
            if (!_map.containsKey(subId_))
                return;
            Subscription sub = _map.get(subId_);
            int pos = sub._offset / ENTRY_SIZE;
            // NULL out the subs data
            byte[] buf = new byte[ENTRY_SIZE];
            _buffer.position(sub._offset);
            _buffer.put(buf);
            // Move all subs back one to fill the hole
            --_free;
            Field subId = new Field();
            for (int j = pos; j < _free; ++j) {
                int i = j * ENTRY_SIZE;
                // Read the next entry
                _buffer.position((j + 1) * ENTRY_SIZE);
                _buffer.get(buf);
                // Write it to current location
                _buffer.position(i);
                _buffer.put(buf);
                int idLength = 0;
                for (; idLength < Subscription.BYTES_SUBID && buf[idLength] != 0; idLength++)
                    ;
                subId.set(buf, 0, idLength);
                _map.get(subId)._offset = i;
            }
            // NULL out the end and reset the end
            int i = _free * ENTRY_SIZE;
            _buffer.position(i);
            for (; i < (_free + 1) * ENTRY_SIZE; ++i) {
                _buffer.put((byte) 0);
            }

            _map.remove(subId_);
        } finally {
            _lock.unlock();
        }
    }

    // Initializes the RingBookmarkStore by mapping a portion of a file into memory
    // for read-write access.
    private void init() throws AMPSException {
        try {
            _channel = _file.getChannel();
            _buffer = _channel.map(FileChannel.MapMode.READ_WRITE, 0, ENTRIES * ENTRY_SIZE);
            _channel.close();
            _file.close();
        } catch (IOException ioex) {
            throw new AMPSException("error opening store.", ioex);
        }

        _map = new HashMap();
        _free = 0;
        recover();
    }

    /**
     * Called internally by the Client when connected to an AMPS server to indicate
     * what version the server is.
     * 
     * @param version Version number
     */
    public void setServerVersion(int version) {
        _serverVersion = version;
    }

    /**
     * Called internally by the Client to return the server version detected upon
     * logon.
     * 
     * @return The server version.
     */
    public int getServerVersion() {
        return _serverVersion;
    }

    /**
     * In order to unmap the memory used to store the state, this method closes the
     * mapped byte buffer.
     * 
     * @throws Exception Thrown when an operation on the store fails. The exception
     *                   will contain details of the failure.
     */
    public void close() throws Exception {
        // NOTE: this is an imprecise mechanism for closing a mapped byte buffer;
        // The JDK does not yet provide a deterministic way to unmap memory, it
        // may be referred to by other objects. We can force a write and gc, but it
        // is likely that the underlying file will still be mapped.
        _buffer.force();
        _buffer = null;
        _map = null;
        // System.gc();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy