/* * 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.aio; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.nio.ByteBuffer; import java.util.PriorityQueue; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQNativeIOError; import org.apache.activemq.artemis.core.io.AbstractSequentialFile; import org.apache.activemq.artemis.core.io.DummyCallback; import org.apache.activemq.artemis.core.io.IOCallback; import org.apache.activemq.artemis.core.io.SequentialFile; import org.apache.activemq.artemis.core.journal.impl.SimpleWaitIOCallback; import org.apache.activemq.artemis.jlibaio.LibaioFile; import org.apache.activemq.artemis.journal.ActiveMQJournalLogger; import org.apache.activemq.artemis.utils.ReusableLatch; public class AIOSequentialFile extends AbstractSequentialFile { private boolean opened = false; private LibaioFile aioFile; private final AIOSequentialFileFactory aioFactory; private final ReusableLatch pendingCallbacks = new ReusableLatch(); /** * Used to determine the next writing sequence */ private final AtomicLong nextWritingSequence = new AtomicLong(0); /** * AIO can't guarantee ordering over callbacks. * <br> * We use this {@link PriorityQueue} to hold values until they are in order */ final PriorityQueue<AIOSequentialFileFactory.AIOSequentialCallback> pendingCallbackList = new PriorityQueue<>(); /** * Used to determine the next writing sequence. * This is accessed from a single thread (the Poller Thread) */ private long nextReadSequence = 0; public AIOSequentialFile(final AIOSequentialFileFactory factory, final int bufferSize, final long bufferTimeoutMilliseconds, final File directory, final String fileName, final Executor writerExecutor) { super(directory, fileName, factory, writerExecutor); this.aioFactory = factory; } @Override public boolean isOpen() { return opened; } @Override public int calculateBlockStart(final int position) { int alignment = factory.getAlignment(); int pos = (position / alignment + (position % alignment != 0 ? 1 : 0)) * alignment; return pos; } @Override public SequentialFile cloneFile() { return new AIOSequentialFile(aioFactory, -1, -1, getFile().getParentFile(), getFile().getName(), null); } @Override public synchronized void close() throws IOException, InterruptedException, ActiveMQException { if (!opened) { return; } super.close(); final String fileName = this.getFileName(); try { int waitCount = 0; while (!pendingCallbacks.await(10, TimeUnit.SECONDS)) { waitCount++; if (waitCount == 1) { final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); for (ThreadInfo threadInfo : threads) { ActiveMQJournalLogger.LOGGER.warn(threadInfo.toString()); } factory.onIOError(new IOException("Timeout on close"), "Timeout on close", this); } ActiveMQJournalLogger.LOGGER.warn("waiting pending callbacks on " + fileName + " from " + (waitCount * 10) + " seconds!"); } } catch (InterruptedException e) { ActiveMQJournalLogger.LOGGER.warn("interrupted while waiting pending callbacks on " + fileName, e); throw e; } finally { opened = false; timedBuffer = null; aioFile.close(); aioFile = null; } } @Override public synchronized void fill(final int size) throws Exception { checkOpened(); aioFile.fill(size); fileSize = aioFile.getSize(); } @Override public void open() throws Exception { open(aioFactory.getMaxIO(), true); } @Override public synchronized void open(final int maxIO, final boolean useExecutor) throws ActiveMQException { opened = true; try { aioFile = aioFactory.libaioContext.openFile(getFile(), factory.isDatasync()); } catch (IOException e) { factory.onIOError(e, e.getMessage(), this); throw new ActiveMQNativeIOError(e.getMessage(), e); } position.set(0); fileSize = aioFile.getSize(); } @Override public int read(final ByteBuffer bytes, final IOCallback callback) throws ActiveMQException { checkOpened(); int bytesToRead = bytes.limit(); long positionToRead = position.getAndAdd(bytesToRead); bytes.rewind(); try { // We don't send the buffer to the callback on read, // because we want the buffer available. // Sending it through the callback would make it released aioFile.read(positionToRead, bytesToRead, bytes, getCallback(callback, null)); } catch (IOException e) { factory.onIOError(e, e.getMessage(), this); throw new ActiveMQNativeIOError(e.getMessage(), e); } return bytesToRead; } @Override public int read(final ByteBuffer bytes) throws Exception { SimpleWaitIOCallback waitCompletion = new SimpleWaitIOCallback(); int bytesRead = read(bytes, waitCompletion); waitCompletion.waitCompletion(); return bytesRead; } @Override public void writeDirect(final ByteBuffer bytes, final boolean sync) throws Exception { if (sync) { SimpleWaitIOCallback completion = new SimpleWaitIOCallback(); writeDirect(bytes, true, completion); completion.waitCompletion(); } else { writeDirect(bytes, false, DummyCallback.getInstance()); } } /** * Note: Parameter sync is not used on AIO */ @Override public void writeDirect(final ByteBuffer bytes, final boolean sync, final IOCallback callback) { try { checkOpened(); } catch (Exception e) { ActiveMQJournalLogger.LOGGER.warn(e.getMessage(), e); callback.onError(-1, e.getMessage()); return; } final int bytesToWrite = factory.calculateBlockSize(bytes.limit()); final long positionToWrite = position.getAndAdd(bytesToWrite); AIOSequentialFileFactory.AIOSequentialCallback runnableCallback = getCallback(callback, bytes); runnableCallback.initWrite(positionToWrite, bytesToWrite); runnableCallback.run(); } AIOSequentialFileFactory.AIOSequentialCallback getCallback(IOCallback originalCallback, ByteBuffer buffer) { AIOSequentialFileFactory.AIOSequentialCallback callback = aioFactory.getCallback(); callback.init(this.nextWritingSequence.getAndIncrement(), originalCallback, aioFile, this, buffer); pendingCallbacks.countUp(); return callback; } void done(AIOSequentialFileFactory.AIOSequentialCallback callback) { if (callback.writeSequence == -1) { callback.sequentialDone(); pendingCallbacks.countDown(); } if (callback.writeSequence == nextReadSequence) { nextReadSequence++; callback.sequentialDone(); pendingCallbacks.countDown(); flushCallbacks(); } else { pendingCallbackList.add(callback); } } private void flushCallbacks() { while (!pendingCallbackList.isEmpty() && pendingCallbackList.peek().writeSequence == nextReadSequence) { AIOSequentialFileFactory.AIOSequentialCallback callback = pendingCallbackList.poll(); callback.sequentialDone(); nextReadSequence++; pendingCallbacks.countDown(); } } @Override public void sync() { throw new UnsupportedOperationException("This method is not supported on AIO"); } @Override public long size() throws Exception { if (aioFile == null) { return getFile().length(); } else { return aioFile.getSize(); } } @Override public String toString() { return "AIOSequentialFile:" + getFile().getAbsolutePath(); } // Protected methods // ----------------------------------------------------------------------------------------------------- @Override protected ByteBuffer newBuffer(int size, int limit) { size = factory.calculateBlockSize(size); limit = factory.calculateBlockSize(limit); ByteBuffer buffer = factory.newBuffer(size); buffer.limit(limit); return buffer; } // Private methods // ----------------------------------------------------------------------------------------------------- private void checkOpened() { if (aioFile == null || !opened) { throw new NullPointerException("File not opened, file=null"); } } }