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

org.neo4j.kernel.impl.transaction.log.PhysicalFlushableChannel Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2020 "Graph Foundation,"
 * Graph Foundation, Inc. [https://graphfoundation.org]
 *
 * This file is part of ONgDB.
 *
 * ONgDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
/*
 * Copyright (c) 2002-2020 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.impl.transaction.log;

import java.io.Flushable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;

import org.neo4j.io.ByteUnit;
import org.neo4j.io.fs.StoreChannel;

import static java.lang.Math.min;

/**
 * The main implementation of {@link FlushableChannel}. This class provides buffering over a simple {@link StoreChannel}
 * and, as a side effect, allows control of the flushing of that buffer to disk.
 */
public class PhysicalFlushableChannel implements FlushableChannel
{
    public static final int DEFAULT_BUFFER_SIZE = (int) ByteUnit.kibiBytes( 512 );

    private volatile boolean closed;

    protected final ByteBuffer buffer;
    protected StoreChannel channel;

    public PhysicalFlushableChannel( StoreChannel channel )
    {
        this( channel, DEFAULT_BUFFER_SIZE );
    }

    public PhysicalFlushableChannel( StoreChannel channel, int bufferSize )
    {
        this.channel = channel;
        this.buffer = ByteBuffer.allocate( bufferSize );
    }

    void setChannel( LogVersionedStoreChannel channel )
    {
        this.channel = channel;
    }

    /**
     * External synchronization between this method and close is required so that they aren't called concurrently.
     * Currently that's done by acquiring the PhysicalLogFile monitor.
     */
    @Override
    public Flushable prepareForFlush() throws IOException
    {
        buffer.flip();
        StoreChannel channel = this.channel;
        try
        {
            channel.writeAll( buffer );
        }
        catch ( ClosedChannelException e )
        {
            handleClosedChannelException( e );
        }
        buffer.clear();
        return channel;
    }

    private void handleClosedChannelException( ClosedChannelException e ) throws ClosedChannelException
    {
        // We don't want to check the closed flag every time we empty, instead we can avoid unnecessary the
        // volatile read and catch ClosedChannelException where we see if the channel being closed was
        // deliberate or not. If it was deliberately closed then throw IllegalStateException instead so
        // that callers won't treat this as a kernel panic.
        if ( closed )
        {
            throw new IllegalStateException( "This log channel has been closed", e );
        }

        // OK, this channel was closed without us really knowing about it, throw exception as is.
        throw e;
    }

    @Override
    public FlushableChannel put( byte value ) throws IOException
    {
        bufferWithGuaranteedSpace( 1 ).put( value );
        return this;
    }

    @Override
    public FlushableChannel putShort( short value ) throws IOException
    {
        bufferWithGuaranteedSpace( 2 ).putShort( value );
        return this;
    }

    @Override
    public FlushableChannel putInt( int value ) throws IOException
    {
        bufferWithGuaranteedSpace( 4 ).putInt( value );
        return this;
    }

    @Override
    public FlushableChannel putLong( long value ) throws IOException
    {
        bufferWithGuaranteedSpace( 8 ).putLong( value );
        return this;
    }

    @Override
    public FlushableChannel putFloat( float value ) throws IOException
    {
        bufferWithGuaranteedSpace( 4 ).putFloat( value );
        return this;
    }

    @Override
    public FlushableChannel putDouble( double value ) throws IOException
    {
        bufferWithGuaranteedSpace( 8 ).putDouble( value );
        return this;
    }

    @Override
    public FlushableChannel put( byte[] value, int length ) throws IOException
    {
        int offset = 0;
        while ( offset < length )
        {
            int chunkSize = min( length - offset, buffer.capacity() >> 1 );
            bufferWithGuaranteedSpace( chunkSize ).put( value, offset, chunkSize );

            offset += chunkSize;
        }
        return this;
    }

    private ByteBuffer bufferWithGuaranteedSpace( int spaceInBytes ) throws IOException
    {
        assert spaceInBytes < buffer.capacity();
        if ( buffer.remaining() < spaceInBytes )
        {
            prepareForFlush();
        }
        return buffer;
    }

    /**
     * External synchronization between this method and emptyBufferIntoChannelAndClearIt is required so that they
     * aren't called concurrently. Currently that's done by acquiring the PhysicalLogFile monitor.
     */
    @Override
    public void close() throws IOException
    {
        prepareForFlush().flush();
        closed = true;
        channel.close();
    }

    /**
     * @return the position of the channel, also taking into account buffer position.
     * @throws IOException if underlying channel throws {@link IOException}.
     */
    public long position() throws IOException
    {
        return channel.position() + buffer.position();
    }

    /**
     * Sets position of this channel to the new {@code position}. This works only if the underlying channel
     * supports positioning.
     *
     * @param position new position (byte offset) to set as new current position.
     * @throws IOException if underlying channel throws {@link IOException}.
     */
    public void position( long position ) throws IOException
    {
        // Currently we take the pessimistic approach of flushing (doesn't imply forcing) buffer to
        // channel before moving to a new position. This works in all cases, but there could be
        // made an optimization where we could see that we're moving within the current buffer range
        // and if so skip flushing and simply move the cursor in the buffer.
        prepareForFlush().flush();
        channel.position( position );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy