package org.mapdb;

import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

import static org.mapdb.DataIO.*;

/**
 * Extends {@link StoreDirect} with Write Cache
 */
public class StoreCached extends StoreDirect {


    /**
     * stores modified stack pages.
     */
    //TODO only accessed under structural lock, should be LongConcurrentHashMap?
    protected final LongObjectMap dirtyStackPages = new LongObjectMap();
    protected final LongObjectObjectMap[] writeCache;

    protected final static Object TOMBSTONE2 = new Object(){
        @Override
        public String toString() {
            return StoreCached.class.getName()+".TOMBSTONE2";
        }
    };

    protected final int writeQueueSize;
    protected final int writeQueueSizePerSegment;
    protected final boolean flushInThread;

    public StoreCached(
            String fileName,
            Volume.VolumeFactory volumeFactory,
            Cache cache,
            int lockScale,
            int lockingStrategy,
            boolean checksum,
            boolean compress,
            byte[] password,
            boolean readonly,
            boolean snapshotEnable,
            boolean fileLockDisable,
            HeartbeatFileLock fileLockHeartbeat,
            ScheduledExecutorService executor,
            long startSize,
            long sizeIncrement,
            boolean recidReuseDisable,
            long executorScheduledRate,
            final int writeQueueSize) {
        super(fileName, volumeFactory, cache,
                lockScale,
                lockingStrategy,
                checksum, compress, password, readonly, snapshotEnable, fileLockDisable, fileLockHeartbeat,
                executor,startSize, sizeIncrement, recidReuseDisable);

        this.writeQueueSize = writeQueueSize;
        this.writeQueueSizePerSegment = writeQueueSize/lockScale;

        writeCache = new LongObjectObjectMap[this.lockScale];
        for (int i = 0; i < writeCache.length; i++) {
            writeCache[i] = new LongObjectObjectMap();
        }

        flushInThread = this.executor==null &&
                writeQueueSize!=0 &&
                !(this instanceof StoreWAL); //TODO StoreWAL should dump data into WAL

        if(this.executor!=null &&
                !(this instanceof StoreWAL) //TODO async write should work for StoreWAL as well
                ){
            for(int i=0;iwriteQueueSizePerSegment) {
                                flushWriteCacheSegment(seg);
                            }
                        }finally {
                            lock.unlock();
                        }
                    }
                    },
                        (long) (executorScheduledRate*Math.random()),
                        executorScheduledRate,
                        TimeUnit.MILLISECONDS);
            }
        }
    }


    public StoreCached(String fileName) {
        this(fileName,
                fileName==null? CC.DEFAULT_MEMORY_VOLUME_FACTORY : CC.DEFAULT_FILE_VOLUME_FACTORY,
                null,
                CC.DEFAULT_LOCK_SCALE,
                0,
                false, false, null, false, false, false, null,
                null, 0L, 0L, false, 0L, 0);
    }



    @Override
    protected void initHeadVol() {
        if (CC.ASSERT && !structuralLock.isHeldByCurrentThread())
            throw new AssertionError();

        if(this.headVol!=null && !this.headVol.isClosed())
            headVol.close();
        this.headVol = new Volume.SingleByteArrayVol((int) HEAD_END);
        vol.transferInto(0,headVol,0,HEAD_END);
    }


    @Override
    protected void longStackPut(long masterLinkOffset, long value, boolean recursive) {
        if (CC.ASSERT && !structuralLock.isHeldByCurrentThread())
            throw new AssertionError();
        if (CC.ASSERT && (masterLinkOffset <= 0 || masterLinkOffset > PAGE_SIZE || masterLinkOffset % 8 != 0))
            throw new DBException.DataCorruption("wrong master link");

        long masterLinkVal = parity4Get(headVol.getLong(masterLinkOffset));
        long pageOffset = masterLinkVal & MOFFSET;

        if (masterLinkVal == 0L) {
            longStackNewPage(masterLinkOffset, 0L, value);
            return;
        }

        byte[] page = loadLongStackPage(pageOffset, true);

        long currSize = masterLinkVal >>> 48;

        long prevLinkVal = parity4Get(DataIO.getLong(page, 0));
        long pageSize = prevLinkVal >>> 48;
        //is there enough space in current page?
        if (currSize + 8 >= pageSize) {
            //no there is not enough space
            //first zero out rest of the page
            Arrays.fill(page, (int) currSize, (int) pageSize, (byte) 0);
            //allocate new page
            longStackNewPage(masterLinkOffset, pageOffset, value);
            return;
        }

        //there is enough space, so just write new value
        currSize += DataIO.packLongBidi(page, (int) currSize, longParitySet(value));

        //and update master pointer
        headVol.putLong(masterLinkOffset, parity4Set(currSize << 48 | pageOffset));
    }

    @Override
    protected long longStackTake(long masterLinkOffset, boolean recursive) {
        if (CC.ASSERT && !structuralLock.isHeldByCurrentThread())
            throw new AssertionError();
        if (CC.ASSERT && (masterLinkOffset < FREE_RECID_STACK ||
                masterLinkOffset > FREE_RECID_STACK + round16Up(MAX_REC_SIZE) / 2 ||
                masterLinkOffset % 8 != 0))
            throw new DBException.DataCorruption("wrong master link");

        long masterLinkVal = parity4Get(headVol.getLong(masterLinkOffset));
        if (masterLinkVal == 0) {
            return 0;
        }
        long currSize = masterLinkVal >>> 48;
        final long pageOffset = masterLinkVal & MOFFSET;

        byte[] page = loadLongStackPage(pageOffset,true);

        //read packed link from stack
        long ret = DataIO.unpackLongBidiReverse(page, (int) currSize);
        //extract number of read bytes
        long oldCurrSize = currSize;
        currSize -= ret >>> 56;
        //clear bytes occupied by prev value
        Arrays.fill(page, (int) currSize, (int) oldCurrSize, (byte) 0);
        //and finally set return value
        ret = longParityGet(ret & DataIO.PACK_LONG_RESULT_MASK);

        if (CC.ASSERT && currSize < 8)
            throw new DBException.DataCorruption("wrong currSize");

        //is there space left on current page?
        if (currSize > 8) {
            //yes, just update master link
            headVol.putLong(masterLinkOffset, parity4Set(currSize << 48 | pageOffset));
            return ret;
        }

        //there is no space at current page, so delete current page and update master pointer
        long prevPageOffset = parity4Get(DataIO.getLong(page, 0));
        final int currPageSize = (int) (prevPageOffset >>> 48);
        prevPageOffset &= MOFFSET;

        //does previous page exists?
        if (prevPageOffset != 0) {
            //yes previous page exists

            byte[] page2 = loadLongStackPage(prevPageOffset,true);

            //find pointer to end of previous page
            // (data are packed with var size, traverse from end of page, until zeros

            //first read size of current page
            currSize = parity4Get(DataIO.getLong(page2, 0)) >>> 48;

            //now read bytes from end of page, until they are zeros
            while (page2[((int) (currSize - 1))] == 0) {
                currSize--;
            }

            if (CC.ASSERT && currSize < 10)
                throw new DBException.DataCorruption("wrong currSize");
        } else {
            //no prev page does not exist
            currSize = 0;
        }

        //update master link with curr page size and offset
        headVol.putLong(masterLinkOffset, parity4Set(currSize << 48 | prevPageOffset));

        //release old page, size is stored as part of prev page value
        dirtyStackPages.remove(pageOffset);
        freeDataPut(pageOffset, currPageSize);
        //TODO how TX should handle this

        return ret;
    }

    protected byte[] loadLongStackPage(long pageOffset, boolean willBeModified) {
        if (CC.ASSERT && !structuralLock.isHeldByCurrentThread())
            throw new AssertionError();

        byte[] page = dirtyStackPages.get(pageOffset);
        if (page == null) {
            int pageSize = (int) (parity4Get(vol.getLong(pageOffset)) >>> 48);
            page = new byte[pageSize];
            vol.getData(pageOffset, page, 0, pageSize);
            if(willBeModified) {
                dirtyStackPages.put(pageOffset, page);
            }
        }
        return page;
    }


    @Override
    protected long longStackCount(final long masterLinkOffset){
        if(CC.ASSERT && !structuralLock.isHeldByCurrentThread())
            throw new AssertionError();
        if (CC.ASSERT && (masterLinkOffset <= 0 || masterLinkOffset > PAGE_SIZE || masterLinkOffset % 8 != 0))
            throw new DBException.DataCorruption("wrong master link");

        long nextLinkVal = DataIO.parity4Get(
                headVol.getLong(masterLinkOffset));
        long ret = 0;
        while(true){
            int currSize = (int) (nextLinkVal>>>48);
            final long pageOffset = nextLinkVal&MOFFSET;

            if(pageOffset==0)
                break;

            byte[] page = loadLongStackPage(pageOffset, false);

            //work on dirty page
            while ((page[currSize-1] & 0xFF) == 0) {
                currSize--;
            }

            //iterate from end of page until start of page is reached
            while(currSize>8){
                long read = DataIO.unpackLongBidiReverse(page,currSize);
                //extract number of read bytes
                currSize-= read >>>56;
                ret++;
            }

            nextLinkVal = DataIO.parity4Get(
                    DataIO.getLong(page,0));

        }
        return ret;
    }


    @Override
    protected void longStackNewPage(long masterLinkOffset, long prevPageOffset, long value) {
        if (CC.ASSERT && !structuralLock.isHeldByCurrentThread())
            throw new AssertionError();

        long newPageOffset = freeDataTakeSingle((int) CHUNKSIZE);
        byte[] page = new byte[(int) CHUNKSIZE];
//TODO this is new page, so data should be clear, no need to read them, but perhaps check data are really zero, handle EOF
//        vol.getData(newPageOffset, page, 0, page.length);
        dirtyStackPages.put(newPageOffset, page);
        //write size of current chunk with link to prev page
        DataIO.putLong(page, 0, parity4Set((CHUNKSIZE << 48) | prevPageOffset));
        //put value
        long currSize = 8 + DataIO.packLongBidi(page, 8, longParitySet(value));
        //update master pointer
        headVol.putLong(masterLinkOffset, parity4Set((currSize << 48) | newPageOffset));
    }

    @Override
    protected void flush() {
        if (CC.ASSERT && !commitLock.isHeldByCurrentThread())
            throw new AssertionError();

        if (isReadOnly())
            return;
        flushWriteCache();


        structuralLock.lock();
        try {
            //flush modified Long Stack pages
            long[] set = dirtyStackPages.set;
            for(int i=0;i MAX_REC_SIZE)
                    throw new DBException.DataCorruption("wrong length");

                vol.putData(offset, val, 0, val.length);
            }
            dirtyStackPages.clear();
            headVol.putLong(LAST_PHYS_ALLOCATED_DATA_OFFSET,parity3Set(lastAllocatedData));
            //set header checksum
            headVol.putInt(HEAD_CHECKSUM, headChecksum(headVol));
            //and flush head
            byte[] buf = new byte[(int) HEAD_END]; //TODO copy directly
            headVol.getData(0, buf, 0, buf.length);
            vol.putData(0, buf, 0, buf.length);
        } finally {
            structuralLock.unlock();
        }
        vol.sync();
    }

    protected void flushWriteCache() {
        if (CC.ASSERT && !commitLock.isHeldByCurrentThread())
            throw new AssertionError();

        //flush modified records
        for (int i = 0; i < locks.length; i++) {
            Lock lock = locks[i].writeLock();
            lock.lock();
            try {
                flushWriteCacheSegment(i);

            } finally {
                lock.unlock();
            }
        }
    }

    protected void flushWriteCacheSegment(int segment) {
        if (CC.ASSERT)
            assertWriteLocked(segment);

        LongObjectObjectMap writeCache1 = writeCache[segment];
        long[] set = writeCache1.set;
        Object[] values = writeCache1.values;
        for(int i=0;i A get2(long recid, Serializer serializer) {
        LongObjectObjectMap m = writeCache[lockPos(recid)];
        Object cached = m.get1(recid);
        if (cached !=null) {
            if(cached==TOMBSTONE2)
                return null;
            return (A) cached;
        }
        return super.get2(recid, serializer);
    }

    @Override
    protected  void delete2(long recid, Serializer serializer) {
        if (serializer == null)
            throw new NullPointerException();
        int lockPos = lockPos(recid);

        LongObjectObjectMap map = writeCache[lockPos];
        map.put(recid, TOMBSTONE2, null);

        if(flushInThread && map.size>writeQueueSize){
            flushWriteCacheSegment(lockPos);
        }
    }

    @Override
    public  long put(A value, Serializer serializer) {
        if (serializer == null)
            throw new NullPointerException();

        //TODO this causes double locking, merge two methods into single method
        long recid = preallocate();
        update(recid, value, serializer);
        return recid;
    }

    @Override
    public  void update(long recid, A value, Serializer serializer) {
        if (serializer == null)
            throw new NullPointerException();

        int lockPos = lockPos(recid);
        Cache cache = caches==null ? null : caches[lockPos];
        Lock lock = locks[lockPos].writeLock();
        lock.lock();
        try {
            if(cache!=null) {
                cache.put(recid, value);
            }
            LongObjectObjectMap map = writeCache[lockPos];
            map.put(recid, value, serializer);
            if(flushInThread && map.size>writeQueueSizePerSegment){
                flushWriteCacheSegment(lockPos);
            }

        } finally {
            lock.unlock();
        }
    }


    @Override
    public  boolean compareAndSwap(long recid, A expectedOldValue, A newValue, Serializer serializer) {
        if(serializer==null)
            throw new NullPointerException();

        //TODO binary CAS & serialize outside lock
        final int lockPos = lockPos(recid);
        final Lock lock = locks[lockPos].writeLock();
        final Cache cache = caches==null ? null : caches[lockPos];
        LongObjectObjectMap> map = writeCache[lockPos];
        lock.lock();
        try{
            A oldVal = cache==null ? null : (A) cache.get(recid);
            if(oldVal == null) {
                oldVal = get2(recid, serializer);
            }else if(oldVal == Cache.NULL){
                oldVal = null;
            }
            if(oldVal==expectedOldValue || (oldVal!=null && serializer.equals(oldVal,expectedOldValue))){
                if(cache!=null) {
                    cache.put(recid, newValue);
                }
                map.put(recid,newValue,serializer);
                if(flushInThread && map.size>writeQueueSizePerSegment){
                    flushWriteCacheSegment(lockPos);
                }

                return true;
            }
            return false;
        }finally {
            lock.unlock();
        }
    }


}

 

Ads help maintain this website.