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

com.kolibrifx.plovercrest.server.internal.Window Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
 */

package com.kolibrifx.plovercrest.server.internal;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.ArrayList;
import com.kolibrifx.plovercrest.client.PlovercrestException;
import com.kolibrifx.plovercrest.client.TableClosedException;

public final class Window {
    private static class LocalBuffer {
        ByteBuffer buf;

        LocalBuffer(final MappedByteBuffer src) {
            buf = src.duplicate();
        }

        void close() {
            buf = null;
        }
    }

    private final String fileName;
    private final FileChannel channel;
    private final long startOffset;
    private final long windowSize;
    private boolean closed;

    private MappedByteBuffer mappedFile;
    private ThreadLocal threadLocalBuffer;
    private final Object mappedFileLock = new Object();
    private final ArrayList allLocalBuffers;

    Window(final String fileName, final FileChannel channel, final long offset, final long windowSize) {
        if (windowSize < 8) {
            throw new IllegalArgumentException("Window size must be at least 8 bytes, was " + windowSize);
        }
        if (windowSize > Integer.MAX_VALUE) {
            throw new IllegalArgumentException("Window size cannot be larger than Integer.MAX_VALUE, was "
                    + windowSize);
        }
        this.fileName = fileName;
        this.channel = channel;
        this.startOffset = offset;
        this.windowSize = windowSize;
        this.allLocalBuffers = new ArrayList();
        remap();
    }

    public void remap() {
        synchronized (mappedFileLock) {
            if (closed) {
                return;
            }
            try {
                mappedFile = channel.map(MapMode.READ_WRITE, startOffset,
                                         Math.min(channel.size() - startOffset, windowSize));
                allLocalBuffers.clear();
                threadLocalBuffer = new ThreadLocal() {
                    @Override
                    protected LocalBuffer initialValue() {
                        synchronized (mappedFileLock) {
                            if (closed) {
                                throw new TableClosedException(fileName);
                            }
                            final LocalBuffer localBuf = new LocalBuffer(mappedFile);
                            allLocalBuffers.add(localBuf);
                            return localBuf;
                        }
                    }
                };
            } catch (final IOException e) {
                throw new PlovercrestException("Failed to remap file", e);
            }
        }
    }

    public long writeBuffer(final long offset, final ByteBuffer bytes) {
        final ByteBuffer threadBuf = threadLocalBuffer.get().buf;
        final long length = bytes.remaining();
        try {
            threadBuf.position((int) (offset - startOffset));
            threadBuf.put(bytes);
            return length - bytes.remaining();
        } catch (final NullPointerException e) {
            throw new TableClosedException(fileName);
        }
    }

    public long writeLong(final long offset, final long value) {
        try {
            mappedFile.putLong((int) (offset - startOffset), value);
            return 8;
        } catch (final NullPointerException e) {
            throw new TableClosedException(fileName);
        }
    }

    public long writeInt(final long offset, final int value) {
        try {
            mappedFile.putInt((int) (offset - startOffset), value);
            return 4;
        } catch (final NullPointerException e) {
            throw new TableClosedException(fileName);
        }
    }

    public long startOffset() {
        return startOffset;
    }

    public long endOffset() {
        return startOffset + windowSize;
    }

    public long readBuffer(final long offset, final ByteBuffer buffer) {
        final ByteBuffer threadBuf = threadLocalBuffer.get().buf;
        if (threadBuf == null) {
            throw new TableClosedException(fileName);
        }
        threadBuf.position((int) (offset - startOffset));
        final int pos = buffer.position();
        try {
            final int limit = threadBuf.limit();
            threadBuf.limit(threadBuf.position() + buffer.remaining());
            buffer.put(threadBuf);
            threadBuf.limit(limit);
            return buffer.position() - pos;
        } catch (final NullPointerException e) {
            throw new TableClosedException(fileName);
        }
    }

    public long readLong(final long offset) {
        try {
            return mappedFile.getLong((int) (offset - startOffset));
        } catch (final NullPointerException e) {
            throw new TableClosedException(fileName);
        }
    }

    public int readInt(final long offset) {
        try {
            return mappedFile.getInt((int) (offset - startOffset));
        } catch (final NullPointerException e) {
            throw new TableClosedException(fileName);
        }
    }

    public int remaining(final long offset) {
        try {
            return mappedFile.limit() - (int) (offset - startOffset);
        } catch (final NullPointerException e) {
            throw new TableClosedException(fileName);
        }
    }

    /**
     * Is the given offset within this window's range?
     *
     * Note: does NOT check the file size, only the window size.
     */
    public boolean isWithinRange(final long offset) {
        return (startOffset <= offset) && (offset < startOffset + windowSize);
    }

    /**
     * Forces any changes made to be written to disk.
     */
    public void force() {
        mappedFile.force();
    }

    private void unmap(final boolean flush) {
        // Note: mappedFileLock should be held here

        // This is part of a workaround for the lack of a MappedByteBuffer.unmap() function.
        // We make sure all references to mappedFile, and its thread-local duplicate() results,
        // are set to null, so it can be garbage collected as soon as possible.
        // System.gc() is not called here, since that could become a performance issue.

        // http://bugs.sun.com/view_bug.do?bug_id=4724038
        // http://bugs.sun.com/view_bug.do?bug_id=4715154
        if (mappedFile != null) {
            if (flush) {
                mappedFile.force();
            }
            mappedFile = null;
        }
        for (final LocalBuffer localBuf : allLocalBuffers) {
            localBuf.close();
        }
        allLocalBuffers.clear();
    }

    /**
     * The closest we get to unmap(): set the underlying MappedByteBuffer to null.
     */
    public void close(final boolean flush) {
        synchronized (mappedFileLock) {
            closed = true;
            unmap(flush);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy