/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.io.nio;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.concurrent.Executor;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.api.core.ActiveMQIOErrorException;
import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
import org.apache.activemq.artemis.core.io.AbstractSequentialFile;
import org.apache.activemq.artemis.core.io.IOCallback;
import org.apache.activemq.artemis.core.io.SequentialFile;
import org.apache.activemq.artemis.core.io.SequentialFileFactory;
import org.apache.activemq.artemis.journal.ActiveMQJournalBundle;
import org.apache.activemq.artemis.journal.ActiveMQJournalLogger;
import org.apache.activemq.artemis.utils.Env;
public final class NIOSequentialFile extends AbstractSequentialFile {
private FileChannel channel;
private RandomAccessFile rfile;
private final int maxIO;
public NIOSequentialFile(final SequentialFileFactory factory,
final File directory,
final String file,
final int maxIO,
final Executor writerExecutor) {
super(directory, file, factory, writerExecutor);
this.maxIO = maxIO;
}
@Override
public int calculateBlockStart(final int position) {
return position;
}
@Override
public synchronized boolean isOpen() {
return channel != null;
}
/**
* this.maxIO represents the default maxIO.
* Some operations while initializing files on the journal may require a different maxIO
*/
@Override
public synchronized void open() throws IOException {
open(maxIO, true);
}
@Override
public void open(final int maxIO, final boolean useExecutor) throws IOException {
try {
rfile = new RandomAccessFile(getFile(), "rw");
channel = rfile.getChannel();
fileSize = channel.size();
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
}
@Override
public void fill(final int size) throws IOException {
try {
//uses the most common OS page size to match the Page Cache entry size and reduce JVM memory footprint
final int zeroPageCapacity = Env.osPageSize();
final ByteBuffer zeroPage = this.factory.newBuffer(zeroPageCapacity);
try {
int bytesToWrite = size;
long writePosition = 0;
while (bytesToWrite > 0) {
zeroPage.clear();
final int zeroPageLimit = Math.min(bytesToWrite, zeroPageCapacity);
zeroPage.limit(zeroPageLimit);
//use the cheaper pwrite instead of fseek + fwrite
final int writtenBytes = channel.write(zeroPage, writePosition);
bytesToWrite -= writtenBytes;
writePosition += writtenBytes;
}
if (factory.isDatasync()) {
channel.force(true);
}
//set the position to 0 to match the fill contract
channel.position(0);
fileSize = size;
} finally {
//return it to the factory
this.factory.releaseBuffer(zeroPage);
}
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
}
@Override
public synchronized void close() throws IOException, InterruptedException, ActiveMQException {
super.close();
try {
if (channel != null) {
channel.close();
}
if (rfile != null) {
rfile.close();
}
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
channel = null;
rfile = null;
notifyAll();
}
@Override
public int read(final ByteBuffer bytes) throws Exception {
return read(bytes, null);
}
@Override
public synchronized int read(final ByteBuffer bytes,
final IOCallback callback) throws IOException, ActiveMQIllegalStateException {
try {
if (channel == null) {
throw new ActiveMQIllegalStateException("File " + this.getFileName() + " has a null channel");
}
int bytesRead = channel.read(bytes);
if (callback != null) {
callback.done();
}
bytes.flip();
return bytesRead;
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
if (callback != null) {
callback.onError(ActiveMQExceptionType.IO_ERROR.getCode(), e.getLocalizedMessage());
}
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
}
@Override
public void sync() throws IOException {
if (factory.isDatasync() && channel != null) {
try {
channel.force(false);
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
}
}
@Override
public long size() throws IOException {
if (channel == null) {
return getFile().length();
}
try {
return channel.size();
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
}
@Override
public void position(final long pos) throws IOException {
try {
super.position(pos);
channel.position(pos);
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
throw e;
}
}
@Override
public String toString() {
return "NIOSequentialFile " + getFile();
}
@Override
public SequentialFile cloneFile() {
return new NIOSequentialFile(factory, directory, getFileName(), maxIO, null);
}
@Override
public void writeDirect(final ByteBuffer bytes, final boolean sync, final IOCallback callback) {
if (callback == null) {
throw new NullPointerException("callback parameter need to be set");
}
try {
internalWrite(bytes, sync, callback);
} catch (Exception e) {
callback.onError(ActiveMQExceptionType.GENERIC_EXCEPTION.getCode(), e.getMessage());
}
}
@Override
public void writeDirect(final ByteBuffer bytes, final boolean sync) throws Exception {
internalWrite(bytes, sync, null);
}
@Override
protected ByteBuffer newBuffer(int size, final int limit) {
// For NIO, we don't need to allocate a buffer the entire size of the timed buffer, unlike AIO
size = limit;
return super.newBuffer(size, limit);
}
private void internalWrite(final ByteBuffer bytes,
final boolean sync,
final IOCallback callback) throws IOException, ActiveMQIOErrorException, InterruptedException {
if (!isOpen()) {
if (callback != null) {
callback.onError(ActiveMQExceptionType.IO_ERROR.getCode(), "File not opened");
} else {
throw ActiveMQJournalBundle.BUNDLE.fileNotOpened();
}
return;
}
position.addAndGet(bytes.limit());
try {
doInternalWrite(bytes, sync, callback);
} catch (ClosedChannelException e) {
throw e;
} catch (IOException e) {
factory.onIOError(new ActiveMQIOErrorException(e.getMessage(), e), e.getMessage(), this);
}
}
/**
* @param bytes
* @param sync
* @param callback
* @throws IOException
* @throws Exception
*/
private void doInternalWrite(final ByteBuffer bytes,
final boolean sync,
final IOCallback callback) throws IOException {
try {
channel.write(bytes);
if (sync) {
sync();
}
if (callback != null) {
callback.done();
}
} finally {
//release it to recycle the write buffer if big enough
this.factory.releaseBuffer(bytes);
}
}
@Override
public void copyTo(SequentialFile dstFile) throws IOException {
if (ActiveMQJournalLogger.LOGGER.isDebugEnabled()) {
ActiveMQJournalLogger.LOGGER.debug("Copying " + this + " as " + dstFile);
}
if (isOpen()) {
throw new IllegalStateException("File opened!");
}
if (dstFile.isOpen()) {
throw new IllegalArgumentException("dstFile must be closed too");
}
try (RandomAccessFile src = new RandomAccessFile(getFile(), "rw");
FileChannel srcChannel = src.getChannel();
FileLock srcLock = srcChannel.lock()) {
final long readableBytes = srcChannel.size();
if (readableBytes > 0) {
try (RandomAccessFile dst = new RandomAccessFile(dstFile.getJavaFile(), "rw");
FileChannel dstChannel = dst.getChannel();
FileLock dstLock = dstChannel.lock()) {
final long oldLength = dst.length();
final long newLength = oldLength + readableBytes;
dst.setLength(newLength);
final long transferred = dstChannel.transferFrom(srcChannel, oldLength, readableBytes);
if (transferred != readableBytes) {
dstChannel.truncate(oldLength);
throw new IOException("copied less then expected");
}
}
}
}
}
}